Unity Depth Charges “Remake” with source code

My Unity Depth Charges remake is not an exact remake. I took the idea of a destroyer dropping depth charges to bomb submarines. Other than that there are some major differences from the original game from Gremlin. The 1977 version is time-based. My version runs until you are killed.

The ship is controlled with A and D or the left and right arrow keys. The subs spawns outside the screen. Only one sub for each depth, so there will never be two subs on the same depth. All subs will fire either a bomb or torpedo. There is 10% chance the sub will fire a torpedo. They are about 7 times faster than the bombs. The player can have 4 depth charges in the water at the same time. So everytime a depth charge leaves the screen or hit a sub, you have another depth charge available.

You can download the entire project from my GitHub Repository.

Unity Depth Charges “Remake” Demo Video

Check out the short demo video below. It was a quick run. Those torpedos are fast.

Scripts used in Unity Depth Charges

Barrel.cs – Controls the sinking of the depth charges, and check if it hits a sub or goes below the bottom of the screen.
BoatController.cs – Controlls the boat and fires depth charges.
EnemyBullet.cs – Moves the bomb and torpedos upwards, and check if it hits the player or the sky.
ExplosionPlayer.cs – Spawns the explosion vfx. Same vfx is used for all explosions.
MenuManager.cs – Load the game scene or quit, depends on what the player clicks.
SceneManager.cs – Spawns the subs.
Score.cs – A class made to keep track of the score, ammo and kills.
SubController.cs – Controls the sub, fire torpedos, and bombs, and kills the sub if it gets hit or leaves the screen.

A lot of the code is pretty basic so I wont cover every code line.

Barrel.cs

The barrel script just make sure the barrel moves downward. Just a basic transform.Translate is used to move the barrel. And a 2d box collider checks for any collision with an object with the tag submarine.

// The main part of barrel script
void Update()
    {
        translation = speed * Time.deltaTime;
        transform.Translate(0, translation, 0);

        if (transform.position.y < bottomBorder.y)
        {
            Score.SetCharges(1);
            Destroy(gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Submarine")
        {
            Score.SetCharges(1);
            Destroy(gameObject);
        }
    }

BoatController.cs

This is the script for controlling the player boat. It makes sure you can move the boat right and left, and drop bombs. The script will also make sure you can not leave the screen. Here is the complete script.

The update function does three things. Move the ship by using Input.GetAxis(“Horizontal”), drop a depth charge when pressing space, and update the player position for the subs to aim at once a second.

The LateUpdate() function is used to keep the player on the screen. This should work on any screen resolutions. Here is the code used.

private void LateUpdate()
{
    // Keep the player boat inside the visible screen
    float spriteLeft = transform.position.x - spriteHalfSize.x;
    float spriteRight = transform.position.x + spriteHalfSize.x;

    Vector3 clampedPosition = transform.position;

    if (spriteLeft < leftBorder.x)
        clampedPosition.x = leftBorder.x + spriteHalfSize.x;
    else if (spriteRight > rightBorder.x)
        clampedPosition.x = rightBorder.x - spriteHalfSize.x;

    transform.position = clampedPosition;
}

The 2d box collider checks for collisions between torpedos and bombs launched from the subs. Since there is only one life in this game, it will also send the player back to the menu. There is a two second delay from the player is hit until we load the menu scene. In the meantime I turn of the collider and a renderer to prevent any more collisions and to hide the player.

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.tag == "bomb" || collision.tag == "Torpedo")
    {
        GameObject ExPlayer = Instantiate(bomb, transform.position, Quaternion.identity);
        Invoke("GoToMenu", 2);  // Go back to menu after 2 seconds
        Destroy(ExPlayer, 2.5f);
        rend.enabled = false;   // Make player invisible 
        mCollider.enabled = false; // Turn of collider
    }
}

void GoToMenu()
{
    Destroy(gameObject);
    UnityEngine.SceneManagement.SceneManager.LoadScene("Menu");
}

EnemyBullet.cs

The EnemyBullet script does three things. Check if it a bomb or torpedo, and then set the speed to 1 if it is a bomb, or 7 if it is a torpedo. The update function moves the bullet, and a 2d collider checks if it hits the player or the sky. Both make it disappear. You can see the entire script here.

ExplosionPlayer.cs

This script has only one purpose, and that is to play the explosion. It is the same explosion for all object. This is the script used to play all of them.

public class ExplosionPlayer : MonoBehaviour
{
    [SerializeField]
    GameObject ExplosionVfx = default;

    private void Start()
    {
        GameObject explosion = Instantiate(ExplosionVfx, transform.position, Quaternion.identity);
        Destroy(explosion, 1);
    }
}

MenuManager.cs

MenuManager is the script for the menu scene. Just load the game if the player clicks play, quit if they click quit. And display the high score. That is it.

using UnityEngine;
using UnityEngine.UI;

public class MenuManager : MonoBehaviour
{
    [SerializeField]
    private Text highScoreText;

    public void MainScene()
    {
        UnityEngine.SceneManagement.SceneManager.LoadScene("MainScene");
    }

    public void QuitGame()
    {
        Application.Quit();
    }

    private void Start()
    {
        highScoreText.text = "High Score: " + Score.GetHighScore().ToString();
    }
}

SceneManager.cs

The script for the game scene. It spawns the submarines. When the script starts we find the screen size (width) and reset all player variables such as ammo, kills and score.

void Start()
{
    audioPlayer = GetComponent<AudioSource>();
    leftBorder = Camera.main.ViewportToWorldPoint(Vector3.zero);
    rightBorder = Camera.main.ViewportToWorldPoint(Vector3.one);
    Score.Reset();  // Reset score, kills, subslots, ammo
}

The Update function instantiates the subs every 2 to 4 seconds. The first spawn is set to spawn after 2 seconds.

void Update()
{
    spawnTimer -= Time.deltaTime;
    if (spawnTimer <= 0)
    {
        spawnTimer = Random.Range(2f, 4f);  // Spawn between 2 and 4s
        SpawnSub();
    }
}

There are probably easier and smarter ways of doing the SpawnSub() function.

void SpawnSub()
{
    bool slotFound = false;
    int counter = 0;

    int direction = Random.Range(0, 2);
    audioPlayer.PlayOneShot(sonarSound, 1f);

    switch (direction)
    {
        case 0:
            subPos.x = leftBorder.x - 2f;
            break;
        default:
            subPos.x = rightBorder.x + 2f;
            break;
    }

    while (!slotFound)  // find a free slot to spawn the sub
    {                   // only one sub for each depth 
        subPos.y = Random.Range(-4, 2);
        slotFound = Score.ReadSlot((int)subPos.y);
        counter++;
        if (counter > 6)
        {
            subPos.y = Random.Range(-4, 2);
            counter = 0;
            slotFound = true;
        }
    }

    Score.SetSlot((int)subPos.y, false);
    Instantiate(enemy, subPos, Quaternion.identity);
}

First the script picks a direction for the sub to go. Either from left to right or right to left. Then the script finds a depth for the sub to spawn in on. See the pic below for how I arranged the slots. But I only want one sub for each slot at the time. So a maximum of 6 subs at the time. However, if someone changed the script a reduced the minimum spawn time all slots might be taken. So instead of the game freezing, I just pick a random slot for the next sub. So there will be two subs on the same slot. I try 6 times before spawning the sub on the same slot. Why 6? No idea. It works probably better if that was changed to 1.

This is how the screen is set up with slots for each sub to spawn inside

Score.cs

The score script was at first only the class for keeping track of the score. Then I found it handy to keep a few more variables.

private static int score;
private static int highScore;
private static int charges;
private static int kills;
private static Vector2 boatPos;
private static bool[] subSlots = new bool[6]
{
    true, true, true, true, true, true
};

The functions in this script are just methods for reading and setting these variables. The AddScore function has the scoring algorithm. It is easy tho.

public static void AddScore(int depth)
{
    score += 70 - ((depth + 5) * 10);
    kills++;
    if (score > highScore)  // Check if there is a new high score
        highScore = score;  // every time the player get some points
}

Every kill starts out as 70 points as maximum. But that is for hitting the subs in slot 0. For every slot closer to the surface the scripts subtract 10 points. So subs hit in slot 5 only get 10 points.

SubController.cs

Last script in the game. The script that controls the submarines. At the start of the script, we decide if the sub should release a bomb or a torpedo. There is a 10% chance the sub will release a torpedo.

int torpedoChance = Random.Range(0, 10);
if (torpedoChance == 7)
{
    willFireTorpedo = true;
    audioPlayer.PlayOneShot(torpedoWarning, 1f);
}

Then we check on which side the sub has to spawn on, right or left. We flip the sprite if it was spawned on the left side. At the end of the start function, we check for the player position for where to drop the bombs.

The update function moves the sub. Check for when we reached the player position we got from the start function. And make sure we kill the sub when it leaves the screen.

void Update()
{
    translation = direction * speed * Time.deltaTime;
    transform.Translate(translation, 0, 0);

    // Release the bomb
    if (direction == -1 && transform.position.x < bombCoordinates && !hasFired)
        BombRelease();
    else if (direction == 1 && transform.position.x > bombCoordinates && !hasFired)
        BombRelease();

    // Kill the sub outside of borders
    if (direction == -1 && (transform.position.x < leftBorder.x - spriteHalfSize.x))
        KillSub();
    else if (direction == 1 && (transform.position.x > rightBorder.x + spriteHalfSize.x))
        KillSub();
}

Rest of the script only checks if the subs get hit by a depth charge and kill the sub if it does.

Unity Depth Charges, known bugs

At the moment I only know of one bug in the game. That is you can continue to drop depth charges after you are killed. Should be an easy fix, right?

If you are into retro stuff, check out my simple but cool retro demo.

About Author

Related Posts

C# Reference Types

Understanding C# Reference Types

One of the key features of C# is its support for C# reference types, which allow developers to create complex, object-oriented applications. In this blog post, we…

c# value types

Understanding C# Value Types

C# is a powerful programming language that is widely used for developing a wide range of applications. One of the key features of C# is its support…

C# check if server is online

C# check if server is online directly from your code. Check servers or services like web servers, database servers like MySQL and MongoDB. You can probably check…

C# Convert Int to Char

C# convert int to char google search is giving some result which I think is not directly what some people want. Most results are giving instructions on…

c# bash script

C# Bash Script Made Easy

There are many reasons why it could be handy to run a bash script from a C# application. In 2014, before I changed to a Mac as…

Unity persistant datapath

Unity Persistent Data Path

The Unity persistentDataPath is read only and will return the path where you can save data that will be persitent betwen sessions. Even app updates doesn’t touch…

This Post Has One Comment

Leave a Reply