For week 8, I started making walls to obstruct the enemy and add more detail to the scene. Thinking back to the plan, I also wanted the player to be safe while in ‘white mode’, so I constructed a white wall, where no enemies could pass. This white wall would be a vertical inner wall, while the outer walls would be magenta, to distinguish them from the Green, Red and Blue enemies. The background or outer floor was a dark grey, while the inner floor was a bit lighter with magenta mixed in.

Figure 1: An approximate recreation of my initial level design in Unity.
As for the ability of the white character to move through white walls (both of which actually look grey), I googled and looked into disabling the sprite renderer of the wall while entering its collider, and re-enabling it while exiting its collider. It took a lot of figuring out for what to do next, but eventually I settled for turning the player collider trigger on when going through the same coloured walls. I found an example of the correct code on the Unity forums. This behaviour would be same for the enemies too.
So to summarize the mechanics, the sprite renderer of the wall gets turned off in the OnCollisionEnter2D event, and gets turned on in the OnTriggerExit2D event. The opposite happens with the collider triggers for the objects entering/exiting the walls, as they get turned on when entering and get turned off when exiting.
private SpriteRenderer render;
private GameObject player;
private PolygonCollider2D playerCollider;
void Start()
{
render = GetComponent<SpriteRenderer>();
player = GameObject.FindWithTag("Player");
playerCollider = player.GetComponent<PolygonCollider2D>();
}
void OnCollisionEnter2D(Collision2D collision)
{
if (gameObject.layer == LayerMask.NameToLayer("White Wall") && collision.gameObject.layer == LayerMask.NameToLayer("IgnorePlayer"))
{
render.enabled = false;
playerCollider.isTrigger = true;
}
}
void OnTriggerExit2D(Collider2D other)
{
if (gameObject.layer == LayerMask.NameToLayer("White Wall") && other.gameObject.layer == LayerMask.NameToLayer("IgnorePlayer"))
{
render.enabled = true;
playerCollider.isTrigger = false;
}
}
Figure 2: Pass through walls code.
I also updated the player sprite in preparation for when I implement shooting, my chosen type of attack.
Figure 3: Passing through walls in my project in Unity.
For the next stage, I wanted to pass instructions to the player on how to play the game. I spent hours trying to work out how I could overlay additional text on the screen like I did with Game Over but while actually playing the game. I couldn’t figure it out, so I settled for a decent alternative: I would save images with the text on them and seemlessly add them onto the walls as separate game objects that I can gradually enable on the screen.

Figure 4: The starting message for my project in Unity.
The other main ability I wanted the player to have was teleporting. Reflecting on the mechanics, I wanted a script to find a teleport location within camera view that corresponded to the player colour. It was relatively simple to get working in its most basic form, since you just reassign the value for the position attribute of the player object on pressing the trigger button (T in this case). It gets a bit complicated when you add in the checks, such as finding a teleporter object in camera view, which requires looping through all teleporters and then checking the co-ordinates don’t exceed the camera area.
I also wanted one side of the teleporter to be visible and the other side not. A bug quickly arose where it would cycle through on-off, off-on, off-off, on-on states. I had to think through making a check that always treated the teleporter objects in pairs, so if one was on, the other one was off. It was an interesting challenge.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TeleportScript : MonoBehaviour
{
// Declare variables.
public Camera mainCamera;
private GameObject player;
private PlayerScript playerScript;
[SerializeField] private GameObject bigGreenEnemy;
private bool hasSpawned = false;
// Assign variables.
void Start ()
{
player = GameObject.Find("Player");
playerScript = player.GetComponent<PlayerScript>();
}
void Update ()
{
// Upon pressing down T, finds the nearest visible teleport point, and teleports to it. After, it disables the forward teleport circle and reveals the backward one.
if (Input.GetKeyDown(KeyCode.T))
{
GameObject[] teleporters = GameObject.FindGameObjectsWithTag("Teleporter");
SpriteRenderer spriteRenderer1 = null;
SpriteRenderer spriteRenderer2 = null;
Vector3 teleporterLocation = new Vector3(0,0,0);
foreach (GameObject teleporter in teleporters)
{
bool whiteCheck = player.layer == LayerMask.NameToLayer("IgnorePlayer") && teleporter.layer == LayerMask.NameToLayer("White Teleporter");
bool greenCheck = player.layer == LayerMask.NameToLayer("Player") && teleporter.layer == LayerMask.NameToLayer("Green Teleporter") && playerScript.hasAbility["GreenTeleport"];
if (whiteCheck || greenCheck)
{
Vector3 targetPosition = mainCamera.WorldToViewportPoint(teleporter.transform.position);
if (targetPosition.x >= 0 && targetPosition.x <= 1 && targetPosition.y >= 0 && targetPosition.y <= 1)
{
SpriteRenderer spriteRenderer = teleporter.GetComponent<SpriteRenderer>();
if (spriteRenderer.enabled)
{
teleporterLocation = teleporter.transform.position;
spriteRenderer1 = spriteRenderer;
}
else
{
spriteRenderer2 = spriteRenderer;
}
}
}
}
if (spriteRenderer1 != null && spriteRenderer2 != null && !teleporterLocation.Equals(default))
{
transform.position = teleporterLocation;
spriteRenderer1.enabled = false;
spriteRenderer2.enabled = true;
}
else
{
StartCoroutine(playerScript.Flicker(player));
}
}
}
}
Figure 5: Teleport Script.
I added a dictionary to the player script that would maintain the abilities the player has, such as the teleport ability, and also made the player flicker when the teleport ability was unable to be used. At the same time, I did this for the case of when a player isn’t allowed to change colour while moving through walls.
Figure 6: Teleporting demonstration in my Unity project.
The final feature this week I was thinking about was how the mechanic of collecting abilities would work. I settled on having game objects that would rotate through the y-axis and once the player collided with them, the objects would essentially disappear and the player would now possess the ability. I also had lots of thoughts back and forth on the logic of where things should be positioned.
private float rotationSpeed = 200f;
void Update()
{
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
}
Figure 7: Rotation code for the Collectible script.
The outcome of my thoughts was that the player would start in the safe, white mode, where enemies couldn’t hurt it, then pick up the green move-through-wall ability to the left, which would then make the player go into the green mode. When in the green mode, the player could attack and be attacked. Upon defeating a big green enemy, it could collect the green teleport ability, which would then allow it to teleport to the second part of the level.
void OnCollisionEnter2D(Collision2D collision)
{
// Player upgrade events (via collectibles).
if (collision.gameObject.CompareTag("Collectible"))
{
if (collision.gameObject == greenWallAbility)
{
Destroy(greenWallAbility);
hasAbility["GreenWall"] = true;
colorChange = new Dictionary<Color, Color>
{
{ colorWhite, colorGreen },
{ colorGreen, colorWhite },
};
activeColor = colorGreen;
render.color = colorGreen;
gameObject.layer = LayerMask.NameToLayer("Player");
runSpeed = 6f;
typeR.SetActive(true);
typeSpace.SetActive(true);
whiteSafe.SetActive(true);
return;
}
}
}
Figure 8: Collectible ability code in the Player script.
My plan would be that you always collect the move-through-wall ability before collecting the teleport ability.
Figure 9: Collecting Abilities demonstration in my Unity project.
Next week, I will go over implementing shooting and damage mechanics, making some custom methods to make the code look cleaner, and finally, summarising my production so far.