For my final week of post-production, I started with making it so that the enemy would have to be in camera before it moves, since the red enemies were all stuck to the wall before I got there.
if (targetPosition.x >= 0 && targetPosition.x <= 1.2scale && targetPosition.y >= 0 && targetPosition.y <= 1.2scale)
Figure 1: In-camera view check code
I had already done the check in the teleport code. It was just a case of copying it, and testing it. I changed the upper limit to 1.2 to make them see the player a bit beyond the camera view.
Figure 2: In-camera view demonstration
Next, I thought a lot about how the blue layer part was going to go down. I had this idea that the enemies would be trapped behind walls as you go past them, and then you’d face the final boss. I even considered making the player sprites trapped in these walls, to make it look the final boss was imprisoning them.
Instead, I decided to make the blue move-through-wall ability out in the open and upon collecting it, it’d release lots of big enemies. These enemies would be of scale 4, bigger than all the rest, 5 of them red and one of them blue (which had more HP and stronger attacks). So it would be a great challenge unless you used the new broken attack I decided to pair up with the blue ability upon pressing E!
else if (Input.GetKeyDown(KeyCode.E) && playerScript.activeColor == playerScript.colorBlue)
{
for (int i = 0; i < 5; i++)
{
Fire("Player", playerScript.runSpeed * 5, playerScript.activeColor, transform.rotation * Vector2.up, Quaternion.identity, 0.5f, 1);
Fire("Player", playerScript.runSpeed * 5, playerScript.activeColor, transform.rotation * Vector2.right, Quaternion.AngleAxis(-90, Vector3.forward), 0.5f, 1);
Fire("Player", playerScript.runSpeed * 5, playerScript.activeColor, transform.rotation * Vector2.down, Quaternion.AngleAxis(180, Vector3.forward), 0.5f, 1);
Fire("Player", playerScript.runSpeed * 5, playerScript.activeColor, transform.rotation * Vector2.left, Quaternion.AngleAxis(90, Vector3.forward), 0.5f, 1);
}
}
Figure 3: The E Attack Code
Basically, the player fires out of every one of its vertices 5 times. It took quite a bit of trial and error to work out the correct rotations and vectors to use, but suddenly when I used AngleAxis, everything started to work!
Figure 4: The E-Attack and the Blue Enemy Layer
For the walls that would disappear, I gave them a different tag, and then accessed the deactivate method I made in the Game Manager to loop through all the walls and set them as inactive game objects.
I also made another teleport point, this time with typing “3” and the new co-ordinates.

Figure 5: The Final Bird’s-eye view of the level.
Finally, with the blue teleport ability acquired, it was time to design the final boss. Initially I made it size 5 and tried to make it have an attack come out of every one of its vertices. I didn’t really have enough time to figure out the attack, so I went with it using a similar attack to E coming out the single point, and left in the ability for it to fire when it does the spinning block, as it produced a cool wave effect. I varied the speeds of both the spin attack and non-spin attack to get the right stength in each.
if (scale < 5)
{
Fire("Enemy", enemyScript.runSpeed * 5, bulletColor, position, rotation, 0.5f, scale);
}
else
{
int speedModifier;
speedModifier = blockScript.blockOn ? 5 : 10;
for (int i = 0; i < 5; i++)
{
Fire("Enemy", enemyScript.runSpeed * speedModifier, bulletColor, position, rotation, 0.5f, 1);
waitCount = waitPoint;
}
waitTime = Random.Range(0f, 2f);
waitCount = 0;
}
Figure 6: Final Boss Attack
As you may have seen, I also changed the enemy update code, so that a certain number of attacks decrease them in size repeatedly, not just once. The boss fight was too easy at scale 5 if the E Attack is used, so I set it to scale 7.
void Update()
{
if (playerScript.activeColor != playerScript.colorWhite)
{
distance = Vector2.Distance(transform.position, Player.transform.position);
Vector2 direction = Player.transform.position - transform.position;
float scale = transform.localScale.x;
Vector3 targetPosition = mainCamera.WorldToViewportPoint(transform.position);
if (scale == 2)
{
transform.position = Vector2.MoveTowards(this.transform.position, Player.transform.position, -runSpeed * Time.deltaTime);
}
else if (targetPosition.x >= 0 && targetPosition.x <= 1.2*scale && targetPosition.y >= 0 && targetPosition.y <= 1.2*scale)
{
if ((deathPoint - deathCounter) <= (216 * (runSpeed - 1)) && scale == 7)
{
transform.localScale = new Vector3(6, 6, 1);
}
else if ((deathPoint - deathCounter) <= (125 * (runSpeed - 1)) && scale == 6)
{
transform.localScale = new Vector3(5, 5, 1);
}
else if ((deathPoint - deathCounter) <= (64 * (runSpeed - 1)) && scale == 5)
{
transform.localScale = new Vector3(4, 4, 1);
}
else if ((deathPoint - deathCounter) <= (27 * (runSpeed - 1)) && scale == 4)
{
transform.localScale = new Vector3(3, 3, 1);
}
else if ((deathPoint - deathCounter) <= (8 * (runSpeed - 1)) && scale == 3)
{
transform.localScale = new Vector3(2, 2, 1);
}
transform.position = Vector2.MoveTowards(this.transform.position, Player.transform.position, runSpeed * Time.deltaTime);
}
}
}
Figure 7: Final Enemy Update Code
Upon defeating the boss, I made it spawn a yellow ability collectible, which would take you to the game win screen. I also made another checkpoint to reach the boss.
Figure 8: Final Boss
With the development completed, I feel satisfied. There’s lots of more I could have done to make it more engaging, and look better visually, but it takes time. This was my first time on Unity. I’m sure I can accomplish more next time with the Unity engine knowledge I’ve acquired.
I also played it very safe with coding this time, because I was learning C# and the engine; perhaps I could try to master the arctan2-related maths next time and not just do the odd line with trial and error.
Figure 9: Post-Production Summary Video
Finally, I put together my video presentation. That’s all for this project!
Figure 10: Final Project Presentation Video