Animalien Invaders 2D Laser Defender Game, Part 2

Home / Technology / Game Development / Animalien Invaders 2D Laser Defender Game, Part 2
The Animalien Invaders laser defender game is progressing with a missile firing system for the player, enemy movement along paths, and enemy wave configuration. Read more at blissfullemon.com/animalien-invaders-2d-laser-defender-game-2

Here’s a little refresher, just in case you missed Part 1 last week: Animalian Invaders is a 2D laser defender game. The idea is that animal aliens are attacking Earth and the player must shoot projectiles to scare the invaders away.

In Part 1, I began setting up the game in Unity, adding sprites and player movement. In part 2, I moved into a few more advanced concepts like waypoints, enemy movement, and that sort of thing.

What I Learned

  • How to make the enemy shoot a missile by instantiating and moving a missile game object whenever the space bar is pressed
  • The basic idea of using coroutines to yield its execution for a certain amount of time or until other specified conditions are met – i.e. doing something, waiting a certain amount of time, then continuing
// Sample coroutine that displays text after 5 seconds

StartCoroutine(DelayedTextCoroutine());

IEnumerator DelayedTextCoroutine()
{
     Debug.Log("Another statement will be displayed in 5 seconds");
     yield return new WaitForSeconds(5);
     Debug.Log("Another statement");
}
  • How to use a coroutine to fire continuously at a certain rate while holding down the space bar
  • Using a box 2D trigger game object to destroy missiles once they have collided with it (old concept, just reinforced)
  • The process of creating waypoints to specify movement along a path
  • Lists versus arrays; more specifically, using lists for storing a variable number of elements

If in doubt, whip a list out.

– Ben Tristem, Complete C# Unity Developer 2D: Learn to Code Making Games
  • How to create a list using List<type> listName
  • Using a list to create multiple waypoints for moving the enemies along their path
  • How to set up a WaveConfig scriptable object to spawn waves of enemies along a path

Enemy Waves

The challenge of this game is in facing multiple waves of enemies that move at varying speeds. Because of this, a large part of the work this week was in determining how to set the waves up and, more importantly, what will happen when each wave hits.

There are 4 separate scripts that control enemies and their waves.

  1. EnemySpawner.cs – controls the order of the waves and spawns each one and all the individual enemies included in each wave
  2. Enemy.cs – controls the appearance, hit points, behavior, score, and effects for each enemy
  3. WaveConfig.cs – contains waypoints for paths, spawn speed for each wave, the number of enemies in each wave, and the movement speed of enemies within the wave
  4. EnemyPathing.cs – moves the enemies along their path

The EnemyPathing.cs and WaveConfig.cs scripts have already been created. The next steps will be to create and code the Enemy.cs and EnemySpawner.cs files.

Game Scripts

WaveConfig.cs

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 List<Transform> getWaypoints()
    {
        var waveWaypoints = new List<Transform>();
        foreach (Transform child in pathPrefab.transform)
        {
            waveWaypoints.Add(child);
        }

        return waveWaypoints;
    }

    public float getTimeBetweenSpawns() { return timeBetweenSpawns; }
    public float getSpawnRandomFactor() { return spawnRandomFactor; }
    public int getNumberOfEnemies() { return numberOfEnemies; }
    public float getMoveSpeed() { return moveSpeed; }

}

Shredder.cs

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

public class Shredder : MonoBehaviour
{

    private void OnTriggerEnter2D(Collider2D collision)
    {
        Destroy(collision.gameObject);
    }
}

Player.cs

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

public class Player : MonoBehaviour
{
    // Configuration Params
    [SerializeField] float moveSpeed = 10f;
    [SerializeField] float padding = 1f;
    [SerializeField] GameObject misslePrefab;
    [SerializeField] float projectileSpeed = 10f;
    [SerializeField] float projectileFiringPeriod = 0.1f;

    Coroutine firingCoroutine;

    float xMin , xMax , yMin, yMax;

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

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

    private void Fire()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            firingCoroutine = StartCoroutine(FireContinuously());
        }

        if (Input.GetButtonUp("Fire1"))
        {
            StopCoroutine(firingCoroutine);
        }
    }

    IEnumerator FireContinuously()
    {
        while (true)
        { 
            GameObject missle = Instantiate(misslePrefab,
                    transform.position,
                    Quaternion.identity) as GameObject;
            missle.GetComponent<Rigidbody2D>().velocity = new Vector2(0, projectileSpeed);

            yield return new WaitForSeconds(projectileFiringPeriod);
        }
    }

    private void Move()
    {
        // Horizontal
        var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
        var newXPos = Mathf.Clamp(transform.position.x + deltaX, xMin, xMax);

        // Vertical
        var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
        var newYPos = Mathf.Clamp(transform.position.y + deltaY, yMin, yMax);

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

    private void SetUpMoveBoundaries()
    {
        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;
    }
}

EnemyPathing.cs

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

public class EnemyPathing : MonoBehaviour
{
    [SerializeField] WaveConfig waveConfig;
    List<Transform> waypoints;
    [SerializeField] float moveSpeed = 2f;
    int waypointIndex = 0;

    // Start is called before the first frame update
    void Start()
    {
        transform.position = waypoints[waypointIndex].transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        waypoints = waveConfig.getWaypoints();
        Move();
    }

    private void Move()
    {
        if (waypointIndex <= waypoints.Count - 1)
        {
            var targetPosition = waypoints[waypointIndex].transform.position;
            var movementThisFrame = moveSpeed * Time.deltaTime;
            transform.position = Vector2.MoveTowards
                (transform.position, targetPosition, movementThisFrame);

            if (transform.position == targetPosition)
            {
                waypointIndex++;
            }
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

Moving Forward

There are only a few weeks left of my Summer of Code adventure. As things are starting to wrap up and come to a close, I’d love some input. What would you like to see moving forward? I’m obviously planning on picking back up and finishing this Udemy course, but is there anything else you want to learn more about? Now is the time to start throwing those questions in so I can answer them!

If you’re interested in seeing more in-depth coding explanations and that sort of thing, let me know.

Also, if you enjoy reading content like this, be sure to subscribe below to get alerts in your inbox when I post something new. ❤

The Animalien Invaders laser defender game is progressing with a missile firing system for the player, enemy movement along paths, and enemy wave configuration. Read more at blissfullemon.com/animalien-invaders-2d-laser-defender-game-2

Let's chat!

%d bloggers like this: