Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Correctly play animation based on player cursor and movement (Top-Down Shooter 3D)?

Discussion in 'Animation' started by Saucyminator, Oct 10, 2017.

  1. Saucyminator

    Saucyminator

    Joined:
    Nov 23, 2015
    Posts:
    15
    Hi, I'm trying to play the correct animation for a Top-Down Shooter in 3D, where the player is aiming and moving relative to the mouse cursor.

    I've looked at Unity's Survival Shooter and gotten pretty far with what they've got in the project, but it lacks more complex animations for their character.

    What I got so far:
    • Basic movement, player presses W and goes north, S to the south, etc.
    • Player aiming at mouse position.
    • Model with animations (from Mixamo), see my blend tree image below.
      • Animation controller have two floats, Speed and Strafe (for forward/backward and left/right).



    What I'm missing:
    • C# code that send correct values to my animation controller parameters.
    • I can't figure out how to play the correct animation for example:
      • Player aims to the east but moves to the north - It should play the Run Left animation.
      • Player aims to the west but also moves to the north - it should play the Run Right animation.
      • etc..
    • (I'll try putting up a Git Repo with the stuff I got so far, but I need to clean it up some first.)
    Here's a pretty clear video of what I'm trying to recreate (got inspired by this video!)
     
    C_R_M likes this.
  2. RevoltingProductions

    RevoltingProductions

    Joined:
    May 2, 2017
    Posts:
    37
    You probably need to address root animation.
     
  3. Saucyminator

    Saucyminator

    Joined:
    Nov 23, 2015
    Posts:
    15
    The player moves very unnatural if I enable Root Motion.

    Player.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent(typeof(PlayerController))]
    5. [RequireComponent(typeof(GunController))]
    6. public class Player : Character {
    7.   public float moveSpeed = 5;
    8.   public Crosshairs crosshairs;
    9.  
    10.   private Camera viewCamera;
    11.   private PlayerController controller;
    12.   private GunController gunController;
    13.   private Animator animator;
    14.   private float speed;
    15.   private Vector3 lastPos;
    16.   private Vector3 mousePosition;
    17.  
    18.   void Awake () {
    19.     animator = GetComponent<Animator>();
    20.     controller = GetComponent<PlayerController>();
    21.     gunController = GetComponent<GunController>();
    22.     viewCamera = Camera.main;
    23.     gunController.EquipGun(5);
    24.   }
    25.  
    26.   protected override void Start () {
    27.     base.Start();
    28.   }
    29.  
    30.   void Update () {
    31.     // Movement input
    32.     float horizontal = Input.GetAxisRaw("Horizontal");
    33.     float vertical = Input.GetAxisRaw("Vertical");
    34.     Vector3 _moveInput = new Vector3(horizontal, 0, vertical);
    35.     Vector3 moveVelocity = _moveInput.normalized * moveSpeed;
    36.     controller.Move(moveVelocity);
    37.  
    38.     // Look input
    39.     Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
    40.     Plane groundPlane = new Plane(Vector3.up, Vector3.up * gunController.GunHeight);
    41.     float rayDistance;
    42.  
    43.     if (groundPlane.Raycast(ray, out rayDistance)) {
    44.       mousePosition = ray.GetPoint(rayDistance);
    45.  
    46.       controller.LookAt(mousePosition);
    47.       crosshairs.transform.position = mousePosition;
    48.       crosshairs.DetectTargets(ray);
    49.  
    50.       if ((new Vector2(mousePosition.x, mousePosition.z) - new Vector2(transform.position.x, transform.position.z)).sqrMagnitude > 1) {
    51.         gunController.Aim(mousePosition);
    52.       }
    53.     }
    54.  
    55.     // Weapon input
    56.     if (Input.GetMouseButton(0)) {
    57.       gunController.OnTriggerHold();
    58.     }
    59.     if (Input.GetMouseButtonUp(0)) {
    60.       gunController.OnTriggerRelease();
    61.     }
    62.     if (Input.GetKeyDown(KeyCode.R)) {
    63.       gunController.Reload();
    64.     }
    65.  
    66.     // Animations
    67.     animator.SetFloat("Speed", vertical);
    68.     animator.SetFloat("Strafe", horizontal);
    69.   }
    70.  
    71.   public override void Die () {
    72.     AudioManager.instance.PlaySound("Player Death", transform.position);
    73.     animator.SetTrigger("Die");
    74.     base.Die();
    75.   }
    76. }
    PlayerController.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent(typeof(Rigidbody))]
    5. public class PlayerController : MonoBehaviour {
    6.   private Vector3 velocity;
    7.   private Rigidbody myRigidbody;
    8.  
    9.   void Start () {
    10.     myRigidbody = GetComponent<Rigidbody>();
    11.   }
    12.  
    13.   public void Move (Vector3 _velocity) {
    14.     velocity = _velocity;
    15.   }
    16.  
    17.   public void LookAt (Vector3 lookPoint) {
    18.     Vector3 heightCorrectedPoint = new Vector3(lookPoint.x, transform.position.y, lookPoint.z);
    19.     transform.LookAt(heightCorrectedPoint);
    20.   }
    21.  
    22.   void FixedUpdate () {
    23.     myRigidbody.MovePosition(myRigidbody.position + velocity * Time.fixedDeltaTime);
    24.   }
    25. }
    Character.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Character : MonoBehaviour, IDamageable {
    5.   public event System.Action OnDeath;
    6.   public float startingHealth;
    7.   public float health { get; protected set; }
    8.  
    9.   protected bool dead;
    10.  
    11.   protected virtual void Start () {
    12.     health = startingHealth;
    13.   }
    14.  
    15.   public virtual void TakeHit (float damage, Vector3 hitPoint, Vector3 hitDirection) {
    16.     // Do some stuff here with hit var
    17.     TakeDamage(damage);
    18.   }
    19.  
    20.   public virtual void TakeDamage (float damage) {
    21.     health -= damage;
    22.  
    23.     if (health <= 0 && !dead) {
    24.       Die();
    25.     }
    26.   }
    27.  
    28.   [ContextMenu("Self Destruct")]
    29.   public virtual void Die () {
    30.     dead = true;
    31.     if (OnDeath != null) {
    32.       OnDeath();
    33.     }
    34.     GameObject.Destroy(gameObject);
    35.   }
    36. }
     
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    6,432
    Transform.TransformVector to transform the direction you are looking against the input direction.

    Good, scaleable locomotion is a really difficult thing to do especially when you want to support root motion. You've got to design for fake, controlled transform responses as well as accomodate when the animation is and should be allowed to use it's root motion. Rigidbody and physics support add another complicated layer to consider.

    It's tedious work, and you're going to find that it's easy to do the basics but hard to support more advanced features. If you're working alone, I recommend setting some clear restrictions on design and animation assets so you can reduce the amount of work.

    I suppose I can plug Deftly, since it's a TDS kit, but I'm working on a large update that handles things more elegantly, so you might prefer to wait (if you were interested in a kit).
     
  5. Saso222

    Saso222

    Joined:
    Feb 17, 2015
    Posts:
    4
    Hey,

    Sorry to kind of bump this, but I wanted to show our approach to this problem.

    We're trying something similar to this. I suggest you checking out Battlerite, from Stunlock Studios since their animation and locomotion systems are sweet.

    What I can share with you is the way our PlayerController is sending those "horizontal" and "vertical" variables to the animator so the correct animation is played:

    Code (CSharp):
    1.  
    2.     private void UpdateAnimator()
    3.     {
    4.             float forwardBackwardsMagnitude = 0;
    5.             float rightLeftMagnitude = 0;
    6.             if (axisVector.magnitude > 0) {
    7.                 Vector3 normalizedLookingAt = lookedAtPoint - transform.position;
    8.                 normalizedLookingAt.Normalize ();
    9.                 forwardBackwardsMagnitude = Mathf.Clamp (
    10.                         Vector3.Dot (axisVector, normalizedLookingAt), -1, 1
    11.                 );
    12.  
    13.                 Vector3 perpendicularLookingAt = new Vector3 (
    14.                        normalizedLookingAt.z, 0, -normalizedLookingAt.x
    15.                 );
    16.                 rightLeftMagnitude = Mathf.Clamp (
    17.                        Vector3.Dot (axisVector, perpendicularLookingAt), -1, 1
    18.                );
    19.  
    20.                 animator.SetBool("IsMoving", true);
    21.  
    22.             } else
    23.             {
    24.                 animator.SetBool("IsMoving", false);
    25.             }
    26.  
    27.             // update the animator parameters
    28.             animator.SetFloat ("Forward", forwardBackwardsMagnitude);
    29.             animator.SetFloat ("Right", rightLeftMagnitude);
    30.     }
    31.  
    This is, of course, being executed each frame.
    As you can see, we calculate the forward magnitude by taking the normalized version of the vector that goes from the actual character position to the point he's looking at (the mouse cursor previously projected to the world space) and calculating the dot product between that and the axisVector, which is the vector given by:


    axisVector = new Vector3(
    Input.GetAxis("Horizontal"),
    0,
    Input.GetAxis("Vertical")
    );


    gives us the magnitude of the projection of the axis vector onto the forward vector of the character.

    Making the same stuff with the normalized perpendicular of the "lookAtVector" gives us the magnitude of the projection of the axisVector onto the side vector of the character.

    I hope I could make that clear. It's working as expected. Feel free to ask anything.

    By the way, we are using RootMotion, so the movement is done by the animations.

    Best
     
    Last edited: Apr 19, 2018
    C_R_M likes this.
  6. BCU_Euden

    BCU_Euden

    Joined:
    Mar 14, 2013
    Posts:
    11
    This was perfect! It really helped me sort out my animations. I wasn't using root motion but this is a great solution!