In this tutorial we will learn how to create a simple platformer game inside nunuStudio. We will explore how to control physics elements, import external resources and use keyboard and gamepad input.
This should take about 2 to 3 hours to complete, but its totally fine for new programmers to take longer, don't rush it take your time and try to understand every step of the tutorial.
For this tutorial some previous programming experience is recommended but i will try to explain as much as possible every code needed.
Lets start by creating a simple level for our game. All interactive objects in our game should be created using the physics objects. Phyics objects are invisible, to make them visible in the game we need to attach a visual representation to them.
Start by adding a physics cube, when selected the physics cube will be shown in the editor in green, but when we press start it will becore invisible.
Lets now add a visual representation to our cube, we achieve this by creating a new cube and attaching it as children of the physics cube. To add the new cube as children of the physics cube drag it on the object explorer on top of the physics cube.
After adding a visual representation to our cube we can see that it falls bellow the existing floor, to prevent this we need to add some additional physics elements to be used as floor for our cube.
We can move, resize and scale our cube physics object and the children objects will be afected as well.
The new physics elements need to be configured as static. Static physics element don't move around, they are only used as static walls. To configure the physics as static select it and set the "Type" in the object panel to Static.
Lets now add a camera to our project, cameras controll how the player will see the world, we will use a perspective camera, perspective cameras allow the player to perceive depth and see 3D objects, orthographic cameras are 2D cameras they don't allow the player to perceive distance.
After adding the camera to our scene we need to make it our default camera, to achieve this select the camera object and select the "Use camera" option in the object panel.
Now lets create a new material to make it easier to distinguish our player and the walls we created. To create a new material open the Material menu on the asset explorer and select Standard or Phong Material. To apply the new material to our objects drag it from the asset explorer to the object. I colored mine red but you can choose whatever color you want and even add a texture if you want to.
After adding the material to our cube that we will use as our player we are ready to move on to coding our character behavior. If everything went as expected you should have something similar to this on your computer.
If you are having some trouble completing this part of the tutorial you can download the project fileou can download the project file or open it on the Web Editor.
To move our player around we need to create a new script that will be used to controll the player using the keyboard keys. Scripts are objects, so they have a position, rotation, scale, etc, and can have objects attached as children.
To open the code editor double click the script object on the object explorer or right click on top of it and select "Script editor", a new tab will be open with the code editor.
In this tutorial we will use a single script object to controll all objects in the scene, but is possible to have multiple scripts.
Lets start by getting the player object in our script, to get the player object we can use its name, lets rename our physics cube (that will be used as our player) to player. After that we can use the code bellow to get the player object and store it in a variable.
var player;
function initialize()
{
player = scene.getObjectByName("player");
}
To get keyboard input we have the Keyboard object, that offers keyPressed, keyJustPressed and keyJustRelease methods that allow us to detect when the user has pressed and released a specific key in the keyboard. For this guide we will use the WASD keys.
To move the player around we will manipulate its body, physics bodies have position, velocity, acceleration and force attributes. We change manipulate those to make the player move around, for better control of the player movement we will manipulate the velocity attribute.
To code bellow detects when the user has pressed the A, W and D key and sets the player velocity accordingly. But after a bit of time we can the see that the player falls of our platform, that because our body is a cube and ends up rotating a bit on its corners changing its direction. To use other keys in the Keyboard check the Keyboard API documentation.
function update()
{
if(Keyboard.keyPressed(Keyboard.A))
{
player.body.velocity.x = -5;
}
if(Keyboard.keyPressed(Keyboard.D))
{
player.body.velocity.x = 5;
}
if(Keyboard.keyJustPressed(Keyboard.W))
{
player.body.velocity.y = 10;
}
}
To avoid our player falling down our level we need to block its movement on the Z axis we can achieve this by forcing its position and/or velocity to 0 on that axis.
function update()
{
...
player.body.position.z = 0;
}
Now there is still a problem left to solve, everytime the player keeps the W key pressed the player keeps going up. We can only let our player jump when he is touching the ground. To achieve this we need to check if our player is on the ground and create a new variable that will be set true when the player is touching the ground.
To check if the player has touched the ground, we will create a new callback for the cannon.js world postStep event, this callback is called every time after the physics engine updates the object position, we can get a list of contact points and check if those contact points belong to our player if they do that means our player is colliding with something. Now we only need to check the orientation of the contact point, for that we will check the dot product of the contact point direction and the surface normal (we will assume a normal pointing up) is its the floor that should give us a result near 0, we will assume that any value bellow 0.1 is the floor and we will set a canJump flag to true if so.
var player, world;
function initialize()
{
world = scene.world;
player = scene.getObjectByName("player");
player.canJump = false;
var up = new Vector3(0, 1, 0);
var temp = new Vector3(0, 0, 0);
world.addEventListener("postStep", function(e)
{
if(world.contacts.length > 0)
{
for(var i = 0; i < world.contacts.length; i++)
{
//Get contact points
var contact = world.contacts[i];
//Check if any of the physics elements in the contact is our player
if(contact.bi.id === player.body.id || contact.bj.id === player.body.id)
{
//If our player is the first element negate direction and store in temp
if(contact.bi.id === player.body.id)
{
contact.ni.negate(temp);
}
//Else store direction in temp
else
{
contact.ni.copy(temp);
}
//If dot product if near 0 player is touching the floor and can jump again
if(temp.dot(up) > 0.1)
{
player.canJump = true;
}
}
}
}
});
}
function update()
{
...
if(player.canJump && Keyboard.keyJustPressed(Keyboard.W))
{
player.body.velocity.y = 10;
player.canJump = false;
}
...
}
Now we are able to control our character and have it moving properly around our level, but our camera is still static, to make it follow our character we can simply equal its position in the X and Y coordinates to the player position.
var player, world, camera;
function initialize()
{
camera = scene.getObjectByName("camera");
...
}
function update()
{
...
camera.position.x = player.body.position.x;
camera.position.y = player.body.position.y + 2;
}
If you are having some trouble completing this part of the tutorial you can download the project fileou can download the project file or open it on the Web Editor.
We got our player moving but we still cant die, and dont have any obstacles to avoid or enemies to kill. Dont worry we will take care of that now.
Lets start by making our level a bit bigger, add more platforms some places where we can fall off, the fastest way to do this is by duplicating our already existing floor and after rotating, scaling and moving it around, be carefull we want to apply these changes to the physics object not to the visual representation.
Now lets create some obstacles, first lets start with static obstacles and after we can make them move. I will add obstacles in red and change my player color to green, obstacles will also be static physics objects.
After adding our enemies lets create a new group and add all enemies to that group, so that we can distinguish enemies from normal walls and other physics objects. In your object explorer you new group should look something like this.
Now lets get back to our script and add code to detect collision with enemies, lets start by adding an attribute "isEnemy" to all our enemies that we added to the group we just created. We also need to store our player spawn position so that when we die we get back to that position.
function initialize()
{
...
player.spawn = player.position.clone();
var enemies = scene.getObjectByName("enemies");
for(var i = 0; i < enemies.children.length; i++)
{
enemies.children[i].body.isEnemy = true;
}
...
}
After this we need to add the enemy collision check to the callback we created ealier in our physics world, we just need to check is there is a isEnemy true value and if so reset our character position and speed.
function initialize()
{
...
world.addEventListener("postStep", function(e)
{
if(world.contacts.length > 0)
{
for(var i = 0; i < world.contacts.length; i++)
{
//Get contact points
var contact = world.contacts[i];
//Check if any of the physics elements in the contact is our player
if(contact.bi.id === player.body.id || contact.bj.id === player.body.id)
{
//Check if is an enemy
if(contact.bi.isEnemy || contact.bj.isEnemy)
{
player.body.position.set(player.spawn.x, player.spawn.y, player.spawn.z);
player.body.velocity.set(0, 0, 0);
}
...
}
Before moving on to moving enemies lets add a new static enemy under the floor so that the player dies when it falls off the level, this enemy doesn't need a visual representation.
Now lets make our enemies move, lets choose on of our enemies, create a new script and attach that script as child of the enemy we choose. We can make our enemies move in a couple of different ways.
Bellow is the full code used for this script, it moves the object around from one position to another in looop, it can be ajusted to be easily ajusted and reused to move other objects (for example to create moving platforms).
var velocity = 0.05;
var min = -5;
var max = 5;
var direction = 1;
var enemy;
function initialize()
{
//Get enemy
enemy = self.parent;
//Store a copy of original position
enemy.spawn = enemy.position.clone();
}
function update()
{
//Position direction
if(direction > 0)
{
//Check if position reached max
if(enemy.body.position.x < enemy.spawn.x + max)
{
enemy.body.position.x += velocity;
}
//Invert direction
else
{
direction = -1;
}
}
//Negative direction
else
{
//Check if position reached min
if(enemy.body.position.x > enemy.spawn.x + min)
{
enemy.body.position.x -= velocity;
}
//Invert direction
else
{
direction = 1;
}
}
}
You should have now a moving enemy in your game, duplicate the created script and attach it to other object to make them move as well, try to create a moving platform like the ones in super mario games.
If you are having some trouble completing this part of the tutorial you can download the project fileou can download the project file or open it on the Web Editor.
To add support for gamepad input we can create a Gamepad object in our script, the gamepad object allows us to get input from an USB gamepad, different gamepads might have different button mapping you can use this website to check you gamepad button mapping. To check the name of other buttons in the Gamepad check the Gamepad API documentation.
var gamepad;
function initialize()
{
gamepad = new Gamepad();
...
}
function update()
{
gamepad.update();
if(Keyboard.keyPressed(Keyboard.A) || gamepad.buttonPressed(Gamepad.LEFT))
...
}
Now we already have all basic blocks to make a complete platformer level lets take them and build complete level with actual game assets and tweak gameplay a little bit. To make it easier to overview our level we can use the 2D editor mode (can be toggled in the top right corner of the scene explorer).
Were almost done now its the time to add textures and decoration to our level, this is a good time to experiment to use some of the decoration objects like Particles, LensFlare, etc. I haven't done much regarding decoration simply added a couple of materials and colored everything up, added a LensFlare effect and some particles floating around the level.
Since the player movement was also a little slow i tweaked it a bit by adding the option to run using the keyboard shift key and tweaking the movement to have an acceleration curve instead of instant move.
At the beginning of this tutorial i was planning to change the cube for a ball or lock its rotation but since i find it kinda of funny to see the cube tumbling around so i will leave it like this.
function update()
{
...
var velocity = 6;
var acceleration = 0.6;
if(Keyboard.keyPressed(Keyboard.SHIFT))
{
velocity = 8;
acceleration = 1;
}
if(Keyboard.keyPressed(Keyboard.A) || gamepad.buttonPressed(Gamepad.LEFT))
{
if(player.body.velocity.x > -velocity)
{
player.body.velocity.x -= acceleration;
}
}
if(Keyboard.keyPressed(Keyboard.D) || gamepad.buttonPressed(Gamepad.RIGHT))
{
if(player.body.velocity.x < velocity)
{
player.body.velocity.x += acceleration;
}
}
...
}
If you were able to make it this far congratulations, you have created an awseome platformer game. This should cover all the basic aspects for platformer games. If you have any doubt about any of the steps in the tutorial, feel free to ask me about it.
You can try the final result of this tutorial bellow (wasd keys to move), you can also download the project file or open it on the Web Editor.
If you wish to make the controls feel more like those in a classic platformer without the cube tumbling around the level you can add these little tweaks
First, we need to change the player physics object to a sphere instead of a cube. This way our player will roll easily and won't get stuck to the walls. I chose to make the scale of the sphere (0.5, 0.5 , 0.5)
this way it fits our (1.0, 1.0, 1.0)
cube.
As you can see in red we have also changed the angular damping value, this makes it so our ball won't roll very far after you released the button. You can tweak this value to find the one that suits you the most. I found that 0.999
works pretty good.
As you can see there is also a blue cube named 'playermesh', this mesh will represent our player. This can be a custom model if you wish, but make sure to change the sphere scale to fit your model.
We're almost done now, all we have to do is to teleport our player mesh to the physics object every frame, we can do this by copying the physics position to our player mesh in our update function.
var playermesh;
function initialize()
{
...
playermesh = scene.getObjectByName("playermesh");
...
};
function update()
{
...
//Copy the position of our physics object to our player mesh
playermesh.position.copy(player.body.position);
...
}
If you followed all the steps you should now have smoother controls on your player. You can of course experiment with it as much as you want.
If you are having some trouble completing this part of the tutorial you can download the project fileou can download the project file or open it on the Web Editor.