Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

11 minutes readgamedev

SpriteKit Wiki

This article is part of my personal wiki where I write personal notes while I am learning new technologies. You are welcome to use it for your own learning!

SpriteKit is the latest 2D gaming framework targeting iOS and OS X released with the announce of iOS 7. It provides all the necessary tools to build 2D games: graphics rendering, animations infrastructure, hardware acceleration, physics engine, particle generator and sound playback.

Introduction

As with any gaming framework, the fundamentals of the development of a game consist of managing the game loop, an infinite loop that continuously listens for user input, updates the state of the game and draws the different pieces in the screen.

The game loop in SpriteKit follows these steps:

  1. Update the status of all entities within the game scene
  2. Evaluate Actions done by every entity
  3. Simulate Physics
  4. Draw/Render the game
  5. Back to 1

In each of these steps the entities (SKNodes) that are part of a scene (SKScene) will be updated, animated and drawn in the screen (SKView). In the next sections, I will briefly describe how the different parts of SpriteKit. (I sound like a robot when I am writing this wiki articles XDDD)

Building The Scene

A scene (SKScene) is what we could call as the basic unit of playability within a game:

  • It is used to provide content to be rendered by an SKView
  • It is composed by a tree of nodes (SKNodes) that represent the different entities within the scene (the player, monsters, score, etc).
  • When presented by a view, it is in charge of handling the game loop for all it subnodes. In summary:

It Provides a Coordinate System To Its Children

When a node is placed within a node tree, its position property places it within the coordinate system provided by its parent. Since the Scene works as the root node, all its children will use the coordinate system defined by the scene.

SpriteKit uses the standard conventions used in gaming for coordinate systems and its values are measured in units:

  • Positive X to the right, positive Y to the top,
  • An angle of 0 radians is specified by the x axis and positive angles are determined in the counterclockwise direction.

Creating a Scene

The first time a scene is initialized, its size property is configured via its constructor. This size represents the size of the visible portion of the scene in points.

If the scene doesn’t match the view, you can specify how the scene will be scaled to match the view.

Nodes in SpriteKit

Nodes represent the most basic building components in SpriteKit, basically, everything within SpriteKit is node, even the scenes are nodes.

Notes can draw content, but that is not required of them. SKSpriteNode, for instance, will draw a sprite, but the SKNode will not. You can identify whether a node will draw content by reading its frame property. This property represents the visible area of the parent’s coordinate system that the node draws into, and will be different than zero for drawing nodes.

If a node has descendants that draw content, it is possible for a node’s subtree to provide content even thought that the parent doesn’t. You can access the calculated frame by calling the method calculateAccumulatedFrame.

Physics in SpriteKit

Physics in SpriteKit are performed by adding physic bodies to a scene. A physics body is a simulated physical object connected to a node within the scene that adds physical properties to this node such as mass, density or velocity. These characteristics are going to define how a body is affected by the physical world: how it moves, how it responds to collisions, etc.

Each time a scene computes a new frame of animation, it simulates the effects of forces and collisions on physical bodies conected to the node tree. It computes a final position, orientation and velocity for each physics body and the scene updates the position and rotation of each corresponding node.

Physic Bodies

Physic bodies are represented by SKPhysicsBody and assigned to nodes via the physicsBody property. There are three kinds of bodies:

  • A dynamic volume simulates a physical object with volume and mass that can be affected by forces and collisions in the system. Use dynamic volumes when you need to represent items in the scene that move around and collide with each other.
  • A static volume is similar to a dynamic volume but its velocity is ignored and it is unaffected by forces or collisions (other bodies can still collide with a static volume because indeed it has volume). Use static volumes for elements within the scene that take up space but do not move, such as walls.

Note that you can this dynamicity can be enabled/disabled for the same physics body.

  • An edge is a static volume-less body. They are never moved by the simulation and their mass doesn’t matter. They are used to represent the negative space within a scene (transparencies) or an uncrossable, invisible thin boundary, e.g. the boundaries of your scene. The main difference between an edge and a volume, is that the former allows movement inside its own boundaries. Also if edges are moved, they don’t interact with other edges, only with volumes.

SpriteKit provides some basic standard shapes (circles, squares, etc) as well as arbitrary shapes based on paths.

Using a Physics Shape That Matches A Sprite

Since within a game, we care a lot about collisions, it is very important that a physic body shape matches that of the sprite it is associated with.

The selection of the physics body shape is a tradeoff between precision and performance, where more complex shapes require moer work to be simulated. It is recommended:

  • For volumes:
    • use a circle as it is the most efficient shape
    • a path-based polygon is the least efficient
  • For edges,:
    • use lines and rectangles
    • edge loops and chains are the least efficient

Note that edges are naturally more expensive to compute than volumes, since they have to account for interactions that can occur both in the inside and in the outside of the edge.

Creating Physical Bodies

You can create a physical body by using the methods provided by the SKPhysicsBody class.

For instance, you can create an edge loop around your scene by using this snippet:

- (void) createSceneContents
{
    // ...
    self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}

And a circular volume for a sprite:

SKSpriteNode *sprite = [SKSpriteNode spriteWithImage:@"conanhead.png"];
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;

Configuring Physical Bodies

The SKPhysicsBody class defines the following properties that determine how the physics body is simulated.

Some properties determine how it reacts to forces or collisions in the screen:

  • The mass property determines how forces affect the body, as well as how much momemtum the body has when involved in a collision
  • The friction property determines the roughness of the body surface. It is used to calculate the frictional force that a body applies to other bodies moving along its surface.
  • The linearDamping and angularDamping properties are used to calculate friction on the body as it moves through the world (through air, water, etc).
  • The restitution property determines how much energy a body maintains during a collision (a.k.a. bounciness).

Other properties are used to determine how the simulation is performed on the body itself:

  • The dynamic property determines whether the body is simulated by the physics subsystem.
  • The affectedByGravity property determines whether the simulation exerts a gravitational force on the body.
  • The allowsRotation property determine whether forces can impart angular velocity on the body.

It is recommended that you set the mass on every volume in your scene so that it reacts properly to forces applied to it. A body mass, area and density are interrelated. Whenever you change the mass or density the other value is recalculated (mass = density x area).

Configuring the Physics World

All bodies within a scene are part of the physics world, which is represented by the SKPhysicsWorld attached to the scene. It defines two important characteristics:

  • The gravity that applies an acceleration to volumes
  • The speed or rate at which the simulation runs

Making Bodies Move

By default, only gravity is applied to volumes in the scene. In most cases, you will need to change the speed of the bodies by setting the velocity and angularVelocity properties. As with many other properties, you normally se these values when the body is first created and let the physics simulation do the necessary adjustments.

missile.physicsBody.velocity = self.physicsBody.velocity;
[missile.physicsBody applyImpulse:CGVectorMake(
  missileLaunchImpulse*cosf(shipDirection),
  missileLaunchImpulse*sinf(shipDirection))];

It is also common to adjust the velocity of an object based on the forces applied to it. The default collection of forces applied to a body include:

  • The gravitational force applied by the world
  • The damping forces
  • The frictinal forces

Additionally, you can apply your own forces and impulses:

  • A force is applied for a length of time based on the amount of simulation time that passes between when you apply the force and when the next frame of simulation is processed. They are usually continuous effects.
  • An impulse makes an instantaneous change to the body’s velocity that is independent of the amount of simulation time that has passed. They are usually immediate effects.

You can apply a force/impulse to a body in these ways:

  • As a linear force that only affects its linear velocity.
  • As a angular force that only affects its angular velocity
  • As a force applied to a point on the body. The physics engine calculates the changes in velocity itself.

Below you can see an example on how to simulate a spaceship thrust:

static const CGFloat thrust = 0.12;

CGFloat shipDirection = [self shipDirection];
CGVector thrustVector = CGVectorMake(thrust*cosf(shipDirection),
                                   thrust*sinf(shipDirection));
[self.physicsBody applyForce:thrustVector];

// or a lateral thrust
[self.physicsBody applyTorque:thrust];

Collisions and Contacts

SpriteKit supports two kinds of interactions when two bodies collide:

  • A contact is used to know that two bodies are touching each other. You use contacts when you need to perform gameplay changes when collisions occur.
  • A collision is used to prevent objects from interpenetrating each other. It is automatically calculated by SpriteKit.

Since collisions are expensive, Sprite Kit uses two mechanisms to limit the number of interactions in each frame:

  • Edge-based bodies never interact with other edges.
  • Every body is categorized. Categories are defined by your app (each scene can have up to 32). when you configure a body, you dfine which category it belongs to and which categories of bodies it wants to interact with. Contacts and collisions are specified separatedly.

Relationships between categories don’t need be symmetrical, in fact, it is probably more performant to have one way relationships.

When you have decided on the categories of you game, you need to implement them, together with the interactions using 32-bit masks. Whenever a potential interaction occurs, the category mask of each body is tested against the contact and collision masks of the other body. Sprite Kit performs these test by performing and AND operation between the two masks.

// define categories
static const uint32_t missileCategory     =  0x1 << 0;
static const uint32_t shipCategory        =  0x1 << 1;

// define contact and collision masks
SKSpriteNode *ship = [SKSpriteNode spriteNodeWithImageNamed:@"spaceship.png"];
ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.size];
ship.physicsBody.categoryBitMask = shipCategory;
ship.physicsBody.collisionBitMask = shipCategory | asteroidCategory;
ship.physicsBody.contactTestBitMask = shipCategory | asteroidCategory;

// assign a contact delegate to the physics world
self.physicsWorld.contactDelegate = self;

...
// and provide and implementation
- (void)didBeginContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody *firstBody, *secondBody;

    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
    {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    }
    else
    {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }
    if ((firstBody.categoryBitMask & missileCategory) != 0)
    {
        [self attack: secondBody.node withMissile:firstBody.node];
    }
}
...

Note how:

  • The contacts delegate is passed an SKPhysicsContact object that declares which bodies were involved in the collision.
  • The bodies in the contact can appear in any order.
  • When your game needs to work with contacts, you need to determine the outcome based on both bodies in the collision. Consider using the Double Dispatch Pattern.
  • The node property can access the node that a body is attached to. You can use it to access information about the colliding nodes.

Finally, you can specify high precision collision for small and fast moving objects by using the usePreciseCollisionDecision property. Bear in mind that this technique will be expensive.

References


Jaime González García

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.Jaime González García