Continuing with my intro into IOS development, let’s play with SpriteKit. I’m going to use the same baseline as the appcoder maze game but do it better.
Instead of using UIImageViews and CASimpleAnimation I’ve having a crack at SpriteKit animation.
I created a new repo on github and used XCode to start a new project of type Game, selecting SpriteKit. Set to landscape right Orientation, confirm status bar is hidden.
Run it and we have hello world at 60fps. Click to introduce spaceships into the scene.
There’s quite a lot more to this template than the single view project:
- SKView renders the content in scenes. This is set up on the GameViewController in viewDidLoad
- Scenes extend SKScene. One for each major section of the the app. didMoveToView allows set up of the scene.
The scene dimensions/coordinate system are confusing at first. I updated to the label to show the difference between the scene dimensions (self.view.bounds = 1024x768) and the self.frame dimensions (667x375). String concatenation is a unexpected pain in Objective-C, BTW.
This article describes a correction required to the default project related to scene dimensions. As instructed there, in the view controller I’ve switched from didMoveToView to viewWillLayoutSubviews to setup the scene as the bounds are better known.
Within viewWillLayoutSubviews, after creating the scene I set its size to match that provided by the frame. Now they’re matching :-) It’s probably stupid to do this is supporting multiple sizes - better to use a standard scene size and set up the scaling correctly. Read all of this page in the SDK docs. I also modified GameScene.sks to hardcode the size to the same value, and set gravity to 0.0.
Added the pacman as a sprite loaded from the bundle Images.xcassets. Pretty straight-forward in code. Added the ghosts as sprites. I added SKActions to move up and down, linked together with SKAction sequence. It’s repeated indefinitely with SKAction repeatActionForever. Their initial positions are calculated from the dimensions of the scene and their own height to start on the edges.
For the walls and exit, I added these to the game scene using the visual editor. I can get a reference to them in the scene using scene.childWithNodeName. Now that I’m using the visual editor, adjusted the position settings for the ghosts to fit the populated scene.
Next up, add the listener for the accelerometer.
In the view controller, added core motion dependency and setup the queue and block callback. Added the pacman model to recalculate position from the acceleration data Updated the build phrases into link with the core motion lib.
Added a repaintPacman method to the scene that’s invoked by the block after position is recalculated. This is probably not the best spot for this method. Actually, it’s definitely not. The scene’s physics can be used to apply a force to the pacman
Updated the sprite created to create an SKPhysicsBody from the bounding frame rectangle. Gave them some mass. Within the visual editor, also enabled non-dynamics physics for the wall sprites and exit.
Now the animated ghosts collide with the walls. Nice. Looks like I need a bounding rectangle around the scene too though. This is the “edge loop” added to the scene using the view’s bonding rectangle. That did the trick. The ghost’s timing could be adjusted now (was they paused after a collection to finish their action), but it’ll do for now.
Now, back to animating the pacman…
It looks like it’s not necessary to have a model that calculates position - that’s why there’s a physics engine. I’m taking that back out and applying an impulse force to the pacman using the accelerometer input. I can’t test it properly until I can deploy to a real device though.
To test it out I added some initial force to pacman, and modify the touchesBegin listener to apply a force impulse relative to the vector between the touched position and pacman’s position. This works okay to see everything move about.
Next up is do to something else on collisions…
This is seems a good starting point. Working with collisions and contacts in Sprite Kit
Set up the collision and contact bitmasks so collisions between ghosts and pacman are reported. I tested this and realised it’s essential to keep all the collisions enabled, even if we’re not interested all all contacts for them. ie. ghosts still need to collide with the edge and walls. In fact, once used it’s necessary to set it correctly for every sprint. The contact mask for the walls in the visual editor needed to be changed from 0x1111111…
Updated the scene’s physicsWorld.contactDelgate to the scene’s class, and updated the scene to implement