Project Status
Project Type
Project Duration
Software Used
Languages Used
Finish
University project
~3 months
Unity
C#
Please feel free to play and let me know your opinions. Download the installer and play or the source code to see the entire project!
Play the game lower quality in HTML5
"The Blind Forest" is a 2.5D action-platformed with a focus on mastering of new abilities and exploration into a vast and challenging world. Battle ferocious foes on your journey into the world with the tight and fluid gameplay to get the stone keys and unlock the gates to the temples. Fight and defeat the guardian to retrieve the last key and open the secret temple.
My responsibilities extended more that a Lead programmer I was the leader of the entire project and team many different position like game designer, producer and director took place in order to make this project possible with a small team.
Some of my responsibilities made in the project was but not limited:
Art: UI, Logo
Designer: UI, Level, Light and post process, some effects like orbs, slashes, jumps, deaths, runing, etc...
Game design: Lead game designer of the team, some main designs player, enemies, feature, obstacles, core gameplay system.
Game developer: Lead game developer implement most of the the programing and features of the game such as all the player, enemies systems, some of the individual enemies, almost all of the UI implementation, game manager,helper code and general code that helps the team works more efficient like a animation, effects,sound data table, surrounding sound implementation, UI easy inputs, etc..
When you're a team of only two developers, you end up wearing a lot of hats (and I do love hats!).
For more detail information about the game design the Design and the Post Mortem documents are here!
Laser-the player will be able to shoot a laser beam that deals heavy damage to the enemy.
Wall jump - Allows the player to jump between walls and get to the higher platform. Allows the player up to a second jump to go higher on the vertical level. Player will jump from any wall surface to the opposite direction if the player does not jump but stays in the wall direction he will slide and if the player does not jump and do not face on the direction of the wall, he will fall to the ground.
Attacks-Allows the player to damage the enemies and breakable objects. The player will perform a series of multiple attack animations each time he presses the attack button.
Spider-This spider will continuously look at the player and fire at the player's position.
Bats - The bat will patrol a given space in the air. If the player comes near, the bat will swoop down and attempt to hit the player. It will most likely be flying around with other bats in a cluster.
Orc- Patrol switching a given area. When the player is within range, it will slowly but heavily swing at the player doing a very large amount of damage.
Slime- will jump towards the player in the attempt to slam on top of them.
Boss – will beat the end level of the game. It has clearer transitions to the next stages of the boss. Use various particle effect/ other visual elements to help cover hard transitions between stages.Consists of 3 stage attacks from the boss. 1 stage is a constantly dashing attack, 2 stage is a slamming jumping attack and the 3 level consist of a bullet hell projectile attack.
Platform--Objects that the player can collide and move with.
The object must move the player while the play can still control themselves.
Spike damage characters that collide with it.
Switch-Flip certain switches be pressed to proceed.
Interactive/Breakable Environment - Objects that the player can interact with that can be destroyed.Must have a before and after state and an animation/effect related to its destruction.
Moving Platforms- These platforms move back and forth in a specified area.
Gate door- is the final door for each level. A key stone that can be found on the level is required to open the gate.
The enemy system contain the escencial code for the enemies health, hurt funtions, references,etc.. Each enemy has his own movement AI pathern, detect the player and his own way of attacking the player. Attacking will make triger a hit reaction for the player and it will push back the player.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
//Gamemanager.Instance.animatorFunctionsScript.PlayDieEmitParticles();
//AnimatorFunctions.Instance.PlayDieEmitParticles();
//mesh = GetComponent<MeshRenderer>();
rb = GetComponent<Rigidbody>();
// mesh = GetComponent<MeshRenderer>();
TryGetComponent<PatrolEnemy>(out patrolScript);
TryGetComponent<TurretEnemy>(out turretScript);
TryGetComponent<FlyingEnemy>(out flyScript);
TryGetComponent<SlamEnemy>(out slamScript);
TryGetComponent<ShootScript>(out shootScript);
TryGetComponent<BreakableObject>(out breakScript);
TryGetComponent<BossMain>(out bossScript); HUDScript.pauseGame += Pause;
}
// Update is called once per frame
void Update()
{
if (!paused)
{
if (transform.position.z != 0)
{
transform.position.Set(transform.position.x, transform.position.y, 0);
}
if (runningFrozen > 0)
{
runningFrozen -= Time.deltaTime;
}
else {
frozen = false;
if (patrolScript)
{
patrolScript.Pause(0);
}
else if (flyScript)
{
flyScript.Pause(0);
if (shootScript && flyScript.shootScriptOnStart)
{
shootScript.enabled = true;
}
}
else if (slamScript)
{
slamScript.Pause(0);
}
else if (turretScript)
{
turretScript.Pause(0);
if (shootScript && turretScript.shootScriptOnStart)
{
shootScript.enabled = true;
}
}
else if (bossScript) {
bossScript.Pause(0);
}
} if (frozen) {
if (patrolScript)
{
//patrolScript.Pause(1);
patrolScript.modelAnim.SetTrigger("Dizzy");
}
else if (flyScript)
{
flyScript.Pause(1);
}
else if (slamScript)
{
slamScript.Pause(1);
if (slamScript.GetComponent<ParticleSystem>().isPaused)
{
slamScript.GetComponent<ParticleSystem>().Play();
}
}
else if (turretScript)
{
turretScript.Pause(1);
}
else if (bossScript) {
bossScript.Pause(1);
}
if (shootScript) {
shootScript.enabled = false;
}
}
//agent.destination = player.transform.position;
Health();
AfterDamage();
}
//if (Input.GetButtonDown("Fire1"))
//{
// patrolScript.modelAnim.SetTrigger("Dizzy");
//}
}
Orc
The orc enemy move by two patrol point placed in the map and has some random animation between the movement. If he detect the player he will make a roar animation and his speed will increase, will be at Agro state and it will follow the player and attack him until he kill him or the player go to far away from his area of movement. Is the player is behind the orc he will be detected. The orc decetion is base of a tow collision sphere one for the front and one for the back, and a collision line that will detect if the player is in range to get attack by the orc.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
enemyScript = GetComponent<EnemyAI>();
enemyScript.parent = parent;
modelAnim = model.GetComponent<Animator>(); if (faceLeft) //
{ //
rb.velocity = Vector2.left * speed; //
targetSide = -1; //
} //Set the inital direction of movement
else //
{ //
rb.velocity = Vector2.right * speed; //
targetSide = 1; //
} // lookAroundTimer = Random.Range(10f, 20f);//Set idle delay
}
// Update is called once per frame
void Update()
{
if (!paused) { if (chasePlayer && !isWaiting)
{
if (prevFacing != Gamemanager.Instance.player.transform.position.x < transform.position.x) //If the player is currently behind the enemy
{ //
////debug.log("faceLeft != prevFacing"); //
chaseTimer = chaseDelay; //
} //
else
{
if (chaseTimer <= 0) //
{ //
faceLeft = Gamemanager.Instance.player.transform.position.x < transform.position.x ? true : false; //Set the new facing direction
} //
chaseTimer -= Time.deltaTime; //
}
speed = chasingSpeed;//Set to running speed
}
else
{
speed = basicSpeed;//Walking speed
}
if (enemyScript.afterDamageTimer <= 0 && afterAttackTimer <= 0 && !isWaiting) //
{ //Set velocity based on direction
rb.velocity = (faceLeft ? Vector3.left : Vector3.right) * speed; //
} // if (lookAroundTimer <= 0) //Play idle animation
{ //
if (!chasePlayer && enemyScript.afterDamageTimer <= 0 && afterAttackTimer <= 0) //If not in the middle of fighting the player
{ //
rb.velocity = Vector3.zero; //Stop enemy
int trigger = Random.Range(1, 3); //Random choice to scream or look around
if (trigger == 1) //
{ //
modelAnim.SetTrigger("Look"); //
} //
else //
{ //
modelAnim.SetTrigger("Scream"); //
} //
lookAroundTimer = Random.Range(10f, 20f); //Set the timer
} //
} //
else //
{ //
lookAroundTimer -= Time.deltaTime; //Decrement the timer if > 0
} // if (faceLeft) //Set the models and triggers
{ //depending on facing direction
face.transform.localPosition = (Vector3.left + Vector3.up) * .5f; //
model.transform.localRotation = Quaternion.Euler(Vector3.down * 90); //
model.transform.localPosition = Vector3.right * .04f + Vector3.down; //
chaseDetection.transform.localPosition = Vector3.left * 1.25f; //
} //
else //
{ //
face.transform.localPosition = (Vector3.right + Vector3.up) * .5f; //
model.transform.localRotation = Quaternion.Euler(Vector3.up * 90); //
model.transform.localPosition = Vector3.left * .04f + Vector3.down; //
chaseDetection.transform.localPosition = Vector3.right * 1.25f; //
} //
Attack();
AfterAttack();
ChooseAnimation();
prevFacing = Gamemanager.Instance.player.transform.position.x < transform.position.x;
//if (Input.GetButtonDown("Fire1"))
//{
// modelAnim.SetTrigger("Dizzy");
//}
}
}
Bat
The bat have an are of movement base of a box collision placed in the world. It takes a random point inside the are and move there, then it will calculate another point to go there it will not be able to go outside the area and it will move until detect the player. On player detection it will proced to follow the player and by each attack stopt for a second and then attack, then it will find another random point to go there and proced to trying to detect the player againg. The attacks are base of two actions shotting and biting it will take the decesion wich attack to triger base of the distan betewn him and the player.
Code
See this entire code!
public class FlyingEnemy : MonoBehaviour
{
[HideInInspector]
public Rigidbody rb;
[SerializeField] GameObject model;
[SerializeField] GameObject modelBody;
ShootScript shootScript;
//float rotateBody = -50;
[HideInInspector] public Animator modelAnim; [Header("Basic properties")]
public float speed = 5;
public int contactDamage = 10;
public bool shootTowardsPlayer;
public bool paused; //Space for birds to freely fly in
BoxCollider flySpace; //
//Variables used throughout the program
//
Vector3 target;
Vector3 otherSide;
//Distance from target to bird that the bird considers "Complete"
float threshold = .25f; float distanceToTarget; [Space]
public FlyingStage pathing;
[Space]
[Header("Player chasing")]
[SerializeField] bool canChasePlayer;
[SerializeField] float playerFollowingTime;
float playerRunningTimer;
[Range(0, 100)]
[SerializeField] int percentChanceToChasePlayer = 30;
[SerializeField] Vector3 pivotAdjust;
[Space]
[Header("Vertical Patrol")]
[SerializeField] float verticalPatrolDistance;
public bool shootScriptOnStart; [Space]
[Header("Attacking")]
bool attacking;
[SerializeField] float attackDistance;
[SerializeField] float attackDelay;
float attackTimer;
[SerializeField] float extraDetection;
// Start is called before the first frame update
void Start()
{
flySpace = GetComponentInParent<BoxCollider>();
rb = GetComponent<Rigidbody>();
modelAnim = model.GetComponent<Animator>(); //if (flySpace)
//{
//outOfBoundsDistance = Mathf.Sqrt(((flySpace.size.x / 2) * (flySpace.size.x / 2)) + ((flySpace.size.y / 2) * (flySpace.size.y / 2)));
//} if (pathing != FlyingStage.VerticalPatrol)
{
NewTarget();
BoundsCheck();
}
else
{
target = new Vector2(transform.position.x, transform.position.y + verticalPatrolDistance / 2);
otherSide = target;
otherSide.Set(otherSide.x, otherSide.y - verticalPatrolDistance, otherSide.z);
transform.up = Vector3.up;
rb.velocity = transform.up * speed;
}
shootScript = GetComponent<ShootScript>();
shootScriptOnStart = shootScript.enabled;
} // Update is called once per frame
void Update()
{
if (!paused)
{
Debug.DrawRay(transform.position, (Gamemanager.Instance.player.transform.position + pivotAdjust - transform.position).normalized * (shootScript.projectileLifeTime*shootScript.bulletSpeed + extraDetection), Color.green);
if (Vector3.Distance(transform.position, Gamemanager.Instance.player.transform.position + pivotAdjust) < (shootScript.projectileLifeTime * shootScript.bulletSpeed + extraDetection))
{
shootScript.canShoot = true;
if (shootScript.runningTimer < 1)
{
modelAnim.SetTrigger("Attack02");
rb.velocity = Vector3.zero;
attacking = true;
if (shootTowardsPlayer)
{
transform.up = Gamemanager.Instance.player.transform.position + pivotAdjust - transform.position;
transform.Rotate((transform.up.x < 0 ? Vector3.up : Vector3.down) * 90);
}
}
else
{
attacking = false;
transform.up = target - transform.position;
transform.Rotate((transform.up.x < 0 ? Vector3.up : Vector3.down) * 90);
}
}
else
{
shootScript.canShoot = false;
//Check if player in range of melee attack
if (pathing == FlyingStage.PlayerTarget && !attacking && attackTimer <= 0)
{
if (Physics.Raycast(transform.position, Gamemanager.Instance.player.transform.position + pivotAdjust - transform.position, out RaycastHit hit, attackDistance))
{
if (hit.collider.CompareTag("Player"))
{
modelAnim.SetTrigger("Attack01");
StartCoroutine(Attack(hit.collider));
attacking = true;
attackTimer = attackDelay;
}
}
}
} if (pathing == FlyingStage.VerticalPatrol)
{
if (!attacking)
{
modelAnim.SetBool("Forward", false);
}
Debug.DrawLine(target, transform.position, Color.red);
Debug.DrawLine(otherSide, transform.position, Color.red);
if (transform.position.x < Gamemanager.Instance.player.transform.position.x)
{
transform.rotation = Quaternion.Euler(transform.rotation.x, -90, transform.rotation.z);
}
else
{
transform.rotation = Quaternion.Euler(transform.rotation.x, 90, transform.rotation.z);
}
distanceToTarget = Vector3.Distance(target, transform.position);
if (distanceToTarget <= threshold)
{
verticalPatrolDistance *= -1;
NewTarget();
transform.up = Vector3.up;
} }
else
{
distanceToTarget = Vector3.Distance(target, transform.position);//Update distance to target
if (!attacking)
{
modelAnim.SetBool("Forward", true);
}
if (distanceToTarget <= threshold)//Target reached
{
if (playerRunningTimer <= 0 && canChasePlayer)// If can chase player
{
if (Random.Range(0, 101) < percentChanceToChasePlayer)//Random chance to chase player
{
pathing = FlyingStage.PlayerTarget;
playerRunningTimer = playerFollowingTime;
}
else
{
pathing = FlyingStage.RandomTarget;
}
}
if (!attacking)
{
NewTarget();//Set new target now that original has been reached
}
distanceToTarget = Vector3.Distance(target, transform.position);//Update distance
} if (playerRunningTimer > 0) //
{ //If still chasing the player,
playerRunningTimer -= Time.deltaTime; //lower timer,
NewTarget(); //set target to updated player location
} //
else //
{ //
if (pathing == FlyingStage.PlayerTarget) //
{ //
pathing = FlyingStage.RandomTarget; //
NewTarget(); //if the timer ran out, set a random target
} //
} // if (attackTimer > 0) {
attackTimer -= Time.deltaTime;
} BoundsCheck();
if (attacking) //
{ //
rb.velocity = Vector3.zero; //
} //
else { //
rb.velocity = transform.up * speed; //
} //
}
}
}
Spider
The spider do not have movement it aim at the player base of it position and take taha to calculate the rotation at the rigth angle and shoot the player. The bulletshave some spread mechanic making less eassy for the bullet to hit the player.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
playerPos = Gamemanager.Instance.player.transform;
modelAnim = model.GetComponent<Animator>();
rb = GetComponent<Rigidbody>();
shootScript = GetComponent<ShootScript>();
shootScriptOnStart = shootScript.enabled;
}
// Update is called once per frame
void Update()
{
if (!paused)
{
//**Note**
//Many of the transform.up operations require the *-1 for the sprite. Depending on the asset used, change/remove this value so the sprite
//points correctly and the projectile moves in the right direction rb.velocity = Vector3.zero;
//Change rotation if spider is a dynamic spider
if (isDynamic)
{
//Debug.DrawRay(transform.position, -1 * transform.up.normalized * ((shootScript.bulletSpeed * shootScript.projectileLifeTime) + extraDetection), Color.green);
if (Physics.Raycast(transform.position, (playerPos.position + pivotAdjust - transform.position), out RaycastHit hit, (shootScript.bulletSpeed * shootScript.projectileLifeTime) + extraDetection))
{
if (hit.collider.CompareTag("Player"))
{
shootScript.enabled = true; prevDir = transform.up;
transform.up = new Vector3(transform.position.x - playerPos.position.x, transform.position.y - playerPos.position.y) - (Vector3)pivotAdjust; if (prevDir != transform.up)
{
////debug.log("Turn true");
modelAnim.SetTrigger("Turn");
}
else
{
modelAnim.SetTrigger("Idle");
}
}
else
{
shootScript.enabled = false;
}
}
else
{
shootScript.enabled = false;
}
}
}
}
Slime
The slime will stay at the same location until the player is detected. On player detection it will calculate how to move base of the distance between him and the player. Base of the distqance if the player is to closed he will move or do lgith attacks until he make enough distanse away from the player to make a slam heavy attack that will triger a particle effect. The attacks are base of distance the more distance betewn the him and the player the more he can impulse it self more damage will make to the player on hit.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
particles = GetComponent<ParticleSystem>();
modelAnim = model.GetComponent<Animator>();
particles.startLifetime = slammingTime;
particles.startSpeed = slamTriggerSize / slammingTime * 1.2f;
slamTriggerRadius = slamTrigger.radius;
lookAroundTimer = Random.Range(5f, 10f);
}
// Update is called once per frame
void Update()
{
if (!paused)
{
if (lookAroundTimer > 0) //
{ //
lookAroundTimer -= Time.deltaTime; //
} //
else //Timer to player idle animation
{ //
if (!slamming && canJump) //
{ //
modelAnim.SetTrigger("Look"); //
lookAroundTimer = Random.Range(5f, 10f); //
} //
} // if (!IsStuck()) //If the enemy isnt stuck,
{ //
if (isInRange()) //and the player is in range of the enemy,
{ //
if (canJump) //and the enemy can jump,
{ //
Launch(); //then jump to the player
} //
} //
} // if (runningDelay > 0) //
{ //
runningDelay -= Time.deltaTime; //
} //
else //
{ //Timer between jumps,
if (!slamming && !inAir) //detects if enemy is still in the sequence
{ //
canJump = true; //
} //
} // if (transform.position.x < Gamemanager.Instance.player.transform.position.x) //
{ //
faceLeft = false; //
model.transform.localRotation = Quaternion.Euler(Vector3.up * 90); //
} //Direction check
else //
{ //
faceLeft = true; //
model.transform.localRotation = Quaternion.Euler(Vector3.down * 90); //
} // EnoughSpeed();
Slamming();
pausedVel = rb.velocity;
}
}
Boss
The boss is base of all enemies in the game it has 3 stages each one more dificult and difrent gameplay thatn the other.
Code
void Start()
{
rb = GetComponent<Rigidbody>();
enemyScript = GetComponent<EnemyAI>();
maxHP = enemyScript.HP;
actColRadius = activeCollision.radius;
spiderAnim = spider.GetComponent<Animator>();
orcAnim = orc.GetComponent<Animator>();
batAnim = bat.GetComponent<Animator>();
for (int i = 0; i < guns.Count; i++) {
guns[i].gameObject.transform.RotateAround(gunsPivot.position, Vector3.right, 60*(i+1)); }
particles = GetComponent<ParticleSystem>();
particles.startLifetime = slamTime;
particles.startSpeed = slamRadius / slamTime * 1.1f / 2;
key.SetActive(false);
}
// Update is called once per frame
void Update()
{
if (!paused)
{
StageCheck();
if (dazed)
{
if (runningDazedTimer <= 0)
{
dazed = false;
}
else
{
runningDazedTimer -= Time.deltaTime;
}
}
if (!finalStage)
{
if (rb.velocity != Vector3.zero)
{
if (!enemyScript.sfxAudioSource.isPlaying)
{
enemyScript.sfxAudioSource.PlayOneShot(walking,2f);
}
if (spiderAnim.enabled)
{
spiderAnim.SetBool("Moving", true);
}
}
else
{
if (!dazed)
{
if (spiderAnim.enabled)
{
spiderAnim.SetBool("Moving", false);
}
}
}
}
if (prevStage == stage && !isTransition)
{
if (stage == Stage.Dashing)
{ if (!dazed && canDash)
{
rb.velocity = new Vector3(dashSpeed * (faceLeft ? -1 : 1), rb.velocity.y, 0);
}
}
else if (stage == Stage.Slamming)
{
if (!inAir && !canJump)
{
if (runningSlamTime <= 0)
{
canJump = true;
}
else
{
runningSlamTime -= Time.deltaTime;
}
}
if (canJump)
{
if (Vector3.Distance(transform.position, Gamemanager.Instance.player.transform.position + pivotAdjust) > 10)
{
Launch();
}
else {
Launch(55,flightTime,true);
}
}
}
else
{
if (runningHealthIncrease > 0) {
runningHealthIncrease -= Time.deltaTime;
}
else
{
if (enemyScript.HP < maxHP*.3f)
{
runningHealthIncrease = 1;
enemyScript.HP += bulletHellHealthIncrease;
}
}
if (!isInPattern) {
isInPattern = true;
if (wheelPattern)
{
StartCoroutine(WheelPattern());
}
else if (jigglePattern)
{
StartCoroutine(JigglePattern());
}
else if (heatSeeking) {
StartCoroutine(HeatSeeking());
}
}
}
}
else if(prevStage != stage)
{
//Debug.Break();
Transition();
}
prevStage = stage;
pausedVel = rb.velocity;
}
}
Feel free to see this entire code!
Moving platform
Moving platform will lock the player root to his location and transfer the relative position, it will move by an array of vector of reference location placed in the world.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
if (points.Length > 0)
{
currentTarget = points[0];
}
tolerance = speed/100;
HUDScript.pauseGame += Pause;
}
// Update is called once per frame
void FixedUpdate()
{
if (!paused)
{
if (transform.position != currentTarget)
{
MovePlatform();
}
else
{
UpdateTarget();
}
}
}
private void MovePlatform()
{
Vector3 heading = currentTarget - transform.position;
transform.position += (heading / heading.magnitude) * speed * Time.deltaTime; if (heading.magnitude < tolerance)
{
transform.position = currentTarget;
delayStart = Time.time;
}
} private void UpdateTarget()
{
if (automatic)
{
if (Time.time - delayStart > delayTime)
{
NextPlatform();
}
}
} public void NextPlatform()
{
pointNumber++;
if (pointNumber >= points.Length)
{
pointNumber = 0;
}
currentTarget = points[pointNumber];
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player") || other.name.Contains("Slam Enemy"))
{
//Gamemanager.Instance.GetComponent<Animator>().enabled = false;
other.transform.parent = transform;
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player") || other.name.Contains("Slam Enemy"))
{
//Gamemanager.Instance.GetComponent<Animator>().enabled = true;
other.transform.parent = null;
}
}
private void OnDestroy()
{
HUDScript.pauseGame -= Pause;
}
void Pause() {
paused = !paused;
}
Switches
The switches walls will be deactivade when the assinged swicth is triger.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
leverSound = GetComponent<AudioSource>();
HUDScript.pauseGame += Pause;
}
// Update is called once per frame
void Update()
{
if (!paused)
{
if (playerContact)
{
if (canFlip)
{
if (!triggerOnContact)
{
if (Input.GetKeyDown(KeyCode.E) || (Input.GetButtonDown("Fire1")))
{
flipped = !flipped;
leverSound.Play();
GameObject newparticule = (GameObject)Instantiate(particuleExplode, transform.position + new Vector3(Random.Range(-2, 2), Random.Range(0, 3), -10), Quaternion.identity);
Destroy(newparticule, 1);
}
}
}
}
if (prevFlip != flipped)
{
flipWasNotEqual = true;
if (!triggerOnContact)
{
if (flipped)
{
anim.SetTrigger("FlipOn");
}
else
{
anim.SetTrigger("FlipOff");
}
}
canFlip = false;
}
else
{
flipWasNotEqual = false;
}
prevFlip = flipped;
}
}
Spikes
Spikes will damage the player and trigger the hit function then it will turn the collision for a couple seconds.
Code
See this entire code!
public class SpikeController : MonoBehaviour
{
[SerializeField] float stayHitTimer = 2;
float runningTimer;
public int damage = 25;
public void OnTriggerEnter(Collider col)
{
//if (col.gameObject.name == "Player")
if (col.gameObject == Gamemanager.Instance.player)
{
Gamemanager.Instance.playerScript.Hurt(damage,0);
runningTimer = stayHitTimer;
}
}
public void OnTriggerStay(Collider col)
{
//if (col.gameObject.name == "Player")
if (!Gamemanager.Instance.playerScript.paused)
{
if (col.gameObject == Gamemanager.Instance.player)
{
runningTimer -= Time.deltaTime;
if (runningTimer <= 0)
{
runningTimer = stayHitTimer;
Gamemanager.Instance.playerScript.Hurt(damage, 0);
}
}
}
}
Boxes
The boxes will breaks at hit and will drops randomly ether health orbs or mana orbs.
Code
See this entire code!
// Start is called before the first frame update
void Start()
{
enemyScript = GetComponent<EnemyAI>();
//maxHP = enemyScript.HP;
box = GetComponent<BoxCollider>();
rb = GetComponent<Rigidbody>();
}
private void Update()
{
paused = enemyScript.paused;
if (paused) {
if (!Gamemanager.Instance.playerScript.paused) {
enemyScript.Pause();
//Debug.Break();
}
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Enemy") && gameObject.layer == 0) {
enemyScript.takeDamage(100);
}
}
public void ChangeShape() {
mesh.enabled = false;
box.size = new Vector3(box.size.x, .1f, box.size.z);
broken.SetActive(true);
gameObject.layer = 5;
Destroy(gameObject,2);
}
void Start()
{
Time.timeScale = 1;
gameOverAlpha = gameOver.GetComponent<RawImage>().color.a;
if (bossEnemyScript)
{
bossMaxHP = bossEnemyScript.HP;
}
}
void Update()
{
SetHealthBar();
SetManaBar();
CheckPause();
CheckEnding();
} public void SetHealthBar()
{
healthBar.value = Gamemanager.Instance.playerScript.HP;
if (bossHealth)
{
if (bossEnemyScript && bossEnemyScript.HP > 0)
{
bossHealth.normalizedValue = bossEnemyScript.HP / bossMaxHP;
}
else {
if (bossHealth)
{
bossHealth.value = 0;
bossHealth.fillRect.sizeDelta = Vector2.zero;
bossHealth.fillRect.position.Set(-1000, -1000, 0);
}
if (!bossEnemyScript && bossHealthObj.activeSelf) {
bossHealthObj.SetActive(false);
}
}
}
} public void SetHealthBar(int Health)
{
healthBar.value = Health;
} public void SetManaBar()
{
manaBar.value = Gamemanager.Instance.playerScript.Mana;
} public void SetManaBar(int Mana)
{
manaBar.value = Mana;
} public void CheckPause() {
if (!died && !levelComplete && canPause)
{
if (Input.GetKeyDown(KeyCode.Escape) || Input.GetButtonDown("start") || isPauseButton)
{
if (!isOptions)
{
if (!pauseMenu.activeSelf)
{
if (pauseButton)
{
pauseButton.SetActive(false);
isPauseButton = false;
}
pauseMenu.SetActive(true);
}
else
{
if (pauseButton)
{
pauseButton.SetActive(true);
isPauseButton = false;
}
pauseMenu.SetActive(false);
}
pauseGame();
}
else {
OptionsMenu();
}
}
}
}
public void OptionsMenu() {
if (!options.activeSelf)
{
options.SetActive(true);
isOptions = true;
pauseButtons.SetActive(false);
}
else {
if (!volume.activeSelf && !controls.activeSelf)
{
options.SetActive(false);
isOptions = false;
pauseButtons.SetActive(true);
}
else if (volume.activeSelf)
{
volume.SetActive(false);
optionsButtons.SetActive(true);
}
else if (controls.activeSelf)
{
controls.SetActive(false);
optionsButtons.SetActive(true);
}
}
}
public void CheckEnding() {
if (Gamemanager.Instance.player)
{
if (levelComplete) {
if (!hasPausedAtEnding)
{
pauseGame();
hasPausedAtEnding = true;
}
gameOver.SetActive(true);
completeEnding.SetActive(true);
}
}
} public void YouDied()
{
if (!died)
{
died = true;
StartCoroutine(DiedFade());
}
}
See this entire code!
Game Maneger
this is esencially the interface and the references for the game it take referance such as the player and the enemy.
Code
See this entire code!
public class Gamemanager : MonoBehaviour
{
public static Gamemanager Instance { get; private set; } // static singleton
// player information
public GameObject player;
//public PlayerController playerScript;
public PlayerController_UnityChan playerScript; // SpawnLocation information
public Transform SpawnLocation; // HUD information
public GameObject HUD;
public HUDScript HUDScript; public GameObject animatorFunctions;
public AnimatorFunctions animatorFunctionsScript;
public string nextLevelName; public Vector3 lastCheckPointPos;
public AudioMixer GlobalMixer;
public GameObject textPopup;
public TextPopup textPopupScript; [Header("References")]
public Image inventoryItemImage;
private void Awake()
{
Instance = this;
GlobalMixer.SetFloat("MasterVol", PlayerPrefs.GetFloat("MasterVol"));
GlobalMixer.SetFloat("MusicVol", PlayerPrefs.GetFloat("MusicVol"));
GlobalMixer.SetFloat("AmbienceVol", PlayerPrefs.GetFloat("AmbienceVol"));
GlobalMixer.SetFloat("SFXVol", PlayerPrefs.GetFloat("SFXVol"));
}
// Start is called before the first frame update
void Start()
{ if (player)
{
playerScript = player.GetComponent<PlayerController_UnityChan>();
}
// HUD initialization
HUD = GameObject.FindGameObjectWithTag("HUD");
if (HUD)
{
HUDScript = HUD.GetComponent<HUDScript>();
}
if (animatorFunctions)
{
animatorFunctionsScript = animatorFunctions.GetComponent<AnimatorFunctions>();
}
textPopup = GameObject.FindGameObjectWithTag("TextPopup");
if (textPopup) {
textPopupScript = textPopup.GetComponent<TextPopup>();
}
}
}
We set out to create a 2.5D game by taking as references popular games like Ori and the Will of the Wisps and Hollow knight while adding new features that will make the game more original. The Blind Forest is built from the ground up in Unity, creating the code, concept, level design and game design from scratch. We spent 1 months to design the concept and the design document for the game and the next 1 month on the project rebuilding the original game in Unity so that we had a solid foundation to work from to the next month. Then we started to add new systems and features. In total the project took 3 months to made.
The game is design to be with fluid and precises movements and mechanics with a full of challenges enemies that are very different from each other’s and requires different tactics to be defeated.