This tutorial will teach you how to use the Sprite Kit framework to create a question-based facts game. It is designed for both novice and advanced users. Along the way, you will apply the Sprite Kit core. The Facts Game series is divided into three tutorials in order to completely cover each topic.
Introduction
This series is divided into three tutorials: Project Setup, Interface Creation, and Game Logic. Each part will produce a practical result, and the sum of all parts will produce the final game. After the three part series, readers will be able to create a simple question-and-answer tap game featuring sounds, animations, menus, rules, timers, and UIKit interaction. Despite the fact that each part can be read independently, for a better understanding we suggest that you follow the tutorials in order. We also included the source code for each part separately, thus providing a way to start the tutorial at any section.
At the end of this tutorial, the result will look like this:
1. Initial Setup
Start a new SpriteKit
Xcode project and name it Facts. The deployment information used across the three parts is:
- Deployment Target: 7
- Devices: iPad
- Device Orientation: Portrait
- Hide status bar style
Note that the following four frameworks are automatically included in the project: CoreGraphics
, UIKit
, SpriteKit
, and Foundation
. You will see three classes, an image (Spaceship.png
) and a Main.storyboard
file. You can delete the image since you will not use it.
Now open the Myscene.m
and comment the whole -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method. You do not need to create a tap gesture in this scene because you will use several UIKit UIButtons
for user interaction.
2. Backgrounds, Labels, and Buttons
Start by changing the background color of the SKScene by changing the self.backgroundColor
properties. Note that it uses an SKColor
object with three properties for red, blue, and green components. Now change the myLabel
text to "Facts!!" or to any other title of your interest.
The next step is to add the necessary resources to your project; find them in the Resources folder. Despite the fact that you will not use all the resources now, you should add them all. Since you will use the UIKit framework, add a specific method -(void) didMoveToView:(SKView *)view
that detects if the scene transition occurred without problems. If no problems occurs, the app loads resources into the screen. Add this method to your code. Inside you will declare three buttons: one for starting the game, one for options, and the last one to exit the app.
Add three UIButtons
to your MyScene.m
. It will look like this:
@implementation MyScene { UIButton *startButton; UIButton *optionsButton; UIButton *exitButton; }
Each button has specific configurations regarding the frame's location and size, the background color, and the background image. You should try to create a UIButton
and add it to the scene view. The buttons should look like the following snippet:
-(void) didMoveToView:(SKView *)view{ startButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; startButton.frame = CGRectMake(CGRectGetMidX(self.frame)-100, CGRectGetMidY(self.frame), 200, 70.0); startButton.backgroundColor = [UIColor clearColor]; [startButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ]; UIImage *buttonImageNormal = [UIImage imageNamed:@"StartBtn.png"]; UIImage *strechableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0]; [startButton setBackgroundImage:strechableButtonImageNormal forState:UIControlStateNormal]; [self.view addSubview:startButton]; optionsButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; optionsButton.frame = CGRectMake(CGRectGetMidX(self.frame)-100, CGRectGetMidY(self.frame)+90, 200, 70.0); optionsButton.backgroundColor = [UIColor clearColor]; [optionsButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ]; UIImage *buttonOptionsImageNormal = [UIImage imageNamed:@"OptionsBtn.png"]; UIImage *strechableButtonOptionsImageNormal = [buttonOptionsImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0]; [optionsButton setBackgroundImage:strechableButtonOptionsImageNormal forState:UIControlStateNormal]; [self.view addSubview:optionsButton]; exitButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; exitButton.frame = CGRectMake(CGRectGetMidX(self.frame)-100, CGRectGetMidY(self.frame)+180, 200, 70.0); exitButton.backgroundColor = [UIColor clearColor]; [exitButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ]; UIImage *buttonExitImageNormal = [UIImage imageNamed:@"ExitBtn.png"]; UIImage *strechableButtonExitImageNormal = [buttonExitImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0]; [exitButton setBackgroundImage:strechableButtonExitImageNormal forState:UIControlStateNormal]; [self.view addSubview:exitButton]; }
Run
the project and see the new root interface. If you want, you can adjust the location, size, and position of the interface objects. If you want to learn more about the SKLabelNode
or UIButton
, I advise you to play with those classes a bit more. The interface should look similar to the following image:
It is now time to add more classes to your project.
3. New Game and Options SKScenes
Step 1
Since you added several buttons, it is now time to use them to redirect the user to another interface. Add two new classes. Choose File > New > File and Objective-C class. Name the classes FactsScene
and OptionsScene
. Note that both will be a superclass of SKScene
.
Four new files are available in your project: FactsScene.h
, FactsScene.m
, OptionsScene.h
, and OptionsScene.m
. Note that the implementation files of both classes are almost empty. Therefore, you must add the class initializers. For both classes use the -initWithSize:
method. Try and add it by yourself. If you have any trouble, the following snippet will help you:
-(id)initWithSize:(CGSize)size{ if (self = [super initWithSize:size]) { NSLog(@"Opstions Scene"); } return self; }
Once again you should apply it for both classes. Now that you have the class initializers, it is now time to modify the UIButton
code from the last step to change between classes. Go back to MyScene.m
and import both classes in the import section:
#import "OptionsScene.h" #import "FactsScene.h"
Now in the startButton
, optionsButton
, and exitButton
, you need to add a custom action. At each button, add the corresponding line of code.
[startButton addTarget:self action:@selector(moveToGame) forControlEvents:UIControlEventTouchUpInside]; [optionsButton addTarget:self action:@selector(moveToOptions) forControlEvents:UIControlEventTouchUpInside]; [exitButton addTarget:self action:@selector(endApplication) forControlEvents:UIControlEventTouchUpInside];
You should see three warnings ("undeclared selector"). Don't worry! It is now time to add those methods to your class.
-(void) moveToGame {} -(void) moveToOptions {} -(void) endApplication {}
Each one is responsible for a specific action and the name of each method is self-explanatory. The -(void) moveToGame
and -(void) moveToOptions
methods are similar, since both are used to create a transition from this SKScene
to another. At each method, you should create an SKScene
destination object and transition object, and present the new scene. Note that since you are using UIKit objects, you also need to remove them from the scene at the SKScene
transitions. If you experience trouble with these steps, the following snippets will guide you:
-(void) moveToGame{ NSLog(@"moveToGame"); FactsScene* factsScene = [[FactsScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))]; SKTransition* transition = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1]; [startButton removeFromSuperview]; [optionsButton removeFromSuperview]; [exitButton removeFromSuperview]; [self.scene.view presentScene:factsScene transition:transition]; } -(void) moveToOptions{ NSLog(@"moveToOptions"); OptionsScene* optionsScene = [[OptionsScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))]; SKTransition* transition = [SKTransition revealWithDirection:SKTransitionDirectionLeft duration:1]; [startButton removeFromSuperview]; [optionsButton removeFromSuperview]; [exitButton removeFromSuperview]; [self.scene.view presentScene:optionsScene transition:transition]; }
The last method -(void) endApplication
is simpler since it is only used to end the application.
-(void) endApplication{ [startButton removeFromSuperview]; [optionsButton removeFromSuperview]; [exitButton removeFromSuperview]; exit(0); }
Step 2
It is now time to Run
the code and test the new features. If everything is fine you should be able to change the default view into the New Game
view and Options
view. However, you cannot go back to the main menu. Let's change that right now.
Your next steps are to add a UIButton to the Options
scene, program it to make a transition back to the main menu, and choose to create a transition effect (or not). In order to do this, several steps are necessary:
- Add the
-(void) didMoveToView:(SKView *)view
method - Add a UIButton object
- Configure the UIButton
- Add the
@selector
method - Import the
MyScene.h
First, go to the OptionsScene.h
and add:
@property (nonatomic, retain) UIButton* backButton;
Now change your attention to OptionsScene.m
. The snippets for the pseudo-code are to add the didMoveToView
method, the UIButton, and the button configuration.
-(void) didMoveToView:(SKView *)view{ _backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; _backButton.frame = CGRectMake(CGRectGetMidX(self.frame)-100, CGRectGetMidY(self.frame)+180, 200, 70.0); _backButton.backgroundColor = [UIColor clearColor]; [_backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal ]; UIImage *buttonExitImageNormal = [UIImage imageNamed:@"ExitBtn.png"]; UIImage *strechableButtonExitImageNormal = [buttonExitImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0]; [_backButton setBackgroundImage:strechableButtonExitImageNormal forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(moveToHome) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_backButton]; }
Next, add the @selector
method.
-(void) moveToHome{ MyScene* myScene = [[MyScene alloc] initWithSize:CGSizeMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame))]; [_backButton removeFromSuperview]; [self.scene.view presentScene:myScene]; }
Run
your code to check if everything is up and running. You can now jump from the root interface to the OptionsScene
and back again. Now, we can populate the Options scene.
4. Options SKScene
Step 1
The game options are where the user can change the game's configurations. For now, you will only focus on two options: music and sound. You need to add an SKLabelNode
, a UISwitch
, and an AVAudioPlayer
object to OptionsScene.h
:
@property (nonatomic, retain) IBOutlet UISwitch *musicSwitch; @property (nonatomic, retain) IBOutlet UISwitch *soundSwitch; @property (nonatomic, retain) SKLabelNode* soundTitle; @property (nonatomic, retain) SKLabelNode* musicTitle;
Then on the OptionsScene.m
inside the didMoveToView
method, allocate the objects as:
_soundSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(CGRectGetMidX(self.frame)+50, CGRectGetMidY(self.frame)-26, 100, 100)];[_soundSwitch addTarget: self action: @selector(flipMusicAndSound:) forControlEvents:UIControlEventValueChanged]; [self.view addSubview: _soundSwitch]; _musicSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(CGRectGetMidX(self.frame)+50, CGRectGetMidY(self.frame)+50, 100, 100)]; [_musicSwitch addTarget: self action: @selector(flipMusicAndSound:) forControlEvents:UIControlEventValueChanged]; [self.view addSubview: _musicSwitch]; _soundTitle = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; [_soundTitle setText:@"Sound"]; [_soundTitle setFontSize:40]; [_soundTitle setPosition:CGPointMake(CGRectGetMidX(self.frame)-80, CGRectGetMidY(self.frame))]; [self addChild:_soundTitle]; _musicTitle = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; [_musicTitle setText:@"Music"]; [_musicTitle setFontSize:40]; [_musicTitle setPosition:CGPointMake(CGRectGetMidX(self.frame)-80, CGRectGetMidY(self.frame)-80)]; [self addChild:_musicTitle];
You will get a warning. To correct it, add the flipMusicAndSound
method. This method checks the state of _soundSwitch
and _musicSwitch
UISwitches, and sets a default key to each property using NSUserDefaults
. Before you complete the method, you should add the NSUserDefaults
object to the class.
@implementation OptionsScene{ NSUserDefaults* defaults; }
Then you should allocate it in the initWithSize
method as:
defaults = [NSUserDefaults standardUserDefaults];
Step 2
Now you are ready to use the advantages of NSUserDefaults
. You will use it to store the state of the sound and music properties across the whole app. You can write and complete the flipMusicAndSound
method. You will use the integer
value 1 to set the sound and music on, and 0 otherwise. The flipMusicAndSound
is below.
- (IBAction)flipMusicAndSound:(id)sender { if (_musicSwitch.on) { [defaults setInteger:1 forKey:@"music"]; } else { [defaults setInteger:0 forKey:@"music"]; } if (_soundSwitch.on) { [defaults setInteger:1 forKey:@"sound"]; } else { [defaults setInteger:0 forKey:@"sound"]; } }
If you Run
the project and play with both switches, you will see that every time you go to the OptionsScene
both switches have a default state, rather than the last state defined. To change that, we must read the NSUserDefaults
object at the didMoveToView
method.
You will define two variables for sound and music and check each inherent property key. It is a very simple and logical decision test. If you are unsure how to do this, the next snippet will assist you:
long soundDefaults = [defaults integerForKey:@"sound"]; long musicDefaults = [defaults integerForKey:@"music"]; if (soundDefaults == 1) { [_soundSwitch setOn:YES animated:YES]; } else { [_soundSwitch setOn:FALSE animated:YES]; } if (musicDefaults == 1) { [_musicSwitch setOn:YES animated:YES]; } else{ [_musicSwitch setOn:FALSE animated:YES]; }
The last step in the OptionsScene.m
file is to remove the UISwitch items from the superview every time the user taps the back button:
[_soundSwitch removeFromSuperview]; [_musicSwitch removeFromSuperview];
Here's an illustration of the Options interface:
Step 3
Move to the FactsScene.m
. Now we can add the sound and music elements. Like with the OptionsScene
, we need to use NSUserDefaults
to get the value for music and sound. We will also need an object to store the music path and another for the iOS music player. Thus we will declare them as:
@implementation FactsScene{ NSUserDefaults* defaults; NSString* musicPath; }
Also in the FactsScene.h
:
@property (nonatomic, strong) AVAudioPlayer* musicPlayer;
You must declare (as you did with the last class) the method -(void) didMoveToView:(SKView *)view
. Inside you need to check if the music and sound preferences are on and allocate the music preferences. Both can be achieved through:
musicPath = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"mp3"]; _musicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:musicPath] error:NULL]; _musicPlayer.numberOfLoops = -1; // loop forever _musicPlayer.volume = 0.7; long musicFlag = [defaults integerForKey:@"music"]; if (musicFlag == 1){ [_musicPlayer play]; } else { [_musicPlayer stop]; }
The above-mentioned code only starts or stops the music if its property is on. We also want to create sounds when a tap occurs. So, we need to catch the touch interactions and react accordingly.
Do you remember the -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method from the MyScene.m
implementation file? You will define it and use it here.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ for (UITouch *touch in touches) { [self touchWillProduceASound:@"False"]; } }
Note that, the -(void) touchWillProduceASound:(NSString*)answer
is not declared yet. Declare it. It's used to test if a given touch is "correct" or "incorrect" (there will be more on that in the next tutorial). It parses the (NSString*)answer
and produces a sound accordingly to the content of that NSString.
The sound is created with the SKAction
object. It creates a sound object and plays a specific sound. Once again, this occurs only if the sound property (given by the NSUserDefaults
) is on.
-(void) touchWillProduceASound:(NSString*)answer{ long soundFlag = [defaults integerForKey:@"sound"]; if (soundFlag == 1){ SKAction* sound; if ([answer isEqualToString:@"False"]) { sound = [SKAction playSoundFileNamed:@"beep.mp3" waitForCompletion:YES]; NSLog(@"inside"); } [self runAction:sound]; } }
Run the project and test the new music and sound interactions. Activate and deactivate both the sound and music UISwitches and move to the New Game scene to test them correctly.
Conclusion
This is the end of the first tutorial! At this point you can initiate and configure a SpriteKit project, add and initialize SKScene
objects, navigate between SKScene
objects, interact between SpriteKit and UIKit frameworks, and add and remove sounds and music. In the following tutorials, you will deal with Interface Creation and Game Logic.
Comments