Unity 2D – Laser Defender (3)

Continued

Shoot

First drag the laser into my assets folder, and then drag it into the hierarchy. Then create a prefab folder and then drag all things in the hierarchy into it except EventSystem.

Now create the laser variable in our script file, add the laser sprite to player's laser prefab, and then delete the laser sprite from our scene. After that, add code as below:

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

public class Player : MonoBehaviour
{

    // configuration parameters
    [SerializeField] float moveSpeed = 10f;
    [SerializeField] float padding = 1f;
    [SerializeField] GameObject laserPrefab;

    float xMin;
    float xMax;
    float yMin;
    float yMax;

    // Start is called before the first frame update
    void Start()
    {
        SetUpMoveBoundaries();
    }

    private void SetUpMoveBoundaries()
    {
        //throw new NotImplementedException();
        Camera gameCamera = Camera.main;
        xMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).x + padding;
        xMax = gameCamera.ViewportToWorldPoint(new Vector3(1, 0, 0)).x - padding;
        yMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).y + padding;
        yMax = gameCamera.ViewportToWorldPoint(new Vector3(0, 1, 0)).y - padding;
    }

    // Update is called once per frame
    void Update()
    {
        Move();
        Fire();
    }

    private void Fire()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            Instantiate(laserPrefab, transform.position, Quaternion.identity); // no rotation
        }
    }

    private void Move()
    {
        var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed; // Edit -> Project Setting -> Input Manager
        var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;

        var newXPos = Mathf.Clamp(transform.position.x + deltaX, xMin, xMax);
        var newYPos = Mathf.Clamp(transform.position.y + deltaY, yMin, yMax);

        transform.position = new Vector2(newXPos, newYPos); // Change the position
    }

}

But now we can realize that our laser can't fly as we want. Add rigidbody2D into the player and change the code to make laser as object:

[SerializeField] float projectileSpeed = 10f;
...
GameObject laser = Instantiate(laserPrefab, transform.position, Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, projectileSpeed);

Because I don't wanna the laser drop down after I projectile, so I should change some attributes:

Coroutines

Coroutines could provide us game an approach to stop until something happen. To let our ship can keep firing while we press the key down, we should add coroutines into our code.

    private void Fire()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            firingCoroutines = StartCoroutine(FireContinuously());
        }
        if (Input.GetButtonUp("Fire1"))
        {
            // StopAllCoroutines();  -- brutal way
            StopCoroutine(firingCoroutines);
        }
    }

    IEnumerator FireContinuously()
    {
        while (true) // caise GetButtonDown only get the single press
        { 
            GameObject laser = Instantiate(laserPrefab, transform.position, Quaternion.identity) as GameObject; // no rotation
                                                                                                                // move object
            laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, projectileSpeed);
            yield return new WaitForSeconds(projectileFiringPeriod);

        }
    }

Shredder

Now we don't want the laser go out of the space range, so we want to create a shredder for it. First create an shredder in the hierarchy. Reset the transform and create the box collider 2D, adjust the size. And then create a script file, add codes:

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

public class Shredder : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        Destroy(collision.gameObject); //destroy the whole gameobject
    }
}

And then check the laser in the hierarchy, add the capsule collider 2D, and finally check the trigger in shredder. So we can destroy the laser goes too far away from our game background.

Waypoints and Move

To give the enemy a way to move, create a game object in the hierarchy, first create the path and waypoints in the hierarchy, and add a new script for enemy. After that drag the three waypoints into enemy. Now we want the enemy can move along the way I set, I should use the MoveTowards(). Add code in our script:

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

public class EnemyPathing : MonoBehaviour
{

    [SerializeField] List<Transform> waypoints; // Transform is x y z
    [SerializeField] float moveSpeed = 2f;
    int waypointIndex = 0;

    // Start is called before the first frame update
    void Start()
    {
        transform.position = waypoints[waypointIndex].transform.position;
        Debug.Log("kk i am on start now");
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("Update now");
        Move();
    }

    private void Move()
    {
        Debug.Log("Move inside");
        // valid point, move
        Debug.Log("waypointIndex is: " + waypointIndex + ", and waypoints.Count -1 is: " + (waypoints.Count - 1));
        if (waypointIndex <= waypoints.Count - 1) // not beyond the limit
        {
            var targetPosition = waypoints[waypointIndex].transform.position;
            var movementThisFrame = moveSpeed * Time.deltaTime; // independent
            transform.position = Vector2.MoveTowards(transform.position, targetPosition, movementThisFrame);

            if (transform.position == targetPosition)
            {
                Debug.Log("update waypointIndex");
                waypointIndex++;
            }
        }

        // otherwise
        else
        {
            Destroy(gameObject);
        }
    }
}

And make sure that the z position of waypoint matches up the z position of enemy.

Wave

Single enemy is some kind of boring, so I want my game can spawn a wave of enemies. First create a wavecofig script and create a wave1 in script:

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

[CreateAssetMenu(menuName = "Enemy Wave Config")]
public class WaveConfig : ScriptableObject
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}

For convenient, create a folder and drag the wave into it. Then change the code in waveconfig:

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

[CreateAssetMenu(menuName = "Enemy Wave Config")]
public class WaveConfig : ScriptableObject
{
    [SerializeField] GameObject enemyPrefab;
    [SerializeField] GameObject pathPrefab;
    [SerializeField] float timeBetweenSpawns = 0.5f;
    [SerializeField] float spawnRandomFactor = 0.3f;
    [SerializeField] int numberOfEnemies = 5;
    [SerializeField] float moveSpeed = 2f;

    public GameObject GetEnemyPrefab()
    {
        return enemyPrefab;
    }

    public GameObject GetPathPrefab()
    {
        return pathPrefab;
    }

    public float GetTimeBetweemSpawns()
    {
        return timeBetweenSpawns;
    }

    public float GetSpawnRandomFactor()
    {
        return spawnRandomFactor;
    }

    public int GetNumberOfEnemies()
    {
        return numberOfEnemies;
    }

    public float GetMoveSpeed()
    {
        return moveSpeed;
    }
}

And drag the prefab of path and enemy into wave1.
Similarly, create another waypoints and set the new wave in enemy prefab let it follow the wave we set and no need to drag the way points one by one now.

Spawn multiple enemies

Create an enemy spanwer in the hierarchy and create a script file for it:

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

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] List<WaveConfig> waveConfigs;
    int startingWave = 0; // idx for wave

    // Start is called before the first frame update
    void Start()
    {
        var currentWave = waveConfigs[startingWave];
        StartCoroutine(SpawnAllEnemiesInWave(currentWave));
    }

    private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        Instantiate(waveConfig.GetEnemyPrefab(), waveConfig.GetWayPoints()[0].transform.position, Quaternion.identity);
        yield return new WaitForSeconds(waveConfig.GetTimeBetweemSpawns());
    }
}

So that the enemy ship could move alone the wave0 I set early. To spawn many enemies, we should add a for loop inside SpawnAllEnemiesInWave.

       for (int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
        {
            Instantiate(waveConfig.GetEnemyPrefab(), waveConfig.GetWayPoints()[0].transform.position, Quaternion.identity);
            yield return new WaitForSeconds(waveConfig.GetTimeBetweemSpawns());
        }

Spawn All Waves

Change the code below:

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

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] List<WaveConfig> waveConfigs;
    [SerializeField] int startingWave = 0; // idx for wave

    // Start is called before the first frame update
    void Start()
    { 
        StartCoroutine(SpawnAllWaves());
    }

    // for spawn all
    private IEnumerator SpawnAllWaves()
    {
        for (int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++ )
        {
            var currentWave = waveConfigs[waveIndex];
            yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave)); // spawn the wave of current one
        }
    }

    private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        for (int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
        {
            var newEnemy = Instantiate(waveConfig.GetEnemyPrefab(), waveConfig.GetWayPoints()[0].transform.position, Quaternion.identity);
            newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig);
            yield return new WaitForSeconds(waveConfig.GetTimeBetweemSpawns());
        }
    }
}

And for more funny, we can add more enemies and waves:

Now I have 4 waves and 2 enemies in total in my game. To let them appear in my game one by one and keep looping, I need to change the code:

    IEnumerator Start()
    {
        do
        {
            yield return StartCoroutine(SpawnAllWaves());
        }
        while (looping);
    }

Leave a Reply