Final Lab
Project Twinstick was a school project where we set out to create our own twin-stick shooter. We developed a game called Final Lab, where a laboratory has been taken over by robots. Two types of robots were created: one that moves along the ground and another drone robot that flies around the room, executing dash attacks on the player and shooting from a distance.
Project Info:
Team Project
Project Time: Year 1 Period 4 (2020)
Engine: Unity
Code Languages: C#
Design Patterns: Singleton & FlyWeight
Enemy Patrol
The enemies follow a predetermined patrol route. They have two statuses: they can be in the Patrol mode or Attack mode. In Attack mode, they follow the player, but since the player can move freely, the enemy must also determine the shortest route to the player and check if they can reach that route. Additionally, the enemy script works in conjunction with three different scripts: an Attack script, a Field of View (FOV) script, and a Health script.
private Transform GetNearestPatrolPoint()
{
float closedPatrolPointF = 100;
Transform closedPatrolPointTrans = null;
for (int i = 0; i < Patrol.Length; i++)
{
Vector3 dir = (Patrol[i].position - transform.position).normalized;
Ray patrolCheckLine = new Ray(transform.position, dir);
RaycastHit hit;
float distanceToShoot = Vector3.Distance(transform.position, Patrol[i].position);
Debug.DrawRay(patrolCheckLine.origin, patrolCheckLine.direction * distanceToShoot, Color.white, 2f);
if (Physics.Raycast(patrolCheckLine, out hit, distanceToShoot, fov.obstacleMask))
{
Debug.DrawRay(transform.position, patrolCheckLine.direction * hit.distance, Color.green, 2f);
Debug.Log(Patrol[i] + " Raakte iets " + hit.transform.name);
}
else
{
if (Vector3.Distance(transform.position, Patrol[i].position) <= closedPatrolPointF)
{
closedPatrolPointF = Vector3.Distance(transform.position, Patrol[i].position);
patrolStep = i;
closedPatrolPointTrans = Patrol[i];
}
}
}
if(closedPatrolPointTrans == null) //
{
Die();
return null;
}
return closedPatrolPointTrans;
}
Gets the nearest patrol point when the enemy spawns.
Save System
You don’t want to start over every time you launch the game, which is why we implemented a save system. The save system stores data such as stage, health, armor, bullets, main weapon, secondary weapon, and score. It is quite complex because the weapons are stored as strings, and a script determines which weapon was saved based on that string.
public void CheckForWeapon(string weaponName)
{
for (int i = 0; i < weapons.Length; i++)
{
if(weapons[i].name.ToLower() == weaponName.ToLower())
{
GameObject weaponClone = Instantiate(weapons[i]);
weaponClone.GetComponent().PickUp(Player);
}
}
}
Get the correct weapon from the save file
Grenade
Grenades are fun to use in-game, which is why we decided to include them. They deal less damage the further you are from the explosion. Initially, we considered adding grenade shrapnel, but we decided to stick with a Physics.OverlapSphere method. We also check if the player is visible, ensuring they aren’t behind a wall, as it wouldn’t be fair for them to take damage in that situation.
Weapons
The FireArm script is responsible for all weapons in the game. It primarily uses weapon data from the ScriptableObject associated with each weapon. The foundation of the script was created by Boas-Bas, but both of us have contributed so much that it has become a collaborative effort. Boas-Bas added functionalities for reloading, picking up weapons, and basic shooting, while I implemented weapon checks, weapon swapping, weapon pickups, gun jams, bullet tracers, and the shotgun script.
private void CheckGunJam()
{
Vector3 dir = ((new Vector3(transform.root.position.x, firepoint.position.y, transform.root.position.z) - firepoint.position + firepoint.forward/2)).normalized;
Ray shootLine = new Ray(firepoint.position + firepoint.forward/ 2 , dir);
RaycastHit hit;
Debug.DrawRay(firepoint.position + firepoint.forward /2, dir * Vector3.Distance(firepoint.position, new Vector3(transform.root.position.x, firepoint.position.y, transform.root.position.z)), Color.white);
if (Physics.Raycast(shootLine, out hit, Vector3.Distance(firepoint.position, new Vector3(transform.root.position.x, firepoint.position.y, transform.root.position.z)), this.weapon.layerMasks))
{
Debug.DrawLine(firepoint.position, hit.point, Color.red);
if (hit.point == null || hit.transform.CompareTag("Player"))
{
jamGun = false;
}
else if (hit.transform.gameObject.layer == 8)
{
jamGun = true;
}
}
else jamGun = false;
}
It ensures that you cannot shoot if your weapon's barrel is sticking through a wall (meaning you can't shoot from the other side of the wall).