Tuesday, December 31, 2019

Tutorial: Making a Platform Game Like Mario With SpriteKit (Part 1 - The Basics)

Another year is gone. I remember when I was a little boy, I'd be hyped when people talk about the "FUTURE". That time, circa 1980s, the "FUTURE" means anywhere in the year 2000+. The year 2020 is somewhat special though due to its nice number arrangement. 2020 itself sounds futuristic. But lo and behold, we are almost there now! 40 years have passed just like that. I have to admit when you reach 40, time seems to go by much more faster. It seems you have so much to do, but there isn't just enough time to do it. Get busy living, get busy dying.

20MHz Processor LMAO. 8 Grand 




In terms of tech, no doubt we have progressed so much. Imagine back then in 1980 a computer with very low specs are so expensive. In terms of computing power, and computing tools like Machine Learning, AI, OCR, and so on we have made quite the leap. Imagine what engineers and scientists could achieve in another 40 years? Most probably I'd be dead by then :P

Done with the intro. So, the first ever game I created from scratch with SpriteKit was the Flappy Bird clone called "Idiot Bird". It was quite a challenge, because I was new to SpriteKit, and Idiot Bird uses physics in order for the bird to fly. And who could forget the way the bird smash its face flat to the ground? Haha...

Believe it or not, my first game I developed in iOS was using 100% UIKit using UIImageView as sprites. Here take a look:

I used Paint Shop Pro to create all the (nasty nasty raster) graphics and place them using UIImageViews. It worked quite OK, because it was a turned based game. And animation is smooth just by using a normal UIView animation. This is a monumental evidence that UIKit is such a great framework. Windows framework fails miserably if you try to animate their buttons. There is no fluidity.

Anyway, lets get to topic: SpriteKit's Physics. You can create a Spritekit game without any physics setup. But the kind of games you can make is therefore limited. Games that won't use physics are like Chess game, or card game, or even simple shooting game (top view typically). But if you wish to make a game that feels real, Physics is a must.

For this purpose, we will create a simple scene. A character bouncing on a platform. The first thing to do is to add 2 sprites to a scene. With my amazing graphics skills (lol), here is what I created:





You may download the image and resize it for other scales. Add both sprites to the scene in didMoveToView and position the character slightly above the platform.


- (void)didMoveToView:(SKView *)view {
    
    //create bunny
    SKSpriteNode *bunny = [SKSpriteNode spriteNodeWithImageNamed:@"bunny"];
    bunny.name = @"bunny";
    bunny.position = CGPointMake(self.size.width/2.0, self.size.height/2.0+100);
    [self addChild:bunny];

    //create platform
    SKSpriteNode *platform = [SKSpriteNode spriteNodeWithImageNamed:@"p_grass"];
    platform.name = @"p_grass";
    platform.position = CGPointMake(self.size.width/2.0, self.size.height/2.0);
    [self addChild:platform];
}

Run it, and you will see both of them in the scene.

But... nothing happens. Boring right? So how do we tell the scene that we want to apply gravity to the sprites in the scene so that the bunny would fall? Easy. Enter the codes below:

    // scene physics initialization
    self.physicsWorld.gravity = CGVectorMake(0, -5.0f);
    
    //create bunny
    SKSpriteNode *bunny = [SKSpriteNode spriteNodeWithImageNamed:@"bunny"];
    bunny.name = @"bunny";
    bunny.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bunny.size]; 
    bunny.position = CGPointMake(self.size.width/2.0, self.size.height/2.0+100);
    [self addChild:bunny];

    SKSpriteNode *platform = [SKSpriteNode spriteNodeWithImageNamed:@"p_grass"];
    platform.name = @"p_grass";
    platform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:platform.size];
    platform.position = CGPointMake(self.size.width/2.0, self.size.height/2.0);
    [self addChild:platform];

What we did was to add one liner scene PhysicsWorld initialization specifying the gravity to a vector. What is vector? If you paid attention in your algebra class you'd know it :P. In simple terms, vector is simply a collection of values that defines direction and magnitude. In our case, SpriteKit is a 2 dimensional space. So it has x and y vector components in a normal Cartesian axes. So to specify a downward gravity, use (0, negative value). Here we use (0,-5) for a fairly moderate pull force to the bottom. If you want the scene environment to pull everything to the top, then you use (0,9), if you want to pull everything to the left, then you use (-8, 0), and if you want to pull everything to bottom left, you can use (-8, -7).

After specifying the scene physics, we also need to specify which of our sprites will be affected by it. So we have to add the physicsBody definition to our spritenode.  There are multiple types of physicsBody, and here we use Rectangle type. There are types of:

  • RectangleWithSize (specify CGSize)
  • CircleWithRadius (specify float radius)
  • PolygonFromPath (specify path of custom shape)
Most people simply use rectangle or circle for physicsbody, unless you are doing something complicated, polygon physicsbody should not be used. Anyway lets run our game, what's going on?



LOL! Both of them fall down... but duh.. that is exactly what the code says. And that means the physics is now in action. Ok lets take a break to award ourselves with this accomplishment. XD

Obviously we do not want the platform to fall, but yet we still need it to be affected by physics in order to interact with the bunny. If we remove the physicsbody from the platform spritenode, the bunny will ignore the platform like it's not even there. Try it yourself. Here is what you should get:


So to tell the platform not to be affected by gravity, but yet be able to interact with the bunny, we specify a boolean to a property affectedByGravity

platform.physicsBody.affectedByGravity = NO;

Add the line under the physicsBody definition of the platform spritenode and you should end up with the following:

Woah? We still FAIL. But there are progress. As we can see, the bunny now interacts with the platform. The platform no longer falls down by itself but it is being forced down by the weight of the bunny. How do we solve this? Enter the dynamic property of physicsBody.

platform.physicsBody.dynamic = NO;

This tells the physics engine that the platform will ignore any force applied to it. And you will end up with:
As you can see, now the bunny bounces a bit as he lands. This is physics at work. We did not write ANY codes for this bounces. Movements looks more natural and nice and human can relate to it and this is an important factor to capture human's interest in your game.

The final code is:

    self.physicsWorld.gravity = CGVectorMake(0, -3.0f);
    
    //create bunny
    SKSpriteNode *bunny = [SKSpriteNode spriteNodeWithImageNamed:@"bunny"];
    bunny.name = @"bunny";
    bunny.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bunny.size];
    bunny.position = CGPointMake(self.size.width/2.0, self.size.height/2.0+200);
    [self addChild:bunny];

    SKSpriteNode *platform = [SKSpriteNode spriteNodeWithImageNamed:@"p_grass"];
    platform.name = @"p_grass";
    platform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:platform.size];
    platform.physicsBody.affectedByGravity = NO;
    platform.physicsBody.dynamic = NO;
    platform.position = CGPointMake(self.size.width/2.0, self.size.height/2.0);
    [self addChild:platform];

As you can see, I reduced the gravity a bit because I wanted to be able to capture the animation and show you what's going on. I also moved the bunny further up by 200 points. As you can see, now the bunny bounces a bit as he lands. This is physics at work. We did not write ANY codes for this bounces. Movements looks more natural and nice and human can relate to it and this is an important factor to capture human's interest in your game.

Now we got the bunny to land and bounce on the platform, we are that much closer to a platform game like Mario. The next thing to do is add more platforms and code the controls for the bunny. How do we make him jump? Enter the code below:


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    SKSpriteNode *bunny = (SKSpriteNode*)[self childNodeWithName:@"bunny"];
    [bunny.physicsBody applyImpulse:CGVectorMake(0, 50)];
}

touchesBegan is a typical delegate function to detect finger touches on screen. We use this to tell our bunny to jump. In order to jump, an upward momentary force (impulse) is applied once every time we touch the screen. And the results:


Success! Now we have a bunny jumping on a platform. There is another way to make the bunny jump, such as setting velocity of bunny to positive value on the Y axis. And you can fine tune the gravity, the jump speed, bounciness, all by tweaking the gravity, the impulse value, and also a few more bunny's physicBody's properties:

  • restitution
  • mass
  • friction
  • density
Experiment with the values and see what you could come up with. That's all for Part 1 of this tutorial. I will delve in more in the next part so that at least we got a playable simple game collecting coins or carrots :P

Cheers and have a nice day!

No comments:

Post a Comment