

- Päls & Fjäder -
Project Details
Project Length: 2 Weeks
Team Size: 14 people
Engine: Unity
Planning: Discord
Project Length: 2 Weeks
Team Size: 14 people
Engine: Unity
Planning: Discord
Project Length: 2 Weeks
Team Size: 14 people
Engine: Unity
Planning: Discord
My Roles: Character Programmer
Player 2 Character Controller
Water Bullets
Xbox Adaptive Controller
My Roles: Character Programmer
Player 2 Character Controller
Water Bullets
Xbox Adaptive Controller
My Roles: Character Programmer
Player 2 Character Controller
Water Bullets
Xbox Adaptive Controller
Squirrel Character Controller
This was my first project as a programmer at future games, I worked on the character controller for the squirrel.
A requirement for this project was that we use an Xbox adaptive controller meaning that all inputs needed to be performed on just two buttons, my biggest challenge at the time was configuring the inputs since I was new to unity´s input system.
The final controls ended up being left/right buttons rotate the squirrel and to fire the gun you need to press down both buttons.


Show Related Code
using UnityEngine; using UnityEngine.UIElements; using UnityEngine.InputSystem; using FIMSpace.Basics; using UnityEngine.Android; using FIMSpace.Generating.Rules; using UnityEngine.Serialization; using Unity.Cinemachine; public class H_SquirrelMovement : MonoBehaviour { [FormerlySerializedAs("TurnRate")] [SerializeField] private float turnRate; [FormerlySerializedAs("PSpawnpoint")] [SerializeField] private Transform pSpawnpoint; [FormerlySerializedAs("ProjectileInterval")] [SerializeField] private float projectileInterval; [FormerlySerializedAs("WaterForce")] [SerializeField] private float waterForce; [FormerlySerializedAs("PelicanMovement")] [SerializeField] private Galla_PelicanPlayer pelicanMovement; [FormerlySerializedAs("BirdDirectionForce")] [SerializeField] private float birdDirectionForce; [FormerlySerializedAs("_animator")] [SerializeField] private Animator animator; [SerializeField] private ParticleSystem waterImpactSplash; [SerializeField] private float initialTurnRate = 1; [SerializeField] private float turnRateIncrement = 1; private float _projectileCountDown; private float _turnDirection = 0f; private CustomInputSystem _inputSystem; private bool _buttonDownA = false; private bool _buttonDownD = false; private float _currentTurnRate; private bool canShoot = true; [FormerlySerializedAs("Projectile")] public H_WaterBlob projectile; private Quaternion _squirrelWorldRotation; private PlayerInput _playerInput; private InputAction _acButtonAEnter; private InputAction _acButtonAExit; private InputAction _acButtonBEnter; private InputAction _acButtonBExit; private void Awake() { _currentTurnRate = initialTurnRate; _squirrelWorldRotation = transform.rotation; _playerInput = GetComponent<PlayerInput>(); _acButtonAEnter = _playerInput.actions["SquirrelButtonAPressed"]; _acButtonAExit = _playerInput.actions["SquirrelButtonAReleased"]; _acButtonBEnter = _playerInput.actions["SquirrelButtonBPressed"]; _acButtonBExit = _playerInput.actions["SquirrelRotateBReleased"]; animator = GetComponent<Animator>(); } private void Start() { _inputSystem = new CustomInputSystem(); _inputSystem.Enable(); waterForce = waterForce * 100; } private void Update() { if (_buttonDownA && _buttonDownD) { if (canShoot) { _currentTurnRate = initialTurnRate; if (_projectileCountDown <= 0) { FireWaterGun(); _projectileCountDown = projectileInterval; } animator.SetBool("IsTurning", false); animator.SetBool("IsIdle", false); canShoot = false; } } else { RotateSquirrel(); canShoot = true; } if (!_buttonDownA && !_buttonDownD && _projectileCountDown > 0) { animator.SetBool("IsIdle", true); } transform.rotation = _squirrelWorldRotation; _projectileCountDown = _projectileCountDown - Time.deltaTime; } private void RotateSquirrel() { if (_turnDirection != 0f) { animator.SetBool("IsTurning", true); animator.SetBool("IsIdle", false); if (_currentTurnRate < turnRate) { _currentTurnRate += turnRateIncrement; } } else { animator.SetBool("IsTurning", false); _currentTurnRate = initialTurnRate; } _squirrelWorldRotation *= Quaternion.Euler(Vector3.up * (_turnDirection * _currentTurnRate * Time.deltaTime)); } private void FireWaterGun() { CinemachineImpulseSource cinemachineImpulseSource = GetComponent<CinemachineImpulseSource>(); FMODUnity.RuntimeManager.PlayOneShot("event:/BalloonShoot", gameObject.transform.position); // Ensure the impulse is generated with the rotation aligned to the GameObject's forward direction cinemachineImpulseSource.GenerateImpulseWithVelocity(transform.forward * 0.2f); animator.SetTrigger("Shoot"); animator.SetBool("IsIdle", false); // Ensure IsIdle is false when shooting Vector3 projectileSpawn = pSpawnpoint.position; Vector3 pelicanDirection = pelicanMovement.transform.forward; Vector3 pelicanSpeedVector = pelicanDirection * birdDirectionForce; H_WaterBlob projectile = Instantiate(this.projectile, projectileSpawn, pSpawnpoint.transform.rotation); projectile.waterForce = waterForce; projectile.spawnPointTransform = transform; projectile.pelicanSpeedVector = pelicanSpeedVector; projectile.waterImpactParticles = waterImpactSplash; } //-------------------------------------\/ Input Controls \/----------------------------------------- public void ButtonAEnter() { _buttonDownA = true; _turnDirection = -1f; } public void ButtonAExit() { if (_buttonDownA && _buttonDownD) { _buttonDownA = false; _turnDirection = 1f; } else { _buttonDownA = false; _turnDirection = 0f; } } public void ButtonBEnter() { _buttonDownD = true; _turnDirection = 1f; } public void ButtonBExit() { if (_buttonDownA && _buttonDownD) { _buttonDownD = false; _turnDirection = -1f; } else { _buttonDownD = false; _turnDirection = 0f; } } private void OnEnable() { _acButtonAEnter.performed += _ => ButtonAEnter(); _acButtonAEnter.canceled += _ => ButtonAExit(); _acButtonBEnter.performed += _ => ButtonBEnter(); _acButtonBEnter.canceled += _ => ButtonBExit(); } private void OnDisable() { _acButtonAEnter.performed -= _ => ButtonAEnter(); _acButtonAEnter.canceled -= _ => ButtonAExit(); _acButtonBEnter.performed -= _ => ButtonBEnter(); _acButtonBEnter.canceled -= _ => ButtonBExit
Show Related Code
using UnityEngine; using UnityEngine.UIElements; using UnityEngine.InputSystem; using FIMSpace.Basics; using UnityEngine.Android; using FIMSpace.Generating.Rules; using UnityEngine.Serialization; using Unity.Cinemachine; public class H_SquirrelMovement : MonoBehaviour { [FormerlySerializedAs("TurnRate")] [SerializeField] private float turnRate; [FormerlySerializedAs("PSpawnpoint")] [SerializeField] private Transform pSpawnpoint; [FormerlySerializedAs("ProjectileInterval")] [SerializeField] private float projectileInterval; [FormerlySerializedAs("WaterForce")] [SerializeField] private float waterForce; [FormerlySerializedAs("PelicanMovement")] [SerializeField] private Galla_PelicanPlayer pelicanMovement; [FormerlySerializedAs("BirdDirectionForce")] [SerializeField] private float birdDirectionForce; [FormerlySerializedAs("_animator")] [SerializeField] private Animator animator; [SerializeField] private ParticleSystem waterImpactSplash; [SerializeField] private float initialTurnRate = 1; [SerializeField] private float turnRateIncrement = 1; private float _projectileCountDown; private float _turnDirection = 0f; private CustomInputSystem _inputSystem; private bool _buttonDownA = false; private bool _buttonDownD = false; private float _currentTurnRate; private bool canShoot = true; [FormerlySerializedAs("Projectile")] public H_WaterBlob projectile; private Quaternion _squirrelWorldRotation; private PlayerInput _playerInput; private InputAction _acButtonAEnter; private InputAction _acButtonAExit; private InputAction _acButtonBEnter; private InputAction _acButtonBExit; private void Awake() { _currentTurnRate = initialTurnRate; _squirrelWorldRotation = transform.rotation; _playerInput = GetComponent<PlayerInput>(); _acButtonAEnter = _playerInput.actions["SquirrelButtonAPressed"]; _acButtonAExit = _playerInput.actions["SquirrelButtonAReleased"]; _acButtonBEnter = _playerInput.actions["SquirrelButtonBPressed"]; _acButtonBExit = _playerInput.actions["SquirrelRotateBReleased"]; animator = GetComponent<Animator>(); } private void Start() { _inputSystem = new CustomInputSystem(); _inputSystem.Enable(); waterForce = waterForce * 100; } private void Update() { if (_buttonDownA && _buttonDownD) { if (canShoot) { _currentTurnRate = initialTurnRate; if (_projectileCountDown <= 0) { FireWaterGun(); _projectileCountDown = projectileInterval; } animator.SetBool("IsTurning", false); animator.SetBool("IsIdle", false); canShoot = false; } } else { RotateSquirrel(); canShoot = true; } if (!_buttonDownA && !_buttonDownD && _projectileCountDown > 0) { animator.SetBool("IsIdle", true); } transform.rotation = _squirrelWorldRotation; _projectileCountDown = _projectileCountDown - Time.deltaTime; } private void RotateSquirrel() { if (_turnDirection != 0f) { animator.SetBool("IsTurning", true); animator.SetBool("IsIdle", false); if (_currentTurnRate < turnRate) { _currentTurnRate += turnRateIncrement; } } else { animator.SetBool("IsTurning", false); _currentTurnRate = initialTurnRate; } _squirrelWorldRotation *= Quaternion.Euler(Vector3.up * (_turnDirection * _currentTurnRate * Time.deltaTime)); } private void FireWaterGun() { CinemachineImpulseSource cinemachineImpulseSource = GetComponent<CinemachineImpulseSource>(); FMODUnity.RuntimeManager.PlayOneShot("event:/BalloonShoot", gameObject.transform.position); // Ensure the impulse is generated with the rotation aligned to the GameObject's forward direction cinemachineImpulseSource.GenerateImpulseWithVelocity(transform.forward * 0.2f); animator.SetTrigger("Shoot"); animator.SetBool("IsIdle", false); // Ensure IsIdle is false when shooting Vector3 projectileSpawn = pSpawnpoint.position; Vector3 pelicanDirection = pelicanMovement.transform.forward; Vector3 pelicanSpeedVector = pelicanDirection * birdDirectionForce; H_WaterBlob projectile = Instantiate(this.projectile, projectileSpawn, pSpawnpoint.transform.rotation); projectile.waterForce = waterForce; projectile.spawnPointTransform = transform; projectile.pelicanSpeedVector = pelicanSpeedVector; projectile.waterImpactParticles = waterImpactSplash; } //-------------------------------------\/ Input Controls \/----------------------------------------- public void ButtonAEnter() { _buttonDownA = true; _turnDirection = -1f; } public void ButtonAExit() { if (_buttonDownA && _buttonDownD) { _buttonDownA = false; _turnDirection = 1f; } else { _buttonDownA = false; _turnDirection = 0f; } } public void ButtonBEnter() { _buttonDownD = true; _turnDirection = 1f; } public void ButtonBExit() { if (_buttonDownA && _buttonDownD) { _buttonDownD = false; _turnDirection = -1f; } else { _buttonDownD = false; _turnDirection = 0f; } } private void OnEnable() { _acButtonAEnter.performed += _ => ButtonAEnter(); _acButtonAEnter.canceled += _ => ButtonAExit(); _acButtonBEnter.performed += _ => ButtonBEnter(); _acButtonBEnter.canceled += _ => ButtonBExit(); } private void OnDisable() { _acButtonAEnter.performed -= _ => ButtonAEnter(); _acButtonAEnter.canceled -= _ => ButtonAExit(); _acButtonBEnter.performed -= _ => ButtonBEnter(); _acButtonBEnter.canceled -= _ => ButtonBExit
Show Related Code
using UnityEngine; using UnityEngine.UIElements; using UnityEngine.InputSystem; using FIMSpace.Basics; using UnityEngine.Android; using FIMSpace.Generating.Rules; using UnityEngine.Serialization; using Unity.Cinemachine; public class H_SquirrelMovement : MonoBehaviour { [SerializeField] private float turnRate; [SerializeField] private Transform pSpawnpoint; [SerializeField] private float projectileInterval; [SerializeField] private float waterForce; [SerializeField] private Galla_PelicanPlayer pelicanMovement; [SerializeField] private float birdDirectionForce; [SerializeField] private Animator animator; [SerializeField] private ParticleSystem waterImpactSplash; [SerializeField] private float initialTurnRate = 1; [SerializeField] private float turnRateIncrement = 1; private float _projectileCountDown; private float _turnDirection = 0f; private CustomInputSystem _inputSystem; private bool _buttonDownA = false; private bool _buttonDownD = false; private float _currentTurnRate; private bool canShoot = true; public H_WaterBlob projectile; private Quaternion _squirrelWorldRotation; private PlayerInput _playerInput; private InputAction _acButtonAEnter; private InputAction _acButtonAExit; private InputAction _acButtonBEnter; private InputAction _acButtonBExit; private void Awake() { _currentTurnRate = initialTurnRate; _squirrelWorldRotation = transform.rotation; _playerInput = GetComponent<PlayerInput>(); _acButtonAEnter = _playerInput.actions["SquirrelButtonAPressed"]; _acButtonAExit = _playerInput.actions["SquirrelButtonAReleased"]; _acButtonBEnter = _playerInput.actions["SquirrelButtonBPressed"]; _acButtonBExit = _playerInput.actions["SquirrelRotateBReleased"]; animator = GetComponent<Animator>(); } private void Start() { _inputSystem = new CustomInputSystem(); _inputSystem.Enable(); waterForce = waterForce * 100; } private void Update() { if (_buttonDownA && _buttonDownD) { if (canShoot) { _currentTurnRate = initialTurnRate; if (_projectileCountDown <= 0) { FireWaterGun(); _projectileCountDown = projectileInterval; } animator.SetBool("IsTurning", false); animator.SetBool("IsIdle", false); canShoot = false; } } else { RotateSquirrel(); canShoot = true; } if (!_buttonDownA && !_buttonDownD && _projectileCountDown > 0) { animator.SetBool("IsIdle", true); } transform.rotation = _squirrelWorldRotation; _projectileCountDown = _projectileCountDown - Time.deltaTime; } private void RotateSquirrel() { if (_turnDirection != 0f) { animator.SetBool("IsTurning", true); animator.SetBool("IsIdle", false); if (_currentTurnRate < turnRate) { _currentTurnRate += turnRateIncrement; } } else { animator.SetBool("IsTurning", false); _currentTurnRate = initialTurnRate; } _squirrelWorldRotation *= Quaternion.Euler(Vector3.up * (_turnDirection * _currentTurnRate * Time.deltaTime)); } private void FireWaterGun() { CinemachineImpulseSource cinemachineImpulseSource = GetComponent<CinemachineImpulseSource>(); FMODUnity.RuntimeManager.PlayOneShot("event:/BalloonShoot", gameObject.transform.position); // Ensure the impulse is generated with the rotation aligned to the GameObject's forward direction cinemachineImpulseSource.GenerateImpulseWithVelocity(transform.forward * 0.2f); animator.SetTrigger("Shoot"); animator.SetBool("IsIdle", false); // Ensure IsIdle is false when shooting Vector3 projectileSpawn = pSpawnpoint.position; Vector3 pelicanDirection = pelicanMovement.transform.forward; Vector3 pelicanSpeedVector = pelicanDirection * birdDirectionForce; H_WaterBlob projectile = Instantiate(this.projectile, projectileSpawn, pSpawnpoint.transform.rotation); projectile.waterForce = waterForce; projectile.spawnPointTransform = transform; projectile.pelicanSpeedVector = pelicanSpeedVector; projectile.waterImpactParticles = waterImpactSplash; } //-------------------------------------\/ Input Controls \/----------------------------------------- public void ButtonAEnter() { _buttonDownA = true; _turnDirection = -1f; } public void ButtonAExit() { if (_buttonDownA && _buttonDownD) { _buttonDownA = false; _turnDirection = 1f; } else { _buttonDownA = false; _turnDirection = 0f; } } public void ButtonBEnter() { _buttonDownD = true; _turnDirection = 1f; } public void ButtonBExit() { if (_buttonDownA && _buttonDownD) { _buttonDownD = false; _turnDirection = -1f; } else { _buttonDownD = false; _turnDirection = 0f; } } private void OnEnable() { _acButtonAEnter.performed += _ => ButtonAEnter(); _acButtonAEnter.canceled += _ => ButtonAExit(); _acButtonBEnter.performed += _ => ButtonBEnter(); _acButtonBEnter.canceled += _ => ButtonBExit(); } private void OnDisable() { _acButtonAEnter.performed -= _ => ButtonAEnter(); _acButtonAEnter.canceled -= _ => ButtonAExit(); _acButtonBEnter.performed -= _ => ButtonBEnter(); _acButtonBEnter.canceled -= _ => ButtonBExit
Water Bullets
In our game the squirrel player had the objective of shooting water on to burning trees so I had to program the logic for the bullets so that they would interact with the fire system.
At first the bullets only extinguished the trees they collided with, but this caused the game difficulty to become too high so I adjusted it to perform a sphere cast on impact to extinguish all nearby trees as well.

Show Related Code
using FIMSpace.Generating.Rules; using Unity.Mathematics; using UnityEngine; using UnityEngine.Serialization; public class H_WaterBlob : MonoBehaviour { Rigidbody _body; [FormerlySerializedAs("WaterForce")] public float waterForce; [FormerlySerializedAs("PelicanSpeedVector")] public Vector3 pelicanSpeedVector; [FormerlySerializedAs("SpawnPointTransform")] public Transform spawnPointTransform; public float aoeRadius = 5f; public ParticleSystem waterImpactParticles; private void Start() { _body = GetComponent<Rigidbody>(); Vector3 finalBulletVector = (transform.forward * waterForce) + (pelicanSpeedVector); _body.AddForce(finalBulletVector); } private void OnCollisionEnter(Collision collision) { AoeCheck(); Instantiate(waterImpactParticles, transform.position, quaternion.identity); FMODUnity.RuntimeManager.PlayOneShotAttached("event:/WaterSplash", gameObject); Destroy(this.gameObject); } private void AoeCheck() { Collider[] hitColliders = Physics.OverlapSphere(this.transform.position, aoeRadius); foreach (var hitCollider in hitColliders) { TreeUnit tree = hitCollider.gameObject.GetComponent<TreeUnit>(); if (tree != null) { MeshRenderer treeRenderer = tree.GetComponentInChildren<MeshRenderer>(); ForestManager.Instance.ExtinguishTree(tree
Show Related Code
using FIMSpace.Generating.Rules; using Unity.Mathematics; using UnityEngine; using UnityEngine.Serialization; public class H_WaterBlob : MonoBehaviour { Rigidbody _body; [FormerlySerializedAs("WaterForce")] public float waterForce; [FormerlySerializedAs("PelicanSpeedVector")] public Vector3 pelicanSpeedVector; [FormerlySerializedAs("SpawnPointTransform")] public Transform spawnPointTransform; public float aoeRadius = 5f; public ParticleSystem waterImpactParticles; private void Start() { _body = GetComponent<Rigidbody>(); Vector3 finalBulletVector = (transform.forward * waterForce) + (pelicanSpeedVector); _body.AddForce(finalBulletVector); } private void OnCollisionEnter(Collision collision) { AoeCheck(); Instantiate(waterImpactParticles, transform.position, quaternion.identity); FMODUnity.RuntimeManager.PlayOneShotAttached("event:/WaterSplash", gameObject); Destroy(this.gameObject); } private void AoeCheck() { Collider[] hitColliders = Physics.OverlapSphere(this.transform.position, aoeRadius); foreach (var hitCollider in hitColliders) { TreeUnit tree = hitCollider.gameObject.GetComponent<TreeUnit>(); if (tree != null) { MeshRenderer treeRenderer = tree.GetComponentInChildren<MeshRenderer>(); ForestManager.Instance.ExtinguishTree(tree
Show Related Code
using FIMSpace.Generating.Rules; using Unity.Mathematics; using UnityEngine; using UnityEngine.Serialization; public class H_WaterBlob : MonoBehaviour { Rigidbody _body; [FormerlySerializedAs("WaterForce")] public float waterForce; [FormerlySerializedAs("PelicanSpeedVector")] public Vector3 pelicanSpeedVector; [FormerlySerializedAs("SpawnPointTransform")] public Transform spawnPointTransform; public float aoeRadius = 5f; public ParticleSystem waterImpactParticles; private void Start() { _body = GetComponent<Rigidbody>(); Vector3 finalBulletVector = (transform.forward * waterForce) + (pelicanSpeedVector); _body.AddForce(finalBulletVector); } private void OnCollisionEnter(Collision collision) { AoeCheck(); Instantiate(waterImpactParticles, transform.position, quaternion.identity); FMODUnity.RuntimeManager.PlayOneShotAttached("event:/WaterSplash", gameObject); Destroy(this.gameObject); } private void AoeCheck() { Collider[] hitColliders = Physics.OverlapSphere(this.transform.position, aoeRadius); foreach (var hitCollider in hitColliders) { TreeUnit tree = hitCollider.gameObject.GetComponent<TreeUnit>(); if (tree != null) { MeshRenderer treeRenderer = tree.GetComponentInChildren<MeshRenderer>(); ForestManager.Instance.ExtinguishTree(tree
Reflections
This was my first project in my journey to become a game programmer and I feel that it served as a very good starting point to working as a programmer in a team, I learned about Unity's input system and was able to hook up my controller with other programmers systems.
The project it self was quite successful and became quite polished in the end, since we had limitations such as the theme being "Forest", being required to use the Xbox adaptive controller and our target audience being of ages 10-13 the creation process took quite some time but I think it helped us come up with creative solutions to the problems that popped up.
Game Trailer

Smooth Scroll
This will hide itself!
This will hide itself!