There is a hero that is controlled by tapping on the screen. I want the hero too look a little different every time the screen gets touched.
What I did is setting up two images that are a little different. I want the image of the hero to be changed when there is a touch event.
Until now I set up an array to save the information in but it kinda won't work out:
NSMutableArray *heroFrames = [NSMutableArray array];
NSString* textureName = nil;
if (UITouchPhaseBegan) {
textureName = #"hero1.gif";
}
else {
textureName = #"hero2.gif";
}
SKTexture* texture = [SKTexture textureWithImageNamed:textureName];
[heroFrames addObject:texture];
[self setHeroFrames:HeroFrames];
self.hero = [SKSpriteNode spriteNodeWithTexture:[_heroFrames objectAtIndex:1]];
I get an exception when running this.. Any other idea how to achieve my problem?
Thanks guys!
Welcome to SO.
Try this code:
#import "MyScene.h"
#implementation MyScene
{
BOOL myBool;
SKSpriteNode *hero;
SKTexture *texture1;
SKTexture *texture2;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
myBool = false;
texture1 = [SKTexture textureWithImageNamed:#"hero1.gif"];
texture2 = [SKTexture textureWithImageNamed:#"hero2.gif"];
hero = [SKSpriteNode spriteNodeWithTexture:texture1];
hero.position = CGPointMake(200, 150);
[self addChild:hero];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myBool == true)
{
hero.texture = texture1;
myBool = false;
} else
{
hero.texture = texture2;
myBool = true;
}
}
Related
I have a SKSpriteNode, I'm Trying to animate it in my touchesbegan and the animation works fine but when it's done animating, the default texture for my SKSpriteNode is gone. My R.atlas contains images named R1,R2..R7 ,What am I doing wrong? Thanks a lot.
#interface MyScene()
{
SKSpriteNode * rightTubeNode;
}
#property NSArray* rightTubeAnimationArray;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKTextureAtlas *rightTubeAtlas = [SKTextureAtlas atlasNamed:#"R"];
rightTubeTexture = [rightTubeAtlas textureNamed:#"R1"];
NSMutableArray *rightAnimFrames = [NSMutableArray array];
for (int i = 1; i <= rightTubeAtlas.textureNames.count; ++i){
NSString *texture =[NSString stringWithFormat:#"R%d",i];
[rightAnimFrames addObject:[rightTubeAtlas textureNamed:texture]];
}
self.rightTubeAnimationArray = rightAnimFrames;
rightTubeNode = [self createRightTubeNode];
rightTubeNode.position = CGPointMake(self.frame.size.width/2+rightTubeNode.frame.size.width/2,50);
rightTubeNode.zPosition = 0.1;
[self addChild:rightTubeNode];
}
return self;
}
- (SKSpriteNode *)createRightTubeNode
{
SKSpriteNode *rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube.name = #"rightTubeNode";
return rightTube;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint nodesLocation = CGPointMake(location.x,self.frame.size.height/2);
if (nodesLocation.x>self.frame.size.width/2) {
SKNode *archerNode = [self childNodeWithName:#"rightTubeNode"];
if (archerNode != nil){
SKAction *animate = [SKAction animateWithTextures:self.rightTubeAnimationArray
timePerFrame: 0.02];
[archerNode runAction:animate];
}
}
}
}
Re-adding the R.atlas (with checking the Create groups options box) without any further changes did the trick, Thanks to #Whirlwind
I have got the following problem in SpriteKit: My aim is to let bonbons fall continuously from the top of the screen. These bonbons can be collected by the player. Every 0.8 seconds or so, another bonbon should fall down and when the player collides with one of the bonbons, it should disappear. This is my code:
-(void)populate {
for (int i = 0; i < 2; i++) {
[self generate];
}
}
-(void)generate {
Y = (arc4random() % 280) - 140;
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
bonbon.size = CGSizeMake(20, 20);
bonbon.name = #"bonbon";
bonbon.physicsBody.categoryBitMask = bonbonCategory;
bonbon.position = CGPointMake(Y, 500);
bonbon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bonbon.size.width/2];
bonbon.physicsBody.dynamic = YES;
[bonbon addChild:bonbon];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString: #"wallLeft"] || [contact.bodyB.node.name isEqualToString: #"wallLeft"])
{
[hero.physicsBody applyImpulse: CGVectorMake(100, 60)];
self.jumpDirection = YES;
}
else if ([contact.bodyA.node.name isEqualToString: #"bonbon"] || [contact.bodyB.node.name isEqualToString: #"bonbon"])
{
[world enumerateChildNodesWithName:#"bonbon" usingBlock:^(SKNode *node, BOOL *stop) {
PointsLabel *pointsLabel = (PointsLabel *)[self childNodeWithName:#"pointsLabel"];
[pointsLabel increment];
NSLog(#"didContactBonbon");
[bonbon removeFromParent];
NSLog(#"removedFromParent");
}];
}
else {
[hero.physicsBody applyImpulse: CGVectorMake(-100, 60)];
self.jumpDirection = NO;
}
}
I hope you understand my problem. Currently, no bonbons are falling at all. If you need more information, please do not hesitate to ask.
Greets
edit: I hope the formatting is better now. I am quite a newbie, i'm sorry for foolish mistakes :) This is in my MyScene.m, when is call the populate in the initWithSize method with [self populate]; I receive the message signal SIGABRT. What did I do wrong?
I think the problem is that you are adding your SKSpriteNode named bonbon to itself with :
- (void)generate {
...
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
...
[bonbon addChild:bonbon];
}
Try to add bonbon to your SKScene instance.
To use an SKView instance you have to create a UIViewController and give its view the SKView class. You can do that in your storyboard.
Then, in your viewController :
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// Configure the scene
SKScene *scene = [SKScene sceneWithSize:self.view.bounds.size];
// You can add objects to your scene
[scene addChild:bonbon];
// Present the scene.
[skView presentScene:scene];
}
#end
This is a very simple example but if you want more information, I suggest you read this tutorial: Sprite Kit Tutorial for Beginners.
Whenever I want to transition to a certain scene, it takes a couple of seconds before the SKTransition even starts. Is it possible to have all scenes initialized before the game starts?
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition moveInWithDirection:SKTransitionDirectionUp duration:0.5];
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
I've tried this with didMoveToView as well:
#implementation NewScene
-(id)initWithSize:(CGSize)size {
if(self = [super initWithSize:size]) {
// Load a bunch of stuff like this:
SKSpriteNode *menuButton = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenu"];
menuButton.position = CGPointMake(self.frame.size.width/2,self.frame.size.height/4);
menuButton.name = #"menuButton";
[self addChild:menuButton];
[menuButton setScale:0.8];
}
}
How can I make sure that my Sprite Kit game runs smoothly?
EDIT:
It turns out the problem was that I kept putting the main thread to sleep to fade out music. I've made all my audio methods run in the background and it works fine now.
A good approach is to load all resources asyncronously before SKScene's initialization. Apple uses this approach in Adventure game:
NewScene.h:
typedef void (^AGAssetLoadCompletionHandler)(void);
#interface GameScene : SKScene<SKPhysicsContactDelegate, UIGestureRecognizerDelegate>
+ (void)loadSceneAssetsWithCompletionHandler:(AGAssetLoadCompletionHandler)handler;
#end
NewScene.m:
#implementation NewScene
-(id)initWithSize:(CGSize)size {
if(self = [super initWithSize:size]) {
// Load a bunch of stuff like this:
SKSpriteNode *menuButton = [SKSpriteNode spriteNodeWithTexture:[self menuButtonTexture];
menuButton.position = CGPointMake(self.frame.size.width/2,self.frame.size.height/4);
menuButton.name = #"menuButton";
[self addChild:menuButton];
[menuButton setScale:0.8];
}
}
#pragma mark - Shared Assets
+ (void)loadSceneAssetsWithCompletionHandler:(AGAssetLoadCompletionHandler)handler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Load the shared assets in the background.
[self loadSceneAssets];
if (!handler) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Call the completion handler back on the main queue.
handler();
});
});
}
+ (void)loadSceneAssets {
sBackgroundTexture = [SKTexture textureWithImageNamed:#"background"];
sMenuButtonTexture = [SKTexture textureWithImageNamed:#"mainMenu"];
// etc.
}
static SKTexture *sBackgroundTexture = nil;
- (SKTexture *)backgroundTexture {
return sBackgroundTexture;
}
static SKTexture *sMenuButtonTexture = nil;
- (SKTexture *)menuButtonTexture {
return sMenuButtonTexture;
}
#end
Then just present NewScene from your UIViewController:
if (!self.skView.scene) {
CGSize viewSize = self.view.bounds.size;
// Here you can present some loading scene or splash screen
[NewScene loadSceneAssetsWithCompletionHandler:^{
NewScene *scene = [[NewScene alloc] initWithSize:viewSize];
[self.skView presentScene:scene transition:[SKTransition crossFadeWithDuration:1.f]];
}];
}
Im making a game where the colour of a square will change every second and the background will also change colour every second, the user has to tap the square when it is the same colour as the background and the score will increase. But i cant work out how to do this.
This is the code i have so far:
#import "MyScene.h"
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
[self performSelector:#selector(backgrounds) withObject:nil ];
[self performSelector:#selector(createSquare) withObject:nil afterDelay:0];
[self performSelector:#selector(createPSquare) withObject:nil afterDelay:0];
}
return self;
}
-(void) backgrounds {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"blueOutline"];
background.name = #"blueOutline";
background.size = CGSizeMake(320, 480);
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:background];
//meathod sequence at interval
}
-(void) createSquare {
SKSpriteNode *blueSprite = [SKSpriteNode spriteNodeWithImageNamed:#"blue"];
blueSprite.name = #"blueSprite";
blueSprite.size = CGSizeMake(50, 50);
blueSprite.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:blueSprite];
//meathod sequence at interval
}
-(void) createPSquare {
SKSpriteNode *pinkSprite = [SKSpriteNode spriteNodeWithImageNamed:#"pink"];
pinkSprite.name = #"pinkSprite";
pinkSprite.size = CGSizeMake(50, 50);
pinkSprite.position = CGPointMake(CGRectGetMidX(self.frame)+10,CGRectGetMidY(self.frame));
[self addChild:pinkSprite];
//meathod sequence at interval
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"blueSprite"]) {
[node runAction:[SKAction removeFromParent]]; //Removes Sprite from parent
}
if ([node.name isEqualToString:#"pinkSprite"]) {
[node runAction:[SKAction removeFromParent]]; //Removes Sprite from parent
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
I suggest you generalize the code that creates the squares and backgrounds to simplify adding more colors to your game. Here's an example of how to do that:
Define a type that identifies the type of sprite node to create
typedef NS_ENUM (NSInteger, SpriteType) {
SpriteTypeBackground,
SpriteTypeSquare
};
This method adds a square and a background to the scene each with a randomly selected color
- (void) addSquareAndBackground {
_background = [self createSpriteWithType:SpriteTypeBackground];
_square = [self createSpriteWithType:SpriteTypeSquare];
}
This removes the square and background from the scene
- (void) removeSquareAndBackground {
[_background removeFromParent];
[_square removeFromParent];
}
This creates either a square or a background sprite based on the specified type
-(SKSpriteNode *) createSpriteWithType:(SpriteType)type {
// Select a color randomly
NSString *colorName = [self randomColorName];
SKSpriteNode *sprite;
if (type == SpriteTypeBackground) {
NSString *name = [NSString stringWithFormat:#"%#Outline",colorName];
sprite = [SKSpriteNode spriteNodeWithImageNamed:name];
sprite.name = name;
sprite.size = CGSizeMake(320, 480);
}
else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:colorName];
sprite.name = [NSString stringWithFormat:#"%#Sprite",colorName];
sprite.size = CGSizeMake(50, 50);
}
sprite.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:sprite];
return sprite;
}
Randomly select a color name
// Set the total number of colors here
#define kNumberOfColors 2
- (NSString *) randomColorName {
NSString *colorName;
switch (arc4random_uniform(kNumberOfColors)) {
case 0:
colorName = #"blue";
break;
case 1:
colorName = #"pink";
break;
// Add more colors here
default:
break;
}
return colorName;
}
Add this to your touchesBegan method to test for a color match
if (node == _square) {
// Extract the color name from the node name
NSArray *squareNameParts = [node.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Extract the color name from the node name
NSArray *backgroundNameParts = [_background.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Compare if the colors match
if ([backgroundNameParts[0] isEqualToString: squareNameParts[0]]) {
NSLog(#"Score");
}
}
All that's left is to create an SKAction that calls addSquareAndBackground, waits for one second, and then calls removeSquareAndBackground. Lather, rinse, repeat!
EDIT: Add this above your #implementation MyScene statement:
#interface MyScene()
#property SKSpriteNode *background;
#property SKSpriteNode *square;
#end
I am having an issue with the sprite animation in my game.
In the current code posted below, the sprite will show on the screen but does not do the walking animation; it is only a static frame of one of the images in the walk cycle.
The init function:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize: size]) {
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
// Create the game nodes
// Background
_backgroundNode = [self createBackgroundNode];
[self addChild: _backgroundNode];
// Foreground
_foregroundNode = [SKNode node];
[self addChild: _foregroundNode];
// Add the Player
_thePlayer = [self createPlayer];
[_foregroundNode addChild:_thePlayer];
[self walkingQueen];
}
return self;
}
Function creating the player / filling in the animation array:
-(SKSpriteNode *)createPlayer
{
SKSpriteNode *playerNode = [SKSpriteNode node];
//SKSpriteNode *_player = [SKSpriteNode node];
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenAnimatedAtlas = [SKTextureAtlas atlasNamed: #"QueenSprites"];
SKTexture *walk1 = [queenAnimatedAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenAnimatedAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenAnimatedAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
/*
int numImages = 9;
for (int i = 7; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"queen%d", i];
SKTexture *temp = [queenAnimatedAtlas textureNamed: textureName];
[walkFrames addObject: temp];
}
*/
//_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
SKSpriteNode *_player = [SKSpriteNode spriteNodeWithTexture:temp];
_player.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
//[self addChild:_player];
//[self walkingQueen];
[playerNode addChild:_player];
[self walkingQueen];
return playerNode;
}
And the function for starting the walk animation:
-(void)walkingQueen
{
for (int i = 0; i < _queenWalkingFrames.count; i++) {
if (_queenWalkingFrames[i] == NULL) {
NSLog(#"ERROR!");
}
}
[_thePlayer runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:_queenWalkingFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:#"walkingInPlaceQueen"];
//return;
}
I played around with the code a bit and the animation DOES work IF I ignore the concept of layers (background / foreground layers) and stick the creation of the player into the init function (basically taking what's inside the createPlayer() and sticking it inside initWithSize()).
I do need to have the layers for my game and everything seems like it should work; had no idea that moving a few lines of code into its own function block would create such an issue. The check inside the walkingQueen() function did not return any errors so I assume my array has been filled with the sprite images.
_thePlayer = [self createPlayer];
This means _thePlayer is nil until after the createPlayer method returns. Thus in the walkingQueen method _thePlayer is nil and the actions don't run.
Replace this:
SKSpriteNode *playerNode = [SKSpriteNode node];
with
_thePlayer = [SKSpriteNode node];
and use _thePlayer instead of playerNode. You can also skip returning and assigning it, it's an ivar after all.
Re-wrote the createPlayer() function. Correct implementation is much simpler and as follows:
-(SKSpriteNode *)createPlayer
{
NSMutableArray *walkFrames = [NSMutableArray array];
SKTextureAtlas *queenWalkingAtlas = [SKTextureAtlas atlasNamed:#"QueenSprites"];
SKTexture *walk1 = [queenWalkingAtlas textureNamed:#"queen7"];
SKTexture *walk2 = [queenWalkingAtlas textureNamed:#"queen8"];
SKTexture *walk3 = [queenWalkingAtlas textureNamed:#"queen9"];
walkFrames[0] = walk1;
walkFrames[1] = walk2;
walkFrames[2] = walk3;
_queenWalkingFrames = walkFrames;
SKTexture *temp = _queenWalkingFrames[0];
_thePlayer = [SKSpriteNode spriteNodeWithTexture:temp];
_thePlayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
return _thePlayer;
}
Basically doing as the post above suggested, get rid of the ivars, use the function ONLY to fill the array with the appropriate images. Then, in the init function, call createPlayer(), add it to the foreground layer, and then call walkingQueen().