A tank for the job of a bike (AKA what not to do when coding)

This post is about a spawn algorithm for the light berries in the game. But, in this post, I will be focusing on the things I did wrong, things that should NOT be done. I will mention why I did it, why I shouldn’t have, and what I could have done.

What happened with this algorithm is similar to this: to do the job of a bike, I tried to build a tank. And to build a tank, I started by building a piece of the tank, which then gets destroyed to remake the proper piece, instead of, well, building a tank from the start. Or even better, build a bike, which was all that was necessary. Oh, and, the result of the whole process wasn’t a good tank, it was one with a gigantic hole in it! So, yea, generally not a good idea to do this.

So here’s what I did. I made an algorithm that stores all the spawn points of the berries in Vector3 (Vector3 because it is required for the instantiate function) with z co-ordinates set to 0 (because it’s a 2D game). Then, I add these to a list. Each time a berry spawns, that point is removed from the list (did I mention how awesome the lists in Unity are?). Each time a berry gets picked, the co-ordinates gets re-added to the list. This time, the list is public static, so it can be accessed and edited from other functions. Static can be used here because there is only need for one list of berry positions. The berry respawn timer and maximum number that can spawn at a time can be changed through Unity with a public editor. While this seems like a simple way to do it, there are several problems with it. Mainly, all the values are hard-coded. So others (even programmers, since they are unfamiliar with my code) will have trouble editing it. But more importantly, any edits to the map, from a tree being moved 0.1 units to a new enemy being added, will most probably make the code unusable. The berry would spawn in the wrong position! Which actually did happen. Twice. I had to find and hard-code 15 to 20 berries, about three times! Now that’s inefficient.

So what I did to ‘fix’ this, is make a new prefab berry sprite (with all the scripts from the berry removed) and a different tag on it. These berries would be scanned for at the start for their positions. The positions get added to the list and the berries would be destroyed. The new prefab was because I didn’t want the actual berries in the scene at start (they had scripts on them) and then randomly destroy and re-add them. So what did I do wrong. For starters, if you are going to have a script manager with a script that searches for different objects in the game, do not put Destroy(this.gameObject); there, it will destroy the script manager itself. I repeat, do NOT destroy the script manager, it could be really bad for the game (and cause potential calls from annoyed team members who can’t figure out why the game  suddenly has an instant win condition)!

What should I have done here? Well for one thing, NOT DELETE THE SCRIPT MANAGER. Use the correct object from the iterator in the list, such as Destroy(berryPoint.gameObject); instead of Destroy(this.gameObject);. For another, I should have realized that the berry prefabs could in fact be added from the start with very little re-factoring. They didn’t even need to be deleted, they would just remain there until they are picked or broken, at which time they will be added correctly to the list. The scripts on the berries probably would not have interfered with anything either!

The pictures below show what the game looked like before and after the errors were fixed. The golden circle is the what you fly into to finish the game, and it should only spawn after all the children (the count should be displayed in the UI) have been found. But all these and more were controlled by the script manager which got deleted (I feel like I keep coming back to this one) by the broken code.

broken
What it actually looked like
working
What it should have looked like

So, to conclude, even though a series of temporary fixes can get things done, it might eventually lead to an inefficient code. While sometimes these are necessary, it is usually better to try to make the best code for the situation from the start (if possible), and at each modification. Don’t make a tank where a bike is enough and most assuredly, don’t make a tank which blows up in your face.

Targeting Algorithm for Hunter Enemy

One of the more important pieces of code I did this week is the hunter’s targeting (aggro) algorithm. The hunter is the first enemy we made (and the only one so far), and what it did earlier was chase the player character if it gets too close. But we wanted to expand on this and make it chase other targets too, based on a priority system.

Our other programmer, Sebastian was the one who initially coded the hunter movement and I didn’t want to mess up his code. So I implemented the targeting system in a way that it doesn’t alter his much. I did this by making two functions, invoked one after the other in every update loop of the hunter’s code. The first one generates a list of all targets in the nearby area and the second one decides which the hunter should follow, based on the priority system. Once this was done, all I had to do was replace the target from Sebastian’s code with the new one calculated by the two functions. Or in simpler terms, point the hunter to a new target! The two functions look like this.

void generateList()
{
targets.Clear();

targets.Add(GameObject.FindGameObjectWithTag("Player"));

GameObject[] berries = GameObject.FindGameObjectsWithTag("Berry");
GameObject[] child = GameObject.FindGameObjectsWithTag("Child");

if (berries.Length > 0)
{
foreach (GameObject entity in berries)
{
if (
(entity.GetComponent<powerUp>().currentState != powerUp.State.notPickedUp)
&& ((Vector3.Distance(entity.transform.position, transform.position)) < aggroDistance) ) { targets.Add(entity); } } } if (child.Length > 0)
{
foreach (GameObject entity in child)
{
if ((Vector3.Distance(entity.transform.position, transform.position)) < aggroDistance)
{
targets.Add(entity);
}
}
}
}
void setTarget()
{
foreach (GameObject prey in targets)
{
if (hunterTarget != null)
{
if (prey.tag == hunterTarget.tag)
{
if ((Vector3.Distance(prey.transform.position, transform.position)) < (Vector3.Distance(hunterTarget.transform.position, transform.position)))
{
hunterTarget = prey;
}
}

else if (prey.tag == "Berry")
{
hunterTarget = prey;
}
else if (prey.tag == "Child" && hunterTarget.tag != "Berry")
{
hunterTarget = prey;
}
else if (prey.tag == "Player" && (hunterTarget.tag != "Child" && hunterTarget.tag != "Berry"))
{
hunterTarget = prey;
}
}
else
{
hunterTarget = GameObject.FindGameObjectWithTag("Player");
}

}
}

Now, before I go into detail about the code, I would like to mention one thing. The list container in C#/Unity is awesome. It is easy to use and makes life easier! Why? Because of easy creation, iteration and element removal. The removal especially, because it can be done by specific element, rather than specific index (specific index is possible as well)! That said, I should also mention that I ran into some problems with it. For one thing, I had trouble adding elements to the list from a different script (even thought it was public static at the time) with this.gameObject (I have no idea why and if anyone knows, please let me know). For another, some of the objects actually get deleted through the course of the game, which leaves the hunter with a null target (the hunter either stood still or started moving erratically following this). The null target throws an exception as well. However, I did manage to fix this by adding an if statement to check for null targets and assign a temporary one when it happens.

Anyway, here’s how the code works. First, it adds the player to the list of targets. Then it scans the scene for all berries and children and adds them to the list if they are within the aggro radius defined by Sebastian. Berries are only added if they are not in notPickedUp state (see my previous blog post for details). The hunter script then sorts through the list and finds the target for the hunter to chase, based on the priority system. The priority goes: closer target of same type as current target > berry > children > player (as suggested by our designer). The target gets reset to the player if the hunter’s current target is destroyed. This is temporary and gets changed very quickly because both the list creation function and the target pick function are called each update. Once the target is picked, Sebastian’s code takes over and moves the hunter accordingly.

hunter-aggro
Light berry being used as a distraction

I also want to mention the list itself that I used to store the target GameObjects. It is currently declared as follows.

private List<GameObject> targets;

As I mentioned earlier, this was a public static variable at start (in fact, it was coded that way till I noticed something while writing this post). If it is public, any other function can edit it. Now, this was the original intended functionality, but I wasn’t able to implement it so it makes sense to have it as private, away from easy (unwanted) modifications. But I also removed the static part from it. Why was it there in the first place? Why did I remove it? Well the first one is simple. To make it easier to add and remove elements through other scripts. I wanted to add and remove berries and children to the list as they spawn (or get destroyed), directly from their behavior scripts. But then why remove it? This is because of an over-sight that would break the hunter behavior itself. If it were static, all hunters would have the same list. But they might have different targets nearby, so they may not follow the right one! For instance, one hunter may have the player nearby while another, a child. If both pairs are out of each others’ ranges, the function will still only calculate one target for both of them (the child because of the priority list)! And only one of the hunters will chase the target because the other one doesn’t have a target in range!

And here’s a video of the code in action.

Lighting System

One of the most core elements of our game is the lighting, as such, this post is about how we managed to implement it, and the part I’ve done in getting it working. This is how the lighting system currently works, although we plan to refine it a bit more later on.

The concept “Echo” requires a dark map where the player navigates around and finds stuff. To implement this in any way, a lighting system is necessary. We decided to do this by attaching a light source (controlled by scripts) to a berry, which the player can then pick up, carry around or throw. A problem here is that the 2D sprites don’t interact with lights, and you have to have a sprite diffuse shader on them for them to do so. The different states of the berry were implemented by my fellow programmer, Sebastian, through an internal state machine. I then coded the behaviour of those states: ‘not picked up’, ‘picked up’, ‘thrown’ and ‘broken’.

Starting State
At initialisation, the berry is set to ‘not picked up’. The code


if ((Vector3.Distance(target.position, transform.position) < distance) && ammo == false)
{
     currentState = State.pickedUp;
     ammo=true;
}

checks if the player is near the berry (with no ammo) and if they are, changes state to picked up. It is implemented like this and not through onCollision, because we didn’t want the berry to collide with the player and trigger the corresponding physics behaviors (like pushing the player off or dragging the player down). The player and berry are also on separate non-colliding layers.

notpicked
Not Picked Up State

Picked State
If the player gets close to the berry, it goes into ‘picked up’ state. The berries position now gets constantly updated to a point in front of the player by using a public transform that stores an empty (child) object in front of the player. The child object is just for convenience, since it can be re-positioned easily. The collider of the berry is disabled, to avoid the possibility of using the berry as a shield against the hunters. The light intensity and range are increased.

picked
Picked Up State

Thrown State
Once picked, the berry can be either thrown (towards the mouse cursor) or dropped. If the berry is thrown, it transitions to ‘thrown’ state and then to ‘broken’ state. If it is dropped, it immediately goes into ‘broken’ state. Since Sebastian did the code for the throw, I will not be going into the details of that.

thrown
Thrown State

Broken State
Once in the broken state, the berry’s light intensity is set to max, while the range increases at first, and then decreases to zero. The increasing and then decreasing is to create an effect like a flash of light illuminating the map before it goes out. At range zero, the object is destroyed (for not wasting memory). The code for this is as follows.


m_light.intensity = 8;

if (isIncreasing == true)
{
     m_light.range += 5f;
     if (m_light.range > 100)
     {
           isIncreasing = false;
     }
}
else if (isIncreasing == false)
{
     m_light.range -= 0.5f;
}

if (m_light.range <= 0)
{
     Destroy(this.gameObject);
}

fading
Broken State