Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity3D Third Person Controller and Animations

Tags:

unity3d

My problem is that a custom walk animation I have set in the third person controller of Unity3D is not shown.

The animation is imported from a FBX file with the [email protected] structure. I know the animation works, because if I use it as the idle animation it shows. On the other hand, it also doesn't seem to be an issue with the controller script, as it works fine with the prototype character.

It seems that the animation being played is the one selected in the Animation component (which has 'Play Automatically' selected). Any attempts to change the animation from the third person controller work for the prototype character, but not for mine.

I don't have nor want run and jump animations. With the prototype character I set the walk animation on each of those with no adverse effects. The controller doesn't turn off animation this way, seeing that there are no log entries in the console from the third person controller script. The relevant line with the CrossFade call is getting called.

Any clues as to where I could look next? Is it more likely an issue with the controller, or with the animation? Something else completely?

Update: Below is the code of my controller. It works fine when I use the sample model of the construction worker that is provided with Unity. The lines with _animation.CrossFade are getting called at the expected times. Using Play or Blend instead doesn't help. There are no errors logged in the console.

For our custom animations however it doesn't work. I am now suspecting the issues lies with the model. Unfortunately I am not at liberty to share a sample of that model. I've asked the animator for further details on how he created the FBX export. Are there any specific settings he needs to use for the model to work in Unity? It remains odd though that the animations do work if I add them indepently to the scene.

// Require a character controller to be attached to the same game object
@script RequireComponent(CharacterController)

public var idleAnimation : AnimationClip;
public var walkAnimation : AnimationClip;

public var walkMaxAnimationSpeed : float = 0.75;

private var _animation : Animation;

enum CharacterState {
    Idle = 0,
    Walking = 1,
}

private var _characterState : CharacterState;

// The speed when walking
var walkSpeed = 2.0;
var speedSmoothing = 10.0;
var rotateSpeed = 500.0;

var targetPrecision = 5;
var targetMaxDistance = 200;

// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.
private var lockCameraTimer = 0.0;

// The current move direction in x-z
private var moveDirection = Vector3.zero;
// The current x-z move speed
private var moveSpeed = 0.0;

// The last collision flags returned from controller.Move
private var collisionFlags : CollisionFlags; 

// Are we moving backwards (This locks the camera to not do a 180 degree spin)
private var movingBack = false;
// Is the user pressing any keys?
private var isMoving = false;

private var isControllable = true;

private var isTargetting : boolean = false;
private var targetPoint : Vector3 = Vector3.zero;

function Awake () {
    moveDirection = transform.TransformDirection(Vector3.forward);

    _animation = GetComponent(Animation);
    if(!_animation)
        Debug.Log("The character you would like to control doesn't have animations. Moving her might look weird.");

    if(!idleAnimation) {
        _animation = null;
        Debug.Log("No idle animation found. Turning off animations.");
    }
    //_animation[idleAnimation.name] = idleAnimation;

    if(!walkAnimation) {
        _animation = null;
        Debug.Log("No walk animation found. Turning off animations.");
    }
    //_animation[walkAnimation.name] = walkAnimation;
}

function UpdateSmoothedMovementDirection () {
    var cameraTransform = Camera.main.transform;

    // Forward vector relative to the camera along the x-z plane    
    var forward = cameraTransform.TransformDirection(Vector3.forward);
    forward.y = 0;
    forward = forward.normalized;

    // Right vector relative to the camera
    // Always orthogonal to the forward vector
    var right = Vector3(forward.z, 0, -forward.x);

    var v = Input.GetAxisRaw("Vertical");
    var h = Input.GetAxisRaw("Horizontal");

    // Are we moving backwards or looking backwards
    if (v < -0.2)
        movingBack = true;
    else
        movingBack = false;

    var wasMoving = isMoving;
    isMoving = Mathf.Abs (h) > 0.1 || Mathf.Abs (v) > 0.1;

    // Target direction relative to the camera
    var targetDirection = h * right + v * forward;

    // Lock camera for short period when transitioning moving & standing still
    lockCameraTimer += Time.deltaTime;
    if (isMoving != wasMoving)
        lockCameraTimer = 0.0;

    // We store speed and direction seperately,
    // so that when the character stands still we still have a valid forward direction
    // moveDirection is always normalized, and we only update it if there is user input.
    if (targetDirection != Vector3.zero) {
        // If we are really slow, just snap to the target direction
        if (moveSpeed < walkSpeed * 0.9) {
            moveDirection = targetDirection.normalized;
        }
        // Otherwise smoothly turn towards it
        else {
            moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
            moveDirection = moveDirection.normalized;
        }
    }

    // Smooth the speed based on the current target direction
    var curSmooth = speedSmoothing * Time.deltaTime;

    // Choose target speed
    //* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
    var targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0);

    _characterState = CharacterState.Idle;

    // Pick speed modifier
    targetSpeed *= walkSpeed;
    _characterState = CharacterState.Walking;

    moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);
}


function UpdateTargettedMovementDirection () {
    var cameraTransform = Camera.main.transform;

    var wasMoving = isMoving;
    isMoving = true;//Mathf.Abs (h) > 0.1 || Mathf.Abs (v) > 0.1;

    // Target direction relative to the camera
    // var targetDirection = h * right + v * forward;
    var targetDirection = Vector3.zero;
    targetDirection.x =  targetPoint.x - transform.position.x;
    targetDirection.z =  targetPoint.z - transform.position.z;
    targetDirection = targetDirection.normalized;
    //Debug.Log("Target direction is " + targetDirection);

    // Lock camera for short period when transitioning moving & standing still
    lockCameraTimer += Time.deltaTime;
    if (isMoving != wasMoving)
        lockCameraTimer = 0.0;

    // We store speed and direction seperately,
    // so that when the character stands still we still have a valid forward direction
    // moveDirection is always normalized, and we only update it if there is user input.
    if (targetDirection != Vector3.zero) {
        // If we are really slow, just snap to the target direction
        if (moveSpeed < walkSpeed * 0.9) {
            moveDirection = targetDirection.normalized;
        }
        // Otherwise smoothly turn towards it
        else {
            moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
            moveDirection = moveDirection.normalized;
        }
    }

    // Smooth the speed based on the current target direction
    var curSmooth = speedSmoothing * Time.deltaTime;

    // Choose target speed
    //* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
    var targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0);

    _characterState = CharacterState.Idle;

    // Pick speed modifier
    targetSpeed *= walkSpeed;
    _characterState = CharacterState.Walking;

    moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);      
}

function Update() {
    if (!isControllable) {
        // kill all inputs if not controllable.
        Input.ResetInputAxes();
    }

    var distance : float = 0;
    if (Input.GetMouseButtonUp(0)) {
        if (isTargetting) {
            isTargetting = false;
            // Debug.Log("Stopped moving");
            FaceCamera();
        } else {
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            var layerMask = 1 << 8; // Terrain is layer 8
            var hit : RaycastHit;
            Physics.Raycast(Camera.main.transform.position, ray.direction, hit, 1000, layerMask);
            distance = Vector3.Distance(transform.position, hit.point);
            if (distance <= targetMaxDistance && hit.point != Vector3.zero) {
                targetPoint = hit.point;
                isTargetting = true;
                // Debug.Log("Mouse up at hit " + hit.point + " at distance " + distance);
            } else {
                isTargetting = false;
                // Debug.Log("Ignored mouse up at hit " + hit.point + " at distance " + distance);
            }
        }
    }

    if (isTargetting) {
        // Debug.Log("Moving to " + targetPoint);
        distance = Vector3.Distance(transform.position, targetPoint);
        if (distance < targetPrecision) {
            // Debug.Log("Reached point " + targetPoint + " at distance " + distance);
            isTargetting = false;
            FaceCamera();
        } else {
            UpdateTargettedMovementDirection();
        }
    } else {
        UpdateSmoothedMovementDirection();
    }

    // Calculate actual motion
    var movement = moveDirection * moveSpeed;
    movement *= Time.deltaTime;

    // Move the controller
    var controller : CharacterController = GetComponent(CharacterController);
    collisionFlags = controller.Move(movement);

    // ANIMATION sector
    if (_animation) {       
        if (controller.velocity.sqrMagnitude < 0.1) {
            _animation.CrossFade(idleAnimation.name);
        } else {
            //_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0, walkMaxAnimationSpeed);
            _animation.CrossFade(walkAnimation.name);       
        }
    } else {
         Debug.Log("Animation is null!");
    }
    // ANIMATION sector

    // Set rotation to the move direction
    transform.rotation = Quaternion.LookRotation(moveDirection);
}

function OnControllerColliderHit (hit : ControllerColliderHit ) {
//  Debug.DrawRay(hit.point, hit.normal);
    if (hit.moveDirection.y > 0.01) 
        return;
}

function GetSpeed () {
    return moveSpeed;
}

function GetDirection () {
    return moveDirection;
}

function IsMovingBackwards () {
    return movingBack;
}

function GetLockCameraTimer () {
    return lockCameraTimer;
}

function IsMoving () : boolean {
    return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5;
}

function Reset () {
    gameObject.tag = "Player";
}

function FaceCamera() {
    var cameraTransform = Camera.main.transform;

    // Forward vector relative to the camera along the x-z plane    
    var forward = cameraTransform.TransformDirection(Vector3.forward);
    forward.y = 0;
    forward = forward.normalized;

    moveDirection = -forward;
}

Update 2: The settings used to create the animations are in these screenshots. Are they correct? Animation export setting 1Animation export setting 2

like image 849
Johan Kool Avatar asked May 01 '12 06:05

Johan Kool


1 Answers

Not (yet :-) an answer but I need more space and images.

Since 3.5 Unity3d the way to handle imports based on [email protected] notation has changed and some people reported trouble (s. for example BIG Unity 3.5.0f1 problem with Skinned Rig - Major FAIL). The problem arises when a 2nd root bone comes to play but I could solve this by dragging the animations to the model file prefab (most of my animations are contained in the model and the remaining 2 are no big pain).

Just to be really sure that I understood your answer in the comments section right, you have something like this:

enter image description here

That means:

  • Within the character model the animations array contains all animations
  • The number of bones and all names are exactly like in your prototype character
  • If you open an animation view, you can see a read-only list of all animations of the character selected in hierarchy view

Assuming this is fine some more suggestions:

  • I can't see any place in the code where you set WrapMode.Loop or animation speed. Are you sure it is configured as Loop in inspector and is not overwritten somewhere.
  • CrossFade with no parameters assumes 0.3 seconds
  • What happens after the cross fade call? Does the DefaulTake stops playing?
  • Can you drag the Walk animation as default
  • Do you use different animation layers?
  • What output do you get when you set up Debug.Log ("Walk: " + player.animation.IsPlaying ("Walk"));

[Update]

  1. Let's look at the import settings. Do you have Split Animations checked and are there maybe wrong values entered at Start and End like 1-1?

    Animation Import Settings

  2. Regarding Toggling of IsPlaying: Please create more output about the AnimationState properties. Most notable speed, length, enabled, weight, ... I have a suspicion that the animation is too short or played too fast.
  3. A walk animation that is no bone animation sounds a bit strange. On the other hand console would cry out loud if bone names are not matching. So I assume there is no problem now.
like image 157
Kay Avatar answered Nov 08 '22 19:11

Kay