Session 3

 

Photoshop Sprite Art Resources:

http://makegames.tumblr.com/post/42648699708/pixel-art-tutorial

http://gas13.ru/v3/tutorials/sywtbapa_almighty_grass_tile.php

https://design.tutsplus.com/tutorials/how-to-create-an-animated-pixel-art-sprite-in-adobe-photoshop–cms-20428

 

Settings for accurate color and resolution for retro-style sprites:

screen-shot-2016-11-05-at-2-54-22-am

Pixels Per Unity determines the size of a sprite compared to Unity’s sizes. An image that is 100×100 would be 1 unit tall in Unity if the setting was 100. 200 would be half the size. 50 would be double the size.

Point (no filter) mode keeps sharp pixelated textures.

Choosing to Override the format for images from compressed 16-bits or truecolor keeps the colors from changing due to effects.

Getting back to Unity:

One of the basic tenets of good programming is DRY: Don’t Repeat Yourself. Functions are one way to keep your code simpler, and write less of it. Other ways to make life easier are variables and loops.

Reduce, Reuse, Recycle: Variables

We’ve already used variables a couple of times in the previous lessons. Variables in Programming are somewhat like variables from algebra. They are both a placeholder in the equation or code for another number or in the case of programming, any kind of data.

Variables in C# are considered “strongly typed.” This means that when you create the variable, you have to specify what kind of data is going to go into it. There are many different variable types, ranging from simple numbers to positions and media. Here are a few of the common ones used while programming for Unity:

  • int: integers, or whole numbers.  ex: 1, 2, 3, 4… and so on.
  • float: floating point numbers, or numbers with decimal points. Used for speed, position, time, or anything where you need finer control than whole numbers. In c#, there needs to be an f after the number. ex: 0.5f 1.3f, 101.23f, and so on.
  • bool: short for boolean operator, this can only be true, or false.
  • Vector3 and Vector2: these are groups of floats most commonly used for positions, but can also be used for rotations and scaling. ex: Vector3:(1, 1, 1).
  • GameObjects: this is a Unity GameObject, and we’ve already used it before for our ship’s bullets so we could fire them.

Here’s how you declare a whole number counter, which is useful for scores, level numbers, and counting a specific number of objects:

int numberVariable;

nunberVariable = 10;

Let’s go back to our ship.cs script and make some changes to learn how to use variables:

  public class ship : MonoBehaviour {
 
     public GameObject bullet;
 
     public float moveSpeed;
 
     // Update is called once per frame
     void Update () {
         if (Input.GetAxisRaw("Horizontal") != 0) {
             float hMove = Input.GetAxisRaw("Horizontal");
             transform.Translate(hMove * moveSpeed * Time.deltaTime, 0, 0, Space.World);
         }
 
         if (Input.GetButtonDown("Jump")) {
             Shoot();
         }
     }

First we add the moveSpeed variable to the script. You may have figured out that we’ve specified that it’s a float. And just like our bullet, we’re making this variable public so that we can see and adjust it in the editor Inspector window.

We’re also replacing the 10 from the movement code with our new variable.  After saving our script, we need to head into the inspector window in the Unity editor. The moveSpeed variable will appear in the ship.cs script component. It will say 0 because it was just declared, but we can adjust that to 10 just like we had it before.

This is very powerful and useful for a variety of applications. We could create a powerup that will increase speed by adjusting the moveSpeed variable. We could change the moveSpeed during testing to make sure the speed and feel are good.

On an unrelated note, I’ve added Space.World at the end of the Translate method, because it will ensure the ship moves left and right even if the ship gets rotated by accident.

Repeating actions a set number of times: For Loops

Another way to save yourself the time of writing code over and over again are

for (int x = 0; x < 10; x++) {
  // your code to repeat
}

Let’s break down the anatomy of the for loop:

int x = 0 is the declaration of the variable that we’re going to count.
x < 10 is the condition that needs to be true to continue the loop.
x++ is increasing the variable x at the end of each loop by 1. ++ is a shorthand for increasing a variable by 1, and is the same as typing x+=1.
This is how the for loop works if we write it out: Start the count from 0. The following code will be performed while the count is less than 10. The count goes up by 1 each time the code is performed.

Let’s make a smarter enemy

Our basic cubes can be our dumb enemies, but we should throw one or two enemies in the game that are smarter. Create a new 3D shape–in my case I used a cylinder for variety, but this can be another cube or a sprite that we create, and apply a new material to. Name this shape “Commander”, and add a RigidBody component as we did in session 2 with the other enemies. Under Add Component, scroll down and click “New Script”, and call it “commander”. Double-click the new script called commander.cs in the Project window to open it up in the code editor. We’re going to add the following highlighted code:

 public class Commander : MonoBehaviour {
 
     public Transform player;
     public float moveSpeed;
     
     // Update is called once per frame
     void Update () {
           if (transform.position.x > player.position.x + 0.2f) {
             transform.Translate(-1 * moveSpeed * Time.deltaTime, 0, 0, Space.World);
           }
           if (transform.position.x < player.position.x - 0.2f) {
             transform.Translate(1 * moveSpeed * Time.deltaTime, 0, 0, Space.World);
           }
     }
 }

Here we’re combining a couple of things. By declaring a variable for the Transform component, when we add the ship object to the commander.cs script component in the Inspector, we’re going to get a shortcut directly to the Transform component in our scripts.

In the Update function, there are two conditional statements that mirror each other, using movement code from our ship. If the ship’s x position is more than a fraction to the left or right of the x position of the player, it will move closer to the player’s direction.

Making the enemy shoot

To make the enemy shoot, I’d added a few lines to the commander.cs file.  I also created an enemy bullet by copying both the original bullet and its script, creating an enemy bullet that travels down instead of up, and changing the tag to “enemy.”

  public class enemyBullet : MonoBehaviour {
 
     public float bulletSpeed;
     
     // Update is called once per frame
     void Update () {
         if(transform.position.y > -15.0f) {
             transform.Translate(0, -bulletSpeed * Time.deltaTime, 0);
         } else {
             Destroy(this.gameObject);
         }
     }
 
 }

Now, we can add that enemy bullet to our commander enemy, same as for the player ship:

   public class Commander : MonoBehaviour {
 
     public Transform player;
     public float moveSpeed;
 
     public GameObject enemyBullet;
 
     
     // Update is called once per frame
     void Update () {
         // Make our enemy 3D cylinder spin in all 3 axis directions
         transform.Rotate(1, 1, 1);
         if (player != null) {
             if (transform.position.x > player.position.x + 0.2f) {
                 transform.Translate(-1 * moveSpeed * Time.deltaTime, 0, 0, Space.World);
             }
             if (transform.position.x < player.position.x - 0.2f) {
                 transform.Translate(1 * moveSpeed * Time.deltaTime, 0, 0, Space.World);
             }
             if (transform.position.x > player.position.x - 0.2f && transform.position.x < player.position.x + 0.1f) {
                 Shoot();
             }
         }
     }
 
     void Shoot () {
         Instantiate(enemyBullet, transform.position, Quaternion.identity);
     }
 }

I’ve also added another conditional that says to fire only if within the movement range where it would be roughly above the player.

Blowing up our ship

Add the following to your ship.cs script, underneath the Update () {} loop:

      void OnTriggerEnter (Collider other) {
         if(other.CompareTag("enemy")) {
             Destroy(this.gameObject);
         }
     }

The player will be destroyed if any object tagged “enemy” hits it, which in this case are the enemies and the bullets they shoot.

The commander keeps shooting at me

We’ll use a bool to track if the enemy has shot, here called “shotReloading” and reload the enemy’s bullet when it moves. This will prevent the enemy from constantly beaming a laser at you.

using UnityEngine;
using System.Collections;

public class Commander : MonoBehaviour {

public Transform player;
 public float movementSpeed;

public GameObject enemyBullet;

public bool shotReloading;

// Use this for initialization
 void Start () {
 
 }
 
 // Update is called once per frame
 void Update () {
 // Make our enemy 3D cylinder spin on all 3 axises
 transform.Rotate(1, 1, 1);
 if (player != null) {
 if (transform.position.x > player.position.x + 0.1f) {
 shotReloading = false;
 transform.Translate(-1 * movementSpeed * Time.deltaTime, 0, 0, Space.World);
 }
 if (transform.position.x < player.position.x - 0.1f) {
 shotReloading = false;
 transform.Translate(1 * movementSpeed * Time.deltaTime, 0, 0, Space.World);
 }
 if (transform.position.x > player.position.x - 0.1f && transform.position.x < player.position.x + 0.1f) {
 if (shotReloading == false) {
 Shoot();
 }
 }
 }
 }

void Shoot () {
 Instantiate(enemyBullet, transform.position, Quaternion.identity);
 shotReloading = true;
 }
}

 

Moving the enemies

A Coroutine is a side process that runs at the same time as other functions, including Update. Coroutines in C# are also a way to use “WaitForSeconds()”, which is a useful function that lets you set a delay for actions to happen.

In the following code added to the enemy.cs script, I’ve added a few variables for movement, and made them public so they can be adjusted in the Editor’s Inspector window. Coroutines have to be called a special way, by entering the name in a StartCoroutine() function.

In this coroutine, a For Loop is used to repeat a series of actions 10 times.  Each step is a delay, followed by moving the enemy in a specified direction. This method for scripting movement also uses a For Loop, starting with the specified distance for movement, and subtracting from it each frame by the fraction of a second that has elapsed, and then moving on to the next step.

  public class enemy : MonoBehaviour {
 
     public float movementSpeed;
     public float movementDelay;
     public float xMovement;
     public float yMovement;
 
 
     // Use this for initialization
     void Start () {
         StartCoroutine("EnemyMovement");
     }
     
     // Update is called once per frame
     void Update () {

     }
 
     IEnumerator EnemyMovement () {
             for (int z = 10; z > 0; z--) {
                 yield return new WaitForSeconds(movementDelay);
                 // Y movement
                 for (float y = yMovement; y > 0; y-=Time.deltaTime) {
                     transform.Translate(0, -1 * movementSpeed * Time.deltaTime, 0, Space.World);
                     yield return null;
                 }
                 yield return new WaitForSeconds(movementDelay);
                 // X movement
                 for (float x = xMovement; x > 0; x-=Time.deltaTime) {
                     transform.Translate(-1 * movementSpeed * Time.deltaTime, 0, 0, Space.World);
                     yield return null;
                 }
                 yield return new WaitForSeconds(movementDelay);
                 // Y movement
                 for (float y = yMovement; y > 0; y-=Time.deltaTime) {
                     transform.Translate(0, -1 * movementSpeed * Time.deltaTime, 0, Space.World);
                     yield return null;
                 }
                 yield return new WaitForSeconds(movementDelay);
                 // X movement
                 for (float x = xMovement; x > 0; x-=Time.deltaTime) {
                     transform.Translate(1 * movementSpeed * Time.deltaTime, 0, 0, Space.World);
                     yield return null;
                 }
             }
             yield return null;
 }

}