Skip to content

Instantly share code, notes, and snippets.

@Matthew-J-Spencer
Created July 6, 2022 11:30
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save Matthew-J-Spencer/7f2d3efa507656cde1b73071daf01d61 to your computer and use it in GitHub Desktop.
Save Matthew-J-Spencer/7f2d3efa507656cde1b73071daf01d61 to your computer and use it in GitHub Desktop.
Control unity animations using code. Video: https://youtu.be/ZwLekxsSY3Y
using System;
using UnityEngine;
using UnityEngine.Tilemaps;
using Random = UnityEngine.Random;
public class PlayerAnimator : MonoBehaviour {
[SerializeField] private float _minImpactForce = 20;
// Anim times can be gathered from the state itself, but
// for the simplicity of the video...
[SerializeField] private float _landAnimDuration = 0.1f;
[SerializeField] private float _attackAnimTime = 0.2f;
private IPlayerController _player;
private Animator _anim;
private SpriteRenderer _renderer;
private bool _grounded;
private float _lockedTill;
private bool _jumpTriggered;
private bool _attacked;
private bool _landed;
private void Awake() {
if (!TryGetComponent(out IPlayerController player)) {
Destroy(this);
return;
}
_player = player;
_anim = GetComponent<Animator>();
_renderer = GetComponent<SpriteRenderer>();
}
private void Start() {
_player.Jumped += () => {
_jumpTriggered = true;
};
_player.Attacked += () => {
_attacked = true;
};
_player.GroundedChanged += (grounded, impactForce) => {
_grounded = grounded;
_landed = impactForce >= _minImpactForce;
};
}
private void Update() {
if (_player.Input.x != 0) _renderer.flipX = _player.Input.x < 0;
var state = GetState();
_jumpTriggered = false;
_landed = false;
_attacked = false;
if (state == _currentState) return;
_anim.CrossFade(state, 0, 0);
_currentState = state;
}
private int GetState() {
if (Time.time < _lockedTill) return _currentState;
// Priorities
if (_attacked) return LockState(Attack, _attackAnimTime);
if (_player.Crouching) return Crouch;
if (_landed) return LockState(Land, _landAnimDuration);
if (_jumpTriggered) return Jump;
if (_grounded) return _player.Input.x == 0 ? Idle : Walk;
return _player.Speed.y > 0 ? Jump : Fall;
int LockState(int s, float t) {
_lockedTill = Time.time + t;
return s;
}
}
#region Cached Properties
private int _currentState;
private static readonly int Idle = Animator.StringToHash("Idle");
private static readonly int Walk = Animator.StringToHash("Walk");
private static readonly int Jump = Animator.StringToHash("Jump");
private static readonly int Fall = Animator.StringToHash("Fall");
private static readonly int Land = Animator.StringToHash("Land");
private static readonly int Attack = Animator.StringToHash("Attack");
private static readonly int Crouch = Animator.StringToHash("Crouch");
#endregion
}
public interface IPlayerController {
public Vector2 Input { get; }
public Vector2 Speed { get; }
public bool Crouching { get; }
public event Action<bool, float> GroundedChanged; // Grounded - Impact force
public event Action Jumped;
public event Action Attacked;
}
@stewartteles
Copy link

Hi, can anyone help?
var state = _player.Input.x == 0 ? Idle : Walk; (Error: NullReferenceException: Object reference not set to an instance of an object ) _player.Input.x not set

@MrTallon
Copy link

test gist

@takahebi
Copy link

takahebi commented Aug 1, 2022

hey can you comment this so it's a little easier to follow? the video is a little quick and i am not familiar with lock state. it would be pretty epic if this were commented so it's easier to learn from

@idbrii
Copy link

idbrii commented Aug 25, 2022

@stewartteles: Probably a Vector2 isn't returned from your IPlayerController. IPlayerController should be a MonoBehaviour that implements IPlayerController and the Input get returns a value from the Unity Input System. See tarodev's IPlayerController implementation in the IPlayerController video.

@takahebi: If you have a sword swing attack, then you want it to play out completely and not just while the attack button is held. So you lock the character into the attack state until the anim completes. LockState is a bit weird because it's a nested function, but you could move it out of GetState and it would be the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment