Hatmancers
About the Game
Hatmancers is a fast-spaced, multiplayer third-person spellslinger made to be playable for all ages. The simplicity of the mechanics gives accessibility to a wide audience, allowing players to easily jump into the action without having to learn complex controls or systems. The game is made to be played in quick rounds, allowing the game to be started and stopped in convenient, short intervals. Filled with different kinds of magic abilities imbued into unique hats, Hatmances is sure to bring tons of action-packed fun to any group of friends.
Developed from August 2019 to December 2019.
Play the game here!
Role in Development
- Managers: Michael Berger, Nathan Glick
- Designer: Nathan Glick
- Programmers: David Liu, Nathan Glick
- Modelers: Surya Ajayakumar, Abhishek Chandrashekhar Panhale
My main role in development for this project was Gameplay Programming. I developed the player controls as well as handling multiplayer functionality in the game. The game supports compatibility for both Xbox and Playstation controllers in conjunction to keyboard controls. I also assisted in fixing bugs and tweaking the game for a balanced experience. My biggest struggle during this project was detecting multiplayer input across different controllers. Unity's control system at the time had interchangeable axes and buttons across both Xbox and Playstation controllers. To get around this, I had the menu detect a starting input from either keyboard, Xbox, or Playstation controller and assigned it to a player.
Script Examples
PlayerController.csCameraMouse.csManager.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; /// Handles all inputs from the player. Currently consists of: /// - Movement /// - Jumping /// Authors: Abhi, David, Michael /// Code Resource: https://forum.unity.com/threads/proper-velocity-based-movement-101.462598/ public class PlayerController : MonoBehaviour { // Modifiable Attributes public int playerNum; public Transform head; public float speed; public float rotationSpeed; public float jumpSpeed; // Initial jump velocity public float jumpHoldTime; // Time the button can be held down to add force into the air public float gravityForce; // Force of gravity weighing down in air //Pause Menu Variables [SerializeField] private Manager manager; // Rigidbody for physics private Rigidbody body; // Store CameraMouse to access orbit dampening private CameraMouse cameraMouse; // Storing WizardAnimScript to control animations private WizardAnimScript animScript; // Stored values for axes private float vertical; private float horizontal; // Timers private float jumpTimer; // Flags public bool grounded = false; public bool canMove = true; // Start is called before the first frame update void Start() { jumpTimer = 0; body = GetComponent(); cameraMouse = head.gameObject.GetComponentInChildren (); animScript = this.gameObject.GetComponentInChildren (); manager = GameObject.Find("Manager").GetComponent (); } // Update is called once per frame void Update() { // If player presses Escape button/Start button if (Input.GetAxis("STA" + playerNum) != 0) { if (!Manager.isGamePaused && !Manager.resumingGame) { manager.PauseGame(); } else if (Manager.resumingGame) { Manager.resumingGame = false; } } } // Update is called once per frame void LateUpdate() { // If the game is paused, stop scene activities if (Manager.isGamePaused) { return; } if (!canMove) return; // Check if not on ground if (Physics.Raycast(transform.position, -transform.up, 0.5f)) { grounded = true; } else { grounded = false; } // Get axes values vertical = Input.GetAxis("LSV" + playerNum); horizontal = Input.GetAxis("LSH" + playerNum); // Check if player wants to jump and can jump if (Input.GetAxis("A" + playerNum) > 0 && grounded) { // Add force to simulate a jump body.AddForce(Vector3.up * jumpSpeed, ForceMode.Force); jumpTimer = 0; } else if (Input.GetAxis("A" + playerNum) != 0 && !grounded && jumpTimer < jumpHoldTime) { body.AddForce(Vector3.up * jumpSpeed, ForceMode.Force); jumpTimer += Time.deltaTime; } else if (Input.GetAxis("A" + playerNum) == 0 && !grounded) { // Set counter force body.AddForce(Vector3.down * gravityForce, ForceMode.Force); } else if (!grounded) { body.AddForce(Vector3.down * (gravityForce / 2f), ForceMode.Force); } // Add more speed only if it hasn't reached max speed if (body.velocity.x < speed) body.AddForce(transform.forward * vertical * speed, ForceMode.Force); if (body.velocity.z < speed) body.AddForce(transform.right * horizontal * speed, ForceMode.Force); // Project velocity onto the ground body.velocity = Vector3.ProjectOnPlane(body.velocity, Terrain.activeTerrain.terrainData.GetInterpolatedNormal(transform.position.x, transform.position.z)); // Deadzone for velocity if (body.velocity.magnitude < 0.1) { body.velocity = Vector3.zero; body.angularVelocity = Vector3.zero; } // Set horizontal rotation of the body to the horizontal rotation of the head transform.rotation = Quaternion.Euler(transform.rotation.y, cameraMouse.localRotation.x, 0f); /* ====================== ===== ANIMATIONS ===== ====================== */ // IF the player is grounded & the player is NOT moving... if (grounded && vertical == 0 && horizontal == 0) { // Playing the idle animation animScript.Idle(); } // IF the player is grounded & the player is moving... if (grounded && (vertical != 0f || horizontal != 0f)) { // Play the running animation animScript.Run(); } // IF the player presses the shoot button... if (Input.GetAxis("LT" + playerNum) > 0 || Input.GetAxis("RT" + playerNum) > 0) { // Play the shooting animation animScript.Shoot(); } } /// Gets the player number value. public int GetPlayerNum() { return playerNum; } }