Thursday, January 18, 2018

Simple Side Scroller - A Unity Game

Guest Post from Siddhant Gupta

This Simple Side Scroller is a game made from resources mentioned in the site at the end of this post. In this game we fly a plane which shoots sprites. This is a description of the features of the resources:

Free plane sprite for your side scrolling shooter games

Features:
  • 1 Plane with 3 animations: fly, shoot, & dead
  • Simple background
  • Fully editable vector source files in SVG and AI file formats.
  • Separate PNG sequence files for quick integration in your game projects
Here is how you set up the background image:



You may consult YouTube videos on how to change background.
Create a big Background Quad and apply a texture to it like this.
Change the background sprite to repeat (default is clamp) and hit the Apply button. 
This will allow our background to scroll freely without any artifacts. You may not perceive it but our plane will be stationery and the background will move up and down.
Now click and drag the BG sprite/texture to the Quad, the quad will be UV wrapped by this texture now.
At first the texture will appear very dark, because it is a Standard Shader with an Albedo texture so it interacts with all the lights in the world, since this is a simple 2D game we wont be interacting with lights, so we have to change the texture to an Unlit shader which supports a texture.
Browse to the mesh Renderer of the Quad and click on the Shader Dropdown and select Unlit/Texture. Now u have a properly lit textured quad, doesn't Unity make such complex things so simple! 😃



It is always a good idea to rename our objects in the scene to something that makes sense , so we will rename our Quad to Background (name it to whatever u want).

Now comes the fun part CODE!!!!!

So first lets make out bg scroll to give the fake illusion of player moving
Create a new Script , called Scroller.cs.




using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scroller : MonoBehaviour {
//Public field shows up in the inspector so it can be easily 
//tweaked even while the game is 
//running
public float scrollSpeed;
//Private fields dont show up in the inspector
private float scrollValue;
private MeshRenderer renderer;
// Use this for initialization
void Start () {
renderer = GetComponent<MeshRenderer>();
}
// Update is called once per frame

void Update () {
// Update the scroll value .....Time.deltaTime is used to make sure the 

// scrolling is independent of the fps the game is running on.
scrollValue += scrollSpeed*Time.deltaTime;
// UV values range from 0 - 1 so there is no point in exceeding the 
// value > 1 , so we reset the value back to 0 , in order to loop it.

if(scrollValue >= 1f)
{
scrollValue = 0f;
}
//Update the Main texture offset X value to make it scroll horizontally.
renderer.material.mainTextureOffset = new Vector2(scrollValue,0f);
}
}


The idea of this script is to update the UV Offset of the Main Texture (Background texture) of the quad. This will allow us to scroll it in real time.

Time.Delta time is very important to make Your calculations independent of frames per second, I would suggest u too try this code without Time.deltaTime (Comment that out) and see
What happens?
(Spoiler : The scrollValue in Scroller.cs will take unpredictable jumps which will cause the bg to scroll unevenly.)

Q. What is Awake()?
A. Unity invokes the Awake method on all the gameObjects active in the scene when the scene starts.

Q. What is Start()?
A. After Awake, Start method is called, so these two methods can be used as constructors for our objects and classes, all the initial requirements of the code can be put here, like assigning references or grabbing the MeshRenderer component like we did in the Scroller.cs script, because before we can use the component in the code, we need a reference to it. The Mesh Renderer component can be grabbed in Awake() or Start() , it wont make a difference in this example as the first use of the renderer is in the update script.

Q What is Update()?
A This is the main method , which u will end up using a lot , basically Unity calls this method each frame, so all the code under this method will get Updated each frame. Anything that requires to be run/executed each frame should be done inside this method.

It has all the details u need to know about Unity's Script Execution order

Ok so we r done with bg scrolling now lets move on to the player movement and firing bullets
Before we move on to scripting, lets talk about Unity Prefabs.




Prefabs are a great way to change multiple instances of the same GameObject from only a single object called prefab.Imagine if u have 10 point lights in the scene now u want to change their color from say yellow to red, instead of clicking each and every light and changing its property, what u can do is drag and drop one point light from the scene to the asset folder(we have created a prefab folder under assets, I urge u to do the same, keeps our hierarchy clean) to create a prefab of that object, now u can drag this prefab and put it in the scene multiple times. So now say we have 10 Point lights in the scene which are clones of that prefab, but the good thing is now if u change the color of the prefab to red then all the clones of that prefab in the scene will also change to red color.
Isn't that amazing how Unity simplifies small small things, that make a huge difference in the development process.
Here is a much better explanation of Prefabs than i could give, please refer to this link before proceeding from Here.


using System.Collections;

using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public float accel = 0f;
    public float currentSpeed;
    public float targetSpeed;
    public float fireDelay = 0f;

    private float timeElapsed = 0f;
    public GameObject bulletPrefab;

    // Use this for initialization
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        //Detect Keyboard Input
        if (Input.GetKey(KeyCode.S))
        {
            targetSpeed = -5f;
        }
        else if (Input.GetKey(KeyCode.W))
        {
            targetSpeed = 5f;
        }
        else
        {
            targetSpeed = 0f;
        }

      //Lerp the CurrentSpeed towards the targetSpeed
        MoveToTargetSpeed();
        if (Input.GetKey(KeyCode.Space))
        {

/* ADD a delay between each time the player fires the bullet , so we dont spam the frame with multiple bullet objects , usually 0.1s dealy works good
but it all depends on ur game design and difficulty.This variable can also be used as an upgrade option , where the players can change their ships firing speed.*/
           
            timeElapsed += Time.deltaTime;
            if (timeElapsed >= fireDelay)
            {
                Fire();
                timeElapsed = 0f;
            }
        }
    }
    void MoveToTargetSpeed()

    {
        /* Increment or Decrement the currentSpeed value  */
   currentSpeed += accel * Time.deltaTime * Mathf.Sign(targetSpeed - currentSpeed);

/* This is where the bug happens but it is a nice feature to have so i kept this poor piece of logic */
        if (Mathf.Abs(Mathf.Abs(targetSpeed) - Mathf.Abs(currentSpeed)) < 0.01f)
        {
            currentSpeed = targetSpeed;
        }

/* Store the Next Position to update in temp var so that we can check if the player is reaching the bonds of the device's screen you dont want the player to go off the screen. Camera.main.WorldToScreenPoint is a helpful method to convert any Vector in world space to screen space. We need the player Coordinates in screen space so we can compare it to the device height and width which are pixel values  */


Vector3 nextPos = new Vector3(transform.position.x, transform.position.y + currentSpeed * Time.deltaTime, transform.position.z);
        if (Camera.main.WorldToScreenPoint(nextPos).y > Screen.height || Camera.main.WorldToScreenPoint(nextPos).y < 0f)
        {

/* if players next position is crossing the device's screen bounds then stop the movement and return to Update , this skips the actual position update we r doing in the last line of this method*/
            currentSpeed = 0f;
            targetSpeed = 0f;
return;
        }
// if everything is alright then update the player position.
         transform.position = nextPos;
    }

    void Fire()
    {

// Create a clone of the Bullet GameObject at the current player position and the // bullets 
// rotation should be the default prefab rotation.
        Instantiate(bulletPrefab, transform.position, Quaternion.identity);
    }
}


The above player script is pretty straightforward, check for inputs, set targetSpeed accordingly and lerp the current speed towards the target speed to get a feeling of acceleration.

The player script holds a reference to a prefab called BulletPrefab, so we can change and modify the bullets from one single object.

The script also allows the player ship to Fire after a delay of 0.1s. It is good to have a delay so as not to spam the frame with multiple bullets(that is just unrealistic and would probably lag out your system).
Now there is a fun part to this code, which i did not realize while writing, i produced a bug in the movement code but decided to keep it as a feature 😜. If you notice the player ship keeps floating up and down randomly thats beacuse the currentSpeed variable in the Player.cs never actually sets to 0, it will give a +- 1f fluctuation. It makes the player ship move more realistically cause no plane flys in a perfect straight line, winds cause the plane to have a little turbulence which is showed here by the random up and down moevement, isn't that cool !!!

To make the bullets move when fired a simple Bullet.cs script is used

using System;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;

    void Update()


    {
        transform.Translate(new Vector3(speed,0f,0f)*Time.deltaTime);
    }
}



Nothing much to explain here, when the bullets are instantiated by the player, the Update() method of the bullet script starts getting invoked which translates the bullet in forward direction. Refer to the code comments for better details.

Just a few lines of Code and we already have soo much going on.

Sources: