Xamarin Recipe Cook-off: Using SpriteKit on iOS7 with C#

monkeyIn this post I’m going to walk you through a tutorial that will show you the basics of Apple’s new SpriteKit API for iOS7. To follow the tutorial, you will need basic knowledge of iOS application development and C# / Xamarin.iOS.

To build the demo project, you will need Xamarin.iOS 7 (Indie Edition will do), Xcode 5 and iOS SDK7.

If you don’t know what SpriteKit is all about, you can check out the details on Apple’s developer pages (a developer account is required to access them): SpriteKit introduction

For all you eager-beavers who cannot wait to download the project before even knowing what it is all about, here’s the link:

https://github.com/Krumelur/KSBoingBall

Let’s have a look at what we’re going to build:

The demo's main scene showing the bouncing ball.

The demo’s main scene showing the bouncing ball.

Some folks may recognize the ball: it’s the good old Amiga Boing Ball from the 1980s! I found a perfect remake of its animation at the AmigaLog where the images can be downloaded. Very well done and perfect for our little tutorial.
Of course we want to tweak things a bit in order to show SpriteKit’s abilities, so my version has an option to let it snow – not just because it’s already close to that time of year, but mainly to demonstrate the use of particle effects:

Main scene with particles enabled.

Let it snow!

So let’s get started! After all you’re reading this because you want to know how to get things done! We will cover the following topics:

  • How to add a scene to a UIViewController
  • How to interact with a scene through standard UIKit elements
  • How to create the background by using SKShapeNode
  • How to create the animated ball and the shadow effect
  • How to set up the physics to make the ball bounce
  • How to play sound effects upon collisions
  • How to create and use a particle system built in Xcode’s particle editor

How to add a scene to a UIViewController

Apple made it really easy for us developers. SpriteKit is very well integrated into the rest of the API. It all starts with a classic UIViewController but we tell it that its view is an SKView:

public override void LoadView ()
{
  // Let our view be an SKView.
  var skView = new SKView ();
  this.View = skView;
}

Simple, isn’t it? But this won’t get us far. Let’s add some action to the SKView by overriding ViewWillLayoutSubviews():

public override void ViewWillLayoutSubviews ()
{
  base.ViewWillLayoutSubviews ();
  // Check if the scene is set up yet. It not, do it.
  if(this.SKView.Scene == null)
  {
    // Create our scene and bring it on the screen.
    var scene = new BoingScene (this.SKView.Bounds.Size);
    this.SKView.PresentScene (scene);
  }
}

Why ViewWillLayoutSubviews()? Because that’s a good place where we can be sure that the framework has adjusted all metrics like view size.

You can see that SKView is a subclass of UIView that comes with some extra spicing. One of the extra methods is PresentScene(). This is what really makes the scene appear inside the view of our view controller.

The scene itself is a subclass of SKScene, named BoingScene:

public class BoingScene : SKScene
{
  public BoingScene(SizeF size) : base(size)
  {
    // Contents skipped.
  }
}

The code above will give us an empty scene. In order to prove that there are things going on, we can enable some debug settings in our SKView:

this.SKView.ShowsFPS = true;
this.SKView.ShowsNodeCount = true;
this.SKView.ShowsDrawCount = true;

The properties above will bring a frame counter on the screen that shows the following information:

  • How many FPS we’re running at. We should always aim for 60 FPS on the device. The simulator is a different story. It will be slower.
  • How many nodes are on the screen. Think of a node as a sprite.
  • How many draw calls are made. If you take the particle system from the screenshot above, it’ll be one node but 140 draw calls have to be made to draw the snow flakes.

We have to keep a constant eye on these numbers. We should never have more things on the screen as necessary to keep our game a fluent experience.

Now that we’ve got an empty scene, we need to add some content to it. The content of a scene consists of SKNode instances. The most used subclass is SKSpriteNode, which we’ll discuss in detail. An SKSpriteNode is – well – a sprite. It is a container that holds information about the frames, the textures, the actions it performs and the physical behavior.

You’ll see how to set up all these things in the paragraphs where the background and the ball sprites are created.

How to interact with a scene through standard UIKit elements and device triggers

Before we dive deeply into populating our scene, let’s take a small detour that shows you an interesting aspect of how well Apple integrated SpriteKit into the UIKit environment.

How would we create a button on top of a scene? As we have already learned, SKScene is not a UIView. One option is of course to create custom touchable areas. This might be a valid approach but in some cases it is much better and simpler if we leverage the powers of UIKit. If you scroll a bit back up, you’ll find that we have a UIViewController in place and that hosts a view of type SKView. Et voila! There we have it. SKView is a subview of UIView. By placing UI elements onto that view, we can integrate UIKit elements into our scene.

All we need to add, is custom UI code inside the UIViewController:

public override void ViewDidLoad ()
{
  base.ViewDidLoad ();
  // Add a button to switch snowing on or off.
  var btnSnow = new UIButton (UIButtonType.RoundedRect) {
    Frame = new RectangleF (10, this.View.Bounds.Height - 30, 100, 30),
    AutoresizingMask = UIViewAutoresizing.FlexibleTopMargin | UIViewAutoresizing.FlexibleRightMargin
  };
  btnSnow.SetTitle ("Toggle snow", UIControlState.Normal);
  btnSnow.TouchUpInside += (sender, e) => {
    if(this.SKView.Scene != null)
    {
      var boingScene = this.SKView.Scene as BoingScene;
      boingScene.ToggleSnow();
    }
  };
this.View.Add (btnSnow); }

You see: no magic, just standard UIKit code. All we do here, is call a method inside our SKScene to toggle the snow particle effect on and off. The most difficult part here is the fact, that the RoundedRect button isn’t a rounded rect at all in iOS7! :-)

The full demo in addition contains support for shake gestures. Shaking the device kicks the ball. You can check out the code at Github.

How to create the background by using SKShapeNode

In order to create the graphical content of an SKSpriteNode we can either use a texture (which is nothing but a fancy word for image) or a color.

The sprites in this demo are all based on graphics/images/textures. However I want to demonstrate that this image data cannot only be loaded from existing PNGs or other file formats, but can also be created dynamically by code. iOS offers the powerful CoreGraphics (CG) API. It allows us to draw paths, perform shading, create gradients and much more. In SpriteKit we can make use of the powers of CoreGraphics by using SKShapeNode.

But first: why would we bother creating dynamic sprites? Let’s just always create all our graphical data as a PNGs and we’re done, right? Often this is the easiest solution and works quite well. But look at the screenshots at the beginning of the post. The background of the scene shows a grid. The distance between two grid lines is 32 units. Now have a look at the different iOS7 compatible devices: iPhone 4 inch Retina, iPhone 3.5 inch Retina, iPad 2 non Retina and  iPad 3+ Retina all have different screen resolutions. If we want our grid to always have a spacing of 32 units, we’d end up with four different grid images – all of them using up some space in your app bundle.

So let’s go that extra bit and create SKShapeNode:

static SKNode CreateBackgroundNode(SizeF size)
 {
   // Use a SKShapeNode. This can display any arbitrary CG content.
   var shapeNode = new SKShapeNode ();
   var path = new CGPath();
   shapeNode.StrokeColor = UIColor.FromRGB (112, 80, 160);
   float cellSize = 32f;

   // Draw vertical lines.
   float x = 0f;
   do
   {
     path.MoveToPoint (x, 0);
     path.AddLineToPoint (x, size.Height);
     x += cellSize;
   } while(x < size.Width);

   // Draw horizontal lines.
   float y = 0f;
   do
   {
     path.MoveToPoint (0, y);
     path.AddLineToPoint (size.Width, y);
     y += cellSize;
   } while(y < size.Height);

   shapeNode.Path = path;

   return shapeNode;
 }

We have created a device resolution independent sprite. The SKShapeNode can be used like any other SKNode and added to a scene.

How to create the animated ball and the shadow effect

The main actor in the demo is of course the animated Boing Ball. It’s not simply a sprite with a static texture, it shows us three different things:

  • How to create an animated sprite
  • How to create the illusion of an animated shadow
  • How to apply physics to a sprite

So let’s create our BoingBall instance of SKSpriteNode. The project contains 59 PNGs that represent the animation of the ball. To turn these PNGs into an animated object you create an SKSpriteNode instance without any texture data and then attach an action to it. The action is what is animating the sprite. Actions come in various flavors: they animate, play sounds, apply filters and so on.

Let’s have a look at the required steps.

First, we create an instance of SKSpriteNode. As all of the PNGs of the animation have the same size, it is convenient to initialize the instance with the first frame’s texture. This will setup the dimensions of the sprite for us:

var ballNode = SKSpriteNode.FromImageNamed ("./assets/BallFrames/0001.png");

Next, we’ll need to tell the ball that it is supposed to spin. So let’s first load the frame PNGs as SKTextures and store them in a list:

var ballFrameTextures = new List();
 for(int frameIndex = 1; frameIndex <= 59; frameIndex++)
 {
 var filename = string.Format ("./assets/BallFrames/{0:0000}.png", frameIndex);
 var ballFrameTexture = SKTexture.FromImageNamed (filename);
 ballFrameTextures.Add (ballFrameTexture);
 }

And here come the actions I was referring to. The sprite and the frames alone won’t do anything. We need to tell SpriteKit what to do with the frames. In our case we want to animate the textures:

var rotationAction = SKAction.AnimateWithTextures (ballFrameTextures.ToArray (), ROTATION_SPEED);

Problem with the code above: the animation would stop after the last frame. Obviously this is not desired. Fortunately, SKActions can be be chained or wrapped. In order to repeat the previous actions forever, wrap it:

var endlessRotationAction = SKAction.RepeatActionForever (rotationAction);

Note that any action can be repeated. This is not limited to an animation! We could also play a sound over and over again.

Finally, we need to tell the sprite what action to run:

ballNode.RunAction (endlessRotationAction);

That’s it. A spinning ball. Add the ball to the scene using SKScene.AddChild(), set its position and it will start rotating at 60 FPS.

But of course, we’re not done yet.

For practicing and to make the demo look nicer, we want to add a drop shadow-like effect of the ball. I chose to simply create another instance of the ball, using the same animation settings and add that as a child to the ball sprite:

var ballShadowNode = CreateBallNode (false);
ballShadowNode.Alpha = 0.8f;
ballShadowNode.Color = UIColor.Black;
ballShadowNode.ColorBlendFactor = 0.8f;
ballShadowNode.Position = new PointF (SHADOW_OFFSET, -SHADOW_OFFSET);
ballShadowNode.ZPosition = this.ballNode.ZPosition - 1;
// Add the shadow as a child to the ball.
ballNode.AddChild (ballShadowNode);

The code above adjusts the alpha value and blends the overall appearance to black by 80%. By specifying an offset, the shadow will always be moved together with its parent,

How to set up the physics to make the ball bounce

The ball is now sitting there in the scene, waiting for gravity to pull it downwards but nothing is happening. Poor ball. Let’s change that by adding some physics!
Our ball is what shape? Correct, a sphere, or in SpriteKit’s jargon: A body with Circle Of Radius, as a method:

SKPhysicsBody.BodyWithCircleOfRadius()

Each SKNode has a property PhysicsBody of type SKPhysicsBody. This describes the physical behavior of the node. So we tell our ball sprite that it is a sphere:

ballNode.PhysicsBody = SKPhysicsBody.BodyWithCircleOfRadius (ballNode.Frame.Width * 0.5f);

And of course, we want the ball to bounce like crazy. We have a couple of parameters to adjust its behavior. First one is dampening. If we want it to keep on bouncing, the dampening should be very low. How low? Well, I played around until I found a reasonable value. Don’t get scientific. You want it to look nice and not to be mathematical correct.

ballNode.PhysicsBody.LinearDamping = 0.001f;

Next come friction. We don’t want the ball to slow down a lot because of friction.

ballNode.PhysicsBody.Friction = 0.01f;

If the ball hits the borders of the screen it would start rotating. However this would break out nice shadow effect, because the shadow would rotate together with the ball. And after all, we have set up our own spinning animation. So let’s disable rotation.

ballNode.PhysicsBody.AllowsRotation = false;

And here’s another property that influences bounciness: restitution. The higher the restitution, the better the body will be reflected by obstacles.

ballNode.PhysicsBody.Restitution = 0.93f;

Mass influences the overall behavior of the physics simulation. If we change it, all other properties will be affected. Again, this is pure trial and error.

ballNode.PhysicsBody.Mass = 0.5f;

And we want the ball to start its movement to the right and a little bit up, so let’s give it some velocity:

ballNode.PhysicsBody.Velocity = new CGVector (200, -1f);

We have a bouncing ball! Yikes! But as we all know, balls tend to make a a deep “boing” sound if they hit a wall, right? They don’t? For this demo, let’s assume they do, otherwise the next part that deals with collisions and sounds would be empty.

How to play sound effects upon collisions

If we’d run the code with all the things we’ve added so far, the ball would just fly out off the scene and drift into virtual space. Not good. We need some walls around our screen, physical objects. We have already seen that our ball is a sphere (well, a projection of it, a 2D-sphere, or: a circle). This kind of body is called a volume-based body and that means that is gets affected by the force of gravity or other impulses. The counterpart are edge-based bodies. These are used as physical barriers. Just what we need! So let’s quickly add a rectangular barrier around our scene:

this.PhysicsBody = SKPhysicsBody.BodyWithEdgeLoopFromRect (this.Frame);

Hang on: this.PhysicsBody? We have just learned that every SKNode has a PhysicsBody property but isn't <em>this</em> an SKScene? Yep, but SKScene is a subclass of SKEffectNode and that is an SKNode. Lucky us! This means we can apply physical behavior to our scene!

We want to tell our app that this barrier is actually a wall. We'll see in a minute why this is important.

<span style="font-family: Menlo;">1
this.PhysicsBody.SetCategoryBitMask (CONTACT_BITS.Wall);

Note that the SetCategoryBitMask() method is an extension. I like to use enums instead of bit masks, so I created a bunch of helpers in a file called Extensions.cs.
And finally, we want to be informed, if something collides with something else. Either implement a delegate or use Xamarin's event driven version, which I prefer:

this.PhysicsWorld.DidBeginContact += this.HandleDidBeginContact;

The line above fires an event every time two bodies collide and that's exactly when we want the app to say "boing". Let's inspect the event handler:

void HandleDidBeginContact(object sender, EventArgs args)
var contact = sender as SKPhysicsContact;

The sender can be casted to an SKPhysicsContact. This carries the information about the collided objects. We can find out which objects collided and play a sound if the ball hit the wall:

if(contact.BodyA.IsOfCategory(CONTACT_BITS.Wall) && contact.BodyB.IsOfCategory(CONTACT_BITS.Ball))
 {
var soundAction = SKAction.PlaySoundFileNamed ("./assets/slam.mp3", true);
this.RunAction (soundAction);
 }

IsCategoryOf() is again an extension method and it is kind of the counterpart to SetCategoryBitMask(). Remember that we set the category bit mask of the wall to "Wall"? If you look at the project code, you'll see that the category of the ball is set to "Ball".
This allows us to identify objects easily. In a complex game we would have many different wall nodes with different textures. But all of them would be of category "Wall", so collision detection becomes easier.

In order to make the detection work, one extra step has to be taken. Not only need the participants have a category, the physics engine also needs to be told what collisions it should check at all. In order to have the ball checked against the wall, we need to set a category test bit mask:

ballNode.PhysicsBody.SetTestBitMask (CONTACT_BITS.Wall);

And we're done. The ball gets reflected by the walls and goes "boing".

How to create and use a particle system built in Xcode's particle editor

The demo contains some eye candy: particle effects. It is insanely simple to add particle effects in SpriteKit. The responsible class is SKEmitterNode. We can either go and create an emitter node manually in code, or we can use Xcode 5's particle editor:

Xcode 5 Particle Editor

Xcode 5 Particle Editor

The editor is an easy to use tool. Just create an empty SpriteKit project in Xcode and add a new file. From the file selection dialog, pick "SpriteKit Particle File". We can then choose from a couple of different predefined effects like snow or fire. For effects, the parameters can be adjusted. The result will be saved in an ".sks" file. This is nothing but a serialized version of an SKEmitterNode. So how to get that Xcode stuff into a Xamarin.iOS project? Just copy the SKS file to your project and include it as "content" or "resource".
Then simply use NSKeyedUnarchiver to deserialize things:

var particleSystem = NSKeyedUnarchiver.UnarchiveFile ("./assets/SnowParticles.sks") as SKEmitterNode;

I love it if things just work! Build something in Xcode and run it in .NET! :-)

We can then position the particle system on our scene:

particleSystem.Position = new PointF (this.Frame.Width * .5f, this.Frame.Height);

Each particle system is using an image. This image is also added to the Xcode project automatically. However, the path of the image does not match when copying the SKS over to the Xamarin project. To fix this, simply load the image manually and reassign it to the texture property:

particleSystem.ParticleTexture = SKTexture.FromImageNamed ("./assets/spark.png");

Add the particle system to the scene - and let it snow!

this.AddChild(particleSystem);

What's next?

Congratulations! You made it through the tutorial! Where to go from here? If you want to dive deeper into game development with SpriteKit, I recommend to check out Apple's "Adventure" project. It is written in ObjectiveC but you should be able to get a lot of inspiration from.

I also recommend to read through the SpriteKit introduction to see how it all works in detail. You will then also come across all the things we haven't covered here, like:

  • Special node types like SKCropNode
  • Sprite atlas
  • Scene transitions

Thanks for reading and happy coding!

About these ads