Project description

Cornered is an applied party game about meat consumption, one player plays as an animal, the other three players all play a human farmer. During the game we make the human players focus on hunting the animal, once they are near enough they can hit the animal and advance into the next stage. The animal can keep the hunters at a distance by throwing vegetables at them, which will slow them down.

Every time the hunters catch the animal the play area becomes smaller, after catching the animal three times, the borders around the play area dissapear and the players are confronted with a destroyed enviroment. There is a twist however, even though we focus on hunting the animal there are other things in the world that the players can eat, if they survive from eating other things, a good ending is revealed.

Animal Movement
Early on we knew the character was going to be the biggest part of the game, both the animal and the human had to feel good. Because I wanted to give the designers full access to tweaking the character I build my own character controller, I took some inspiration from the default Unity character controller but quickly went my own way.

I decided to split the character functionality in three scripts, a PlayerStats class, a PlayerInput class and a HumanController/AnimalController. This way I would be able to use the PlayerStats and PlayerInput for both the animal and the human, the only thing I would have to change is the controller script component.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent (typeof (PlayerInput))]
public class AnimalController : MonoBehaviour {

    private const float JUMP_SLOWDOWN = 0.85f;

    public GameObject projectile;
    public Animator animator;

    public float moveTurnSpeed = 360f;
    public float idleTurnSpeed = 180f;
    public float jumpTurnSpeed = 40f;
    public float moveSpeed = 5.0f; 
    public float jumpHeight = 10.0f;
    public float fallTime = 2f;
    public int flyTimer = 20;
    public GameObject[] ammo;
    public bool isMoving = false;

    private float shootDelay = 0;
    private bool isGrounded = true;
    private Rigidbody rb;
    private PlayerInput pInput;
    private PlayerStats pStats;
    private bool cooldown = false;

    private void Start() {
    	rb = GetComponent<Rigidbody>();
    	pInput = GetComponent<PlayerInput>();
        pStats = GetComponent<PlayerStats>();

    private void Update() {
    	if(isGrounded) {
    	} else {


    private void HandleGroundedMovement() {
    	float _xhorizontal = pInput.Horizontal;
    	float _zVertical = pInput.Vertical;

        // Character rotation
        if (isMoving) {
            transform.Rotate(0, pInput.MouseX * moveTurnSpeed * Time.deltaTime, 0);
    	} else {
    	    transform.Rotate(0, pInput.MouseX * idleTurnSpeed * Time.deltaTime, 0);

        // Movement
        transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime * _zVertical, Space.Self);
        transform.Translate(Vector3.right * moveSpeed * Time.deltaTime * _xhorizontal, Space.Self);

    	if (pInput.Jump) {
    	     rb.velocity = new Vector3(rb.velocity.x, jumpHeight, rb.velocity.z);
    	     isGrounded = false;

    private void Shoot() {
        if (pInput.Shoot && pInput.Aim && shootDelay <= 0 && pStats.ammunition > 0) {
            Vector3 _spawn = transform.position + (transform.right * 0.8f) + (transform.up * 2f) + (transform.forward * 1f);
            GameObject _b = Instantiate(projectile, _spawn, Quaternion.identity);
            _b.GetComponent<Rigidbody>().AddForce(transform.forward * 200f);

            AudioClip sound = SoundManager.instance.PickRandomSound(SoundManager.instance.sfxAnimalNoise);
            pStats.ammunition -= 1;
            shootDelay = 0.8f;

        shootDelay -= Time.deltaTime;

    private void Grab() {
        Debug.DrawRay(transform.position + (Vector3.up * 0.1f), Vector3.down,;

        if (pInput.Shoot) {
            RaycastHit _hitInfo;
            if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out _hitInfo, 1.0f)) {
                if (_hitInfo.collider.tag == "Food") {
                    Debug.Log("Grab food");
                    AudioClip sound = SoundManager.instance.PickRandomSound(SoundManager.instance.sfxAnimalNoise);

    private void HandleAirbourneMovement() {
    	float _xHorizontal = pInput.Horizontal;
    	float _zVertical = pInput.Vertical;

        transform.Rotate(0, pInput.MouseX * jumpTurnSpeed * Time.deltaTime, 0);
        transform.Translate(Vector3.forward * moveSpeed * (Time.deltaTime * JUMP_SLOWDOWN) * _zVertical, Space.Self);
        transform.Translate(Vector3.right * moveSpeed * (Time.deltaTime * JUMP_SLOWDOWN) * _xHorizontal, Space.Self);

        Vector3 extraGravity = (Physics.gravity * fallTime) - Physics.gravity;

    private void AnimationHandler() {
        animator.SetFloat("animMove", pInput.Vertical);
        animator.SetBool("animAttack", pInput.Shoot);
        animator.SetBool("animJump", pInput.Jump);

    private void CheckIfGrounded() {
    	RaycastHit hitInfo;

    	if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, 1.0f)) {
    	    isGrounded = true;
    	} else {
    	    isGrounded = false;

    private void CheckIfMoving() {
        if (pInput.Horizontal != 0 || pInput.Vertical != 0) {
            isMoving = true;
        } else {
            isMoving = false;

    private IEnumerator InitFlyTimer (int seconds) {
    	cooldown = true;
    	yield return new WaitForSeconds(seconds);
    	cooldown = false;
    	yield return null;

A dedicated script for the PlayerInput seemed like a good idea at first, even though Unity has a fully working Input manager. The reason I made the script was because every player has to listen for input from a different controller, so input (in the input manager) was defined as Jump_P0, Jump_P1, etc. The PlayerInput would then listen for the input.
Later in the development process, this became a chore to maintain and it is something that I would do different next time.

I excluded some part of the script since this is an example.
public class PlayerInput : MonoBehaviour {

    private int playerIndex;

    private bool jump;
    public bool Jump {
    	get { return jump; }

    public bool isInputBlocked;
    private void Start() {
        playerIndex = GetComponent<PlayerStats>().playerIndex;

    private void Update() {
        if (!isInputBlocked) {
            jump = Input.GetButton("Jump_P" + playerIndex);

Project details
  • Category : Game Development, Programming
  • Date Created : January 8, 2017
  • Website : Github

Share this Post: