Culling the Blood Red Forest

Culling the Blood Red Forest is a 3D shooter where you speed around stages killing semi-aggressive forest creatures. The ultimate goal is to beat all the bosses, and in doing so, earn new guns to use on the enemies.

Player Movement

We wanted the game to be a fast-paced arena shooter, and the solution we came up with was to make the player’s movement slightly slippery. To keep the speed going, we also decided to make the walls bouncy, which really helped to keep the player on the move. Since Unity's physics are a bit naturally floaty, it only took a little bit of tinkering around to achieve the desired effect.

Player Movement Snippet
void FixedUpdate()
    {
        playerX = transform.forward;
        playerRight = transform.right;

        //move the player if the "wasd" keys are pressed, using acceleration to simulate slippery physics
        if (Input.GetKey(KeyCode.W))
        {
            rb.AddForce(playerX.x * acceleration, 0, playerX.z * acceleration);
        }
        if (Input.GetKey(KeyCode.A))
        {
            rb.AddForce(-playerRight.x * acceleration, 0, -playerRight.z * acceleration);
        }
        if (Input.GetKey(KeyCode.S))
        {
            rb.AddForce(-playerX.x * acceleration, 0, -playerX.z * acceleration);
        }
        if (Input.GetKey(KeyCode.D))
        {
            rb.AddForce(playerRight.x * acceleration, 0, playerRight.z * acceleration);
        }
    }

Gun Design

The guns were designed to be different in the way that each of them shoot. The pistol shoots a single small shot, the shotgun shoots a burst of pellets, the grenade launcher lobs a large grenade, and the LMG shoots three bullets at once rapidly. The slippery movement also meant that the recoil of the guns could be used to push oneself backwards, allowing the player to slow down or to move backwards outright. The recoil of the LMG is so high, in fact, that you can use it to fly around the stage, making it just as fun to use as it is uncontrollable.

Gun Shot Snippet
void FixedUpdate()
    {
        if (cooldownActivated && ammunition > 0 || infiniteAmmo == true) //if the player has shot and has infinite ammo...
        {
            cooldownSeconds -= Time.deltaTime;
            if(cooldownSeconds < 0) //...prevent shooting again for cooldownSeconds seconds;
            {
                cooldownActivated = false;
                cooldownSeconds = initialSeconds;
            }
        }
        else if (cooldownActivated && ammunition <= 0) //else if the player has shot and has no ammunition...
        {
            cooldownSeconds -= Time.deltaTime;
            if (cooldownSeconds < -2) //...prevent shooting again for cooldownSeconds seconds plus two seconds.
            {
                cooldownActivated = false;
                cooldownSeconds = initialSeconds;
                ammunition = initialAmmunition;
            }
        }
        if (Input.GetMouseButton(0) == true && cooldownActivated == false) //if the left mouse button is clicked and cooldown isn't activated...
        {
            shotSoundEffect.Play(0); //...play the gunshot sound effect...
            Rigidbody bullet;
            bullet = Instantiate(bulletObject, spawnLocation.transform.position, camera.transform.rotation); //...spawn a bullet...
            bullet.gameObject.transform.Rotate(bullet.gameObject.transform.rotation.x, customBulletRotation, 0); //...orient the bullet correctly...
            bullet.gameObject.GetComponent<MeshRenderer>().enabled = true; //...let the bullet be seen...
            bullet.gameObject.SetActive(true); //...activate the bullet...
            bullet.AddForce(camera.transform.forward * acceleration); //...add forward force to the bullet...

            player.GetComponent<Rigidbody>().AddForce(-camera.transform.forward.x*recoil, -camera.transform.forward.y*recoilY, -camera.transform.forward.z*recoil); //...apply recoil to the player...

            ammunition--; //...remove ammunition...

            cooldownActivated = true; //...and activate the cooldown.
        }
    }

Enemy AI

The enemies' AI is programmed to wander around while not near the player, but if the player gets close, they run away. Once the player kills the first wave of enemies, they go on the attack – enemies begin to run towards the player if they get too close. Originally they only ran away, but this made the game almost impossible to lose, so our solution was to have one round passive and the next round aggressive, which also helped to keep the gameplay more fresh.

Enemy AI Snippet
void FixedUpdate()
    {
        if (boundaryBounce) //if the enemy runs into a level boundary...
        {
            boundaryBounceTime -= Time.deltaTime;
            if (boundaryBounceTime <= 0)
            {
                boundaryBounce = false;
            }
            rb.AddForce(transform.forward * acceleration); //...add force in the opposite direction of the boundary for boundaryBounceTime seconds.
        }
        else if (chaser && Vector3.Distance(transform.position, player.transform.position) < minimumDistance) //if the enemy chases and the player is within a minimumDistance radius...
        {
            animator.SetBool("isRunning", true); //...play moving animations...
            transform.LookAt(player.transform);
            rb.AddForce(transform.forward * acceleration); //...add force to run towards the player;
            standingTime = initialStandingTime;
            movingTime = initialMovingTime;
            isMoving = false;
            isStanding = true;
        }
        else if (Vector3.Distance(transform.position, player.transform.position) < minimumDistance) //else if the enemy doesn't chase and the player is within a minimumDistance radius...
        {
            animator.SetBool("isRunning", true); //...play moving animations...
            playerPosition = new Vector3(player.transform.position.x, transform.position.y, player.transform.position.z);
            transform.LookAt(playerPosition);
            transform.Rotate(0, 180, 0);
            rb.AddForce(transform.forward * acceleration); //...add force to run away from the player.
            standingTime = initialStandingTime;
            movingTime = initialMovingTime;
            isMoving = false;
            isStanding = true;
        }
        else if (isMoving) //if the enemy is in an idle moving state...
        {
            movingTime -= Time.deltaTime;
            animator.SetBool("isRunning", true); //...play moving animations...
            if (movingTime <= 0)
            {
                isMoving = false;
                isStanding = true;
                movingTime = initialMovingTime;
            }
            rb.AddForce(transform.forward * acceleration); //...and add force in a random direction.
        }
        else if (isStanding) //if the enemy is in an idle standing state...
        {
            standingTime -= Time.deltaTime;
            animator.SetBool("isRunning", false); //...don't play animations...
            if (standingTime <= 0)
            {
                isMoving = true;
                isStanding = false;
                standingTime = initialStandingTime;
                transform.Rotate(new Vector3(0, Random.Range(0.0f, 360.0f), 0)); //...and rotate to a new direction.
            }
        }
    }