During the initial concept of our project, we knew that we wanted the game to be 2D but we were not sure whether it was best to use an isometric or top-down view. After Alex spent some time drawing isometric art we soon realised that this process would be too time-consuming at that it would be more effective to use a top-down view for our player.
When coming up with the idea of the game we draw inspiration from games such as Enter the Gungeon and this is what we wanted our player to control like. We knew we were not going to use 8D sprites for a player but 4D this means the player would either being facing NE, SE, SW, NW. Below is the initial code from the player controller as you can see an enum is used to store the player direction, when the player is moving in the appropriate direction the enum is updated with this direction which in turn changes the colour of the players sprite, visually show which direction they are walking in.
public class PlayerController : MonoBehaviour
{
public enum PlayerDirection {
Left,
Right,
Up,
Down
}
public PlayerDirection Direction;
private float _Vertical;
private float _Horizontal;
private float _moveLimiter = 0.7f;
[Tooltip("This describes how fast the player will move")]
[Range(0f, 30f)]
[SerializeField] protected float f_RunSpeed = 0.0f;
protected Rigidbody2D rigidBody2D;
public SpriteRenderer spriteRenderer;
void Start()
{
rigidBody2D = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
_Horizontal = Input.GetAxisRaw("Horizontal");
_Vertical = Input.GetAxisRaw("Vertical");
}
private void FixedUpdate() {
if (_Horizontal != 0 && _Vertical != 0) {
_Horizontal *= _moveLimiter;
_Vertical *= _moveLimiter;
}
if(_Horizontal == 1) {
Direction = PlayerDirection.Right;
} else if (_Horizontal == -1) {
Direction = PlayerDirection.Left;
} else if (_Vertical == 1) {
Direction = PlayerDirection.Up;
} else if (_Vertical == -1) {
Direction = PlayerDirection.Down;
}
rigidBody2D.velocity = new Vector2(_Horizontal * f_RunSpeed, _Vertical * f_RunSpeed);
ChangePlayerSprite();
}
public void ChangePlayerSprite() {
switch (Direction) {
case PlayerDirection.Left:
spriteRenderer.color = Color.red;
return;
case PlayerDirection.Right:
spriteRenderer.color = Color.blue;
return;
case PlayerDirection.Up:
spriteRenderer.color = Color.yellow;
return;
case PlayerDirection.Down:
spriteRenderer.color = Color.green;
return;
default:
break;
}
}
}
Now that the player could move around the scene it was time to give them a weapon, the direction that the weapon is facing determines which direction the player should be facing. This means that the rotation script must link to the player controller script. Below is the script used to rotate the gun around a set pivot point, as you can see based on the z axis of the transform of the game object this script is attached to will determine which was the player should be facing, this information is used to update the players sprite. You will also notice that the enum PlayerDirection is no longer being used as it originally was, PlayerDirection is now used to determine if the player is facing forward or backwards, this helps to play animations such as the IDLE. A new enum was used called GunDirection that can tell if the gun is facing 1 of 4 quadrants, NE, SE, SW, NW, when the direction changes, a bool attached to the animator of the player has a parameter called SetGunDirection that will then enter the correct state.
public class GunRotate : MonoBehaviour
{
public PlayerController controller;
private void FixedUpdate() {
Vector3 diffrence = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
diffrence.Normalize();
float rotaionZ = Mathf.Atan2(diffrence.y, diffrence.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotaionZ);
if(transform.eulerAngles.z >= 0 && transform.eulerAngles.z <= 89) {
controller.SetGunDirection(GunDirection.NE);
controller.SetPlayerDirection(PlayerDirection.BACK);
}
if (transform.eulerAngles.z >= 180 && transform.eulerAngles.z <= 269) {
controller.SetGunDirection(GunDirection.SW);
controller.SetPlayerDirection(PlayerDirection.FRONT);
}
if (transform.eulerAngles.z >= 89 && transform.eulerAngles.z <= 179) {
controller.SetGunDirection(GunDirection.NW);
controller.SetPlayerDirection(PlayerDirection.BACK);
}
if (transform.eulerAngles.z >= 269 && transform.eulerAngles.z <= 360) {
controller.SetGunDirection(GunDirection.SE);
controller.SetPlayerDirection(PlayerDirection.FRONT);
}
}
}
Visual representation of how the gun rotation works
Player animator tree
Now that the player could move and look around it was time to add the final core mechanic of our player which would be the ability to dash. The following code is how the dash works and I’ll explain how. First of all a bool is used that sets whether the player can dash or not as well as another variable called isDashing which is used to track if the player is dashing, this is done because isDashing is linked to the time of the actual dash while canDash is used for the total cooldown of the players dash. We then use the players direction to get where they are facing and once they press ‘LShift’ in the update function then this coroutine the player dashes in the direction they are facing, the player dashes as a velocity is being added to the player for the duration of dashingTime, once this time is up the player stops dashing and the dash is then on cooldown.
For a test this is fine but in the final version of the dash we want the player to dash in all 8 directions (N, NE, E, SE, S, SW, W, NW).
private IEnumerator Dash() {
canDash = false;
isDashing = true;
float originalGravity = rigidBody2D.gravityScale;
rigidBody2D.gravityScale = 0.0f;
switch (gunDirection) {
case GunDirection.NE:
rigidBody2D.velocity = new Vector2(transform.localScale.x * dashingPower * _moveLimiter, transform.localScale.y * dashingPower * _moveLimiter);
break;
case GunDirection.NW:
rigidBody2D.velocity = new Vector2(-transform.localScale.x * dashingPower * _moveLimiter, transform.localScale.y * dashingPower * _moveLimiter);
break;
case GunDirection.SW:
rigidBody2D.velocity = new Vector2(-transform.localScale.x * dashingPower * _moveLimiter, -transform.localScale.y * dashingPower * _moveLimiter);
break;
case GunDirection.SE:
rigidBody2D.velocity = new Vector2(transform.localScale.x * dashingPower * _moveLimiter, -transform.localScale.y * dashingPower * _moveLimiter);
break;
default:
break;
}
//rigidBody2D.velocity = new Vector2(transform.localScale.x * dashingPower, transform.localScale.y * dashingPower);
trailRenderer.emitting = true;
spriteRenderer.enabled = false;
yield return new WaitForSeconds(dashingTime);
spriteRenderer.enabled = true;
trailRenderer.emitting = false;
rigidBody2D.gravityScale = originalGravity;
isDashing = false;
yield return new WaitForSeconds(dashingCooldown);
canDash = true;
}