This was written for Unity 2020.2x. Versions older than Unity 2020.2 will take more steps to import the sprites.
Step 1: Create a 2D Unity Project
Open Unity Hub and click on the NEW button.
Select 2D from the Templates, name your project file, and choose where you want to save your project folder.
Click CREATE and wait...for...ever...
Once your Unity project loads, it will look like this:
Click and drag the Game tab to the right so that it snaps and allows you to see both the Scene and Game windows at the same time.
The Game window is what the player would see.
Step 2: Add the Assets
Download these assets:
Extract the files and open them in your File Explorer (outside of Unity).
In the folder, Images, you should have these:
In Unity, in the Project window, click the Assets folder to open it.
Right-click and create a Sprites folder.
Enter the Sprites folder.
Click and drag your Mario assets/images into the folder. This will create copies of the images in your project folder.
Step 3: Create the Scene
Click on the Scenes folder in your Project window.
Right-click and create a new scene. I'll call mine, Mario.
Double-click it to open it.
2D objects have a Main Camera object by default.
Step 4: Add Sprites to the Scene
Right-click in the Hierarchy window to add a 2D Object called a "sprite." I'm using a square sprite to create a platform/ground.
I renamed mine "ground."
It starts off as a white square, let's use the corner vectors to make it larger. Click and drag to reposition it.
Click the color property in the Inspector window in the Sprite Renderer component to change the color.
Click and drag your "stand" image from the Project window (Assets > Sprites) into the Scene window. Place him a little above the "ground" so we can watch him fall.
Rename the sprite to Mario in the Hierarchy window and resize the sprite to your liking.
Holding SHIFT as you drag will keep him proportional as you resize him.
Step 5: Make the Sprites Have Borders and Gravity
To make the Mario asset fall, we need to add a Rigidbody2D component and a BoxCollider2D component.
Click on your Mario asset in the Hierarchy or Scene window.
In your Inspector window, click the Add Component button and search for and select those two components.
Once added, the Inspector window will look like this:
The Rigidbody 2D component adds gravity while the Box Collider 2D adds a boundary. By default, the boundary is the edges of the sprite image.
If you play it now, Mario falls right through the ground!
Click on the Ground object in the Hierarchy or Scene window and add a Box Collider 2D component to it so the Mario sprite stops at the ground.
Now, Mario lands correctly.
Step 6: Creating the Script
In the Project window, in the Assets folder, create a folder named "Scripts" and open it.
Right-click and create a new C# Script.
I will name my script, MarioMove.cs.
After naming it, double-click it to open it in Visual Studio.
Step 7: Organizing Your Script
Let's start with thinking about what we want Mario to do.
Walk/Run
Jump
So let's comment out some sections for our variables.
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassMarioMove:MonoBehaviour{ // Walk // Jump // Start is called before the first frame updatevoidStart() { } // Update is called once per framevoidUpdate() { }}
Let's also create some blank functions to control what happens when Mario does these things.
publicvoidWalk(){}publicvoidJump(){}
Put these functions after the closing curly bracket } for Update(), but before the closing curly bracket } for the class.
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassMarioMove:MonoBehaviour{ // Walk // Jump // Start is called before the first frame updatevoidStart() { } // Update is called once per framevoidUpdate() { }publicvoidWalk() { }publicvoidJump() { }}
Step 8: Create and Assign Default Variables
Our default variables are what we start with, default to, have available throughout the game, etc.
For this script, I will have variables for the Mario game object (so I can move him around) and for the Sprite image it displays - in this case, the standing image.
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassMarioMove:MonoBehaviour{publicGameObject mario;publicSprite standingImage; // Walk // Jump // Start is called before the first frame updatevoidStart() { } // Update is called once per framevoidUpdate() { }publicvoidWalk() { }publicvoidJump() { }}
Save your script and return to Unity.
Click and drag the MarioMove.cs script from your Project folder to the Mario game object in the Hierarchy or Scene windows or add the script through the Add Component button in the Inspector window.
It will look like this when it's added:
Click and drag your Mario object from the Hierarchy window or click on the "target" icon on the right of Mario in the component to select from a list.
For the Standing Image, click and drag from the Project window your "stand" image from the "Sprites" folder OR use the "target" icon on the right of Standing Image to select from a list.
Step 9: Moving Mario With Arrow Keys
Go back to your script. Let's make Mario move to the right when the right arrow is pressed and left when the left arrow is pressed.
Under our Walk variables area, let's create and set a moveSpeed variable:
// Walkpublicfloat moveSpeed =2;
In Update(), we can now check for keystrokes and run code based on them:
if (Input.GetKey(KeyCode.RightArrow)){mario.transform.position+=Vector3.right* moveSpeed *Time.deltaTime;}if (Input.GetKey(KeyCode.LeftArrow)){mario.transform.position+=Vector3.left* moveSpeed *Time.deltaTime;}
Here, we are using Input.GetKey() so that the key inside the parentheses will be checked and be "true" when pressed on every frame it is pressed - great for smooth movement.
Vector3.right and Vector3.left are both shortcuts for 1 or -1 on the x-axis respectively. This helps keep us from having to write out all three axes.
Time.deltaTime helps normalize the speed so that it is consistent at different computer speeds.
Full code so far:
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassMarioMove:MonoBehaviour{publicGameObject mario;publicSprite standingImage; // Walkpublicfloat moveSpeed =2; // Jump // Start is called before the first frame updatevoidStart() { } // Update is called once per framevoidUpdate() {if (Input.GetKey(KeyCode.RightArrow)) {mario.transform.position+=Vector3.right* moveSpeed *Time.deltaTime; }if (Input.GetKey(KeyCode.LeftArrow)) {mario.transform.position+=Vector3.left* moveSpeed *Time.deltaTime; } }publicvoidWalk() { }publicvoidJump() { }}
Save your script and test in Unity!
You can adjust the Move Speed right in the Inspector window!
Be careful: any changes while in Play Mode will revert back to what it was prior to playing.
Step 10: Making Mario "Walk"
We can use the countdown technique we used when looking at the looping nature of Update() to make "lights" flash or have a duration to create a "walk cycle" that goes through a set of still images to create the visual of movement or animation. We'll complete the walk cycle in the next step.
We can start with changing to an image when Mario is walking.
First, we need to have something tell the computer WHEN and IF Mario is walking. Let's do that with a boolean type variable named isWalking and set it to false because it starts off not walking.
publicbool isWalking =false;
We want to change the image - the sprite - the Mario object will have to a walking image when walking and a standing image when not. We can store these sprites in their own variables:
Now that we say what to do when it's walking, we have to say somewhere WHEN to turn on (make true) isWalking and when to turn it off (make false).
We can have isWalking turn on/become true whenever we are pushing an arrow button to make Mario move by adding to the if statements in the Update() function.
Well, we turn on isWalking, we need to turn it off. The most obvious would be to turn off when the user releases or stops pressing the arrow key. Instead of creating code to look for key releases, let's turn these two if statements into an if/else if/else statement so the default would have isWalking turned off (false).
One last thing - we say what to do if isWalking is true, but not what to do when it is not.
Instead of an else statement tacked on to our if statement, let's check for and run code only when isWalking is false, using an exclamation point ! to check for the opposite or whether a variable is false.
if (!isWalking) // Same as if (isWalking == false){mario.GetComponent<SpriteRenderer>().sprite= standingImage;}
Full code so far:
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassMarioMove:MonoBehaviour{publicGameObject mario;publicSprite standingImage; // Walkpublicfloat moveSpeed =2;publicbool isWalking =false;publicSprite walk1;publicSprite walk2;publicSprite walk3;publicSprite walk4; // Jump // Start is called before the first frame updatevoidStart() { } // Update is called once per framevoidUpdate() {if (isWalking) {Walk(); }if (!isWalking) {mario.GetComponent<SpriteRenderer>().sprite= standingImage; }if (Input.GetKey(KeyCode.RightArrow)) {mario.transform.position+=Vector3.right* moveSpeed *Time.deltaTime; isWalking =true; }elseif (Input.GetKey(KeyCode.LeftArrow)) {mario.transform.position+=Vector3.left* moveSpeed *Time.deltaTime; isWalking =true; }else { isWalking =false; } }publicvoidWalk() {mario.GetComponent<SpriteRenderer>().sprite= walk1; }publicvoidJump() { }}
Save your script and return to Unity.
This is what the Mario Move component now looks like:
Click and drag the images from the Project window under Assets > Sprites to the Walk 1-4 fields or use the "target" icon on the right of each to choose from a list. For Walk 4, use the same image as Walk 2.
Test it!
Step 11: Completing the Walk Cycle
Go back to your script.
For the walk cycle, we need to keep track of which walk image is currently being shown. We can do that in an integer variable in our walk variables at the top of the script and start it at the first image or zero.
publicint currentWalkImage =0;
In the Walk() function, we can say which image to show based on the number of currentWalkImage. To keep it simple, we'll do this with if statements.
Now, we need to control WHEN to change the currentWalkImage to the next number using a countdown technique.
At the top of the script, add two floating point variables to the Walk variables - one to keep track of the speed of the animation (how long an image is seen) and a countdown to control the change to the next image.
For spriteCountdown, set in inside the Start() function to set it on the first frame.
voidStart(){ spriteCountdown = spriteLength;}
Back to our Walk() function...
As long as Walk() is running, we want the spriteCountdown variable to countdown and do stuff when it hits zero.
spriteCountdown -=1*Time.deltaTime;
Here, we are subtracting 1 from whatever spriteCountdown is on that frame with a shortcut -=. Multiplying it by Time.deltaTime normalizes the speed of the countdown so it's roughly 1 second if the countdown begins at 1.
Now, for the trigger - using an if statement to test when the countdown reaches or goes lower than zero and have it run code (update the currentWalkImage). We only have four walk images, so we need to add 1 until it gets to the fourth image, then reset to 0.
Jump() will be adding force, so it's a crazy high number as a baseline - 300!
In our Jump() function, we can add code to push up Mario. Its Rigidbody2D component that gives it gravity also has a built-in function called AddForce(). We can get to that function through the component using GetComponent<Rigidbody2d>().
AddForce() needs information - which direction to add force - in the parentheses to work. Let's use the shorthand, Vector3.up and multiply it by the jumpHeight.
Back in our Update() function, we need to tell it when to run the Jump() function - with a keystroke and only on the first frame the key is pressed (Input.GetKeyDown()).
if (Input.GetKeyDown(KeyCode.Space)){Jump();}
This will run only on the first frame the spacebar is pressed.
We can turn on isJumping and turn off isWalking here, too.
if (Input.GetKeyDown(KeyCode.Space)){Jump(); isJumping =true; isWalking =false;}
That means we need to update our if statement that looks at when to change the image to the default standing image.
Add a second condition to the if statement:
if (!isWalking &&!isJumping){mario.GetComponent<SpriteRenderer>().sprite= standingImage;}
The && logical operator makes the if statement only run if both tests are "true." This code is asking "Is this true: isWalking is false AND isJumping is false."
Your Mario Move component will now look like this:
Click and drag your jump sprite from the Project window (in Assets > Sprites) to the Jumping Image field in the Inspector window or click the "target" icon on the right to choose from a list.
Test it!
We are getting closer!
Let's fix:
It doesn't turn off "isJumping"
It allows for jumping even in the middle of a jump
It does a walking cycle even in the air
Back to the script...
For now, we will have Mario turn off jumping when it hits any other object with a collider - like our ground - by using a built-in function, OnColliderEnter2D(). Enter at the end of the function means it only runs on the first frame it recognizes the collision.
At the end of the script, but before/inside the final curly bracket }, put this (it will start to autofill):
It looks a little awkward with Mario always facing right. Let's change it to switch directions when we press the arrow keys.
One of the coolest things about using code to control game objects is that we don't need to have a second set of images for when Mario is looking left. All we need to do is update the width or the local scale on the x-axis by multiplying it by -1. That makes a scale of 1 go to -1 and vice versa.
In your Walk variables, create an integer variable to stand in for a direction. I'll use 0 for left and 1 for right. Mario starts looking right.
publicint direction =1;
At the end of the script before the final curly bracket }, add a new custom script called ChangeDirection().
publicvoidChangeDirection(){}
In that code, we want to update Mario's scale on the x-axis. We can't update localScale.x by itself, so we need to give it a new Vector3 - a set of three floating point numbers, one for each axis. We can have it use the current local scale for these values, multiplying the x-axis value by -1.
publicvoidChangeDirection(){ mario.transform.localScale = new Vector3(mario.transform.localScale.x * -1, mario.transform.localScale.y, mario.transform.localScale.z);
}
In our code that looks at keystrokes for the arrow keys, we need to check for what direction Mario is currently facing and switch it if it doesn't match the direction we need.
In Unity, create a folder in your Assets folder in the Project window and name it Audio.
Just as you click and dragged the image files from the MarioAssets folder, click and drag the audio files from the Audio file.
Click on your Mario object in your Hierarchy or Scene windows.
Click the Add Component button in the Inspector window. Search for and add the Audio Source component.
Click and drag the smw_jump.wav file from the Project window (in Assets > Audio) to the field for AudioClip OR click on the "target" icon to the right of AudioClip to select from a list.
Click the checkbox next to Play on Awake to turn it off.
Go to your script.
Create a variable near the top to hold the reference to the specific AudioSource component.
publicAudioSource jumpSound;
In the Jump() function, have the jumpSound play when it jumps.
Your Mario Move component will now look like this:
Click and drag your Mario object to the Jump Sound field OR click on the "target" icon to the right of Jump Sound to select from a list. We use the Mario object here because the sound is attached to it.