Ok, I’m bumping this thread with code this time, because this is seriously holding up development on this project. A lot of this code is simply used for animation blending and syncing of animation state over the network. The key aspects are in the Update() and scaleAvatar() functions:
using UnityEngine;
using System.Collections;
public class AvatarAnimator : Avatar {
/*
* AvatarAnimator.cs
* by Aaron Siegel, 6-21-2010
*
* Acts as animation blend controller as well as network view sync and RPC call syncing.
*/
public float walkSpeed = 10f;//2.3f;
public float runSpeed = 20f;//3.6f;
public float idleThreshold = 0.3f;
public float speedSmoothing = 8.0f;
public float gravity = 10.0f;
private AnimationState run;
private AnimationState walk;
private float currentSpeed = 0.0f;
private float smoothedSpeed = 0.0f;
private Vector3 lastPos;
private Vector3 pos;
// scaling variables
float maxScale = 7500.0f;
float minScale = 5000.0f;
float currentScale;
float fullZspace = 366;
float zscale;
private bool client = false;
void Start () {
animations = new string[2];
// We are in full control here - don't let any other animations play when we start
animation.Stop();
// By default loop all animations
animation.wrapMode = WrapMode.Loop;
// setup list of animations for reference when setting current animation via RPC
animations[0] = "walk";
animations[1] = "runSlow";
walk = animation["walk"];
run = animation["runSlow"];
// Enable all walk / run cycles
// We will manually adjust the blend weights every frame
walk.enabled = true;
run.enabled = true;
// Synchronize all walk / run cycle animations
animation.SyncLayer(0);
// Put the idle animation in a lower layer.
// This will make it only play if no walk / run cycle is faded in
animation["idle"].layer = -1;
animation["idle"].enabled = true;
animation["idle"].weight = 1;
// discover if this instance is a client or the server
GameObject conductor = GameObject.Find("Conductor");
client = System.Convert.ToBoolean((conductor.GetComponent("Conductor") as Conductor).props.getProperty("client"));
}
// this provides interaction with rigidbody objects or other colliders
void OnControllerColliderHit(ControllerColliderHit hit){
float pushPower = 2.0f;
Rigidbody body = hit.collider.attachedRigidbody;
if(body == null || body.isKinematic){
return;
} else if(hit.moveDirection.y < -0.3){
return;
}
Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
body.velocity = pushDir * pushPower;
}
// this serializes the current animation state and sends it out at a steady rate
void OnSerializeNetworkView (BitStream stream, NetworkMessageInfo info) {
float animTime = 0;
if (stream.isWriting){
animTime = animation[animations[currentAnimation]].normalizedTime;
stream.Serialize(ref animTime);
} else {
stream.Serialize(ref animTime);
animation[animations[currentAnimation]].normalizedTime = animTime;
}
}
[RPC]
void setAvatarPosition(Vector3 pos){
lastPos = transform.position;
transform.position = pos;
//this.pos = pos;
}
[RPC]
void setAvatarRotation(Quaternion rot){
transform.rotation = rot;
}
[RPC]
void setAvatarSpeed(float newSpeed){
currentSpeed = newSpeed;
}
[RPC]
void setAnimationState(int animState){
currentAnimation = animState;
}
[RPC]
void setAnimationPosition(float normPos){
animation[animations[currentAnimation]].normalizedTime = normPos;
}
void scaleAvatar(){
Transform curTransform = (Transform) GetComponent("Transform");
zscale = (fullZspace - transform.position.z) / fullZspace;
currentScale = ((maxScale - minScale) * zscale) + minScale;
//print(transform.position.z +" "+ currentScale);
curTransform.localScale = new Vector3(currentScale, currentScale, currentScale);
}
void Update () {
if(!client){
// check person object position for latest tracking data
lastPos = transform.position;
pos = person.getPosition();
// scale avatar to fake depth effect
scaleAvatar();
// look towards new position
Vector3 offset = pos - transform.position;
offset.y = 0; // ignore Y height so character doesn't rotate along the X axis
if(offset.x != 0 offset.z != 0){
transform.rotation = Quaternion.LookRotation(offset,Vector3.up);
// move to new position
currentSpeed = Vector3.Distance(pos, lastPos);
CharacterController cc = (CharacterController)GetComponent ("CharacterController");
Vector3 forward = transform.TransformDirection(Vector3.forward);
cc.SimpleMove(forward * currentSpeed );
if(!cc.isGrounded){
transform.position = new Vector3(transform.position.x, transform.position.y - (gravity * Time.deltaTime), transform.position.z);
}
networkView.RPC("setAvatarPosition", RPCMode.All, transform.position);
networkView.RPC("setAvatarRotation", RPCMode.All, transform.rotation);
networkView.RPC("setAvatarSpeed", RPCMode.All, currentSpeed);
}
}
smoothedSpeed = Mathf.Lerp(smoothedSpeed, currentSpeed, Time.deltaTime * speedSmoothing);
// Calculate the weight between walk and run from the current speed
float runWeight = Mathf.InverseLerp(walkSpeed, runSpeed, Mathf.Abs(smoothedSpeed));
// adjust the animation playback to match the characters speed
run.speed = smoothedSpeed / runSpeed;
walk.speed = smoothedSpeed / walkSpeed;
// When the character slows down fade out the run and walk weights
if (Mathf.Abs(smoothedSpeed) < idleThreshold){
run.weight -= Time.deltaTime * 5.0f;
walk.weight -= Time.deltaTime * 5.0f;
} else {
float totalWeight = run.weight + walk.weight;
totalWeight += Time.deltaTime * 12;
totalWeight = Mathf.Clamp01(totalWeight);
// Set the weights based on the current speed
run.weight = runWeight * totalWeight;
walk.weight = (1.0f - runWeight) * totalWeight;
}
}
}
The Avatar class is just a simple generic super class extending MonoBehavior to be used with the 3D characters in the scene, and I don’t think it is of much concern while assessing this issue:
using UnityEngine;
using System.Collections;
public class Avatar : MonoBehaviour {
/*
* Avatar.cs
* by Aaron Siegel, 6-10-2010
*
* Super class for all 3D model attributes used to interact with one or more person objects.
*/
protected int currentAnimation = 0;
public string[] animations;
protected Person person;
public void setPerson(Person person){
this.person = person;
}
public void setPosition(Vector3 pos){
transform.position = pos;
}
public void setRotation(Quaternion rot){
transform.rotation = rot;
}
public void setAnimationState(int animState){
currentAnimation = animState;
}
public void setAnimationPosition(float normPos){
animation[animations[currentAnimation]].normalizedTime = normPos;
}
}
I am positive that this has something to do with the order of operations. I noticed a lot of scripts applying gravity and movement to character controllers inside FixedUpdate instead of Update.
The problems I’m encountering are:
(1) If I give them no gravity and they’re too high, they just walk in place in the sky and never go anywhere.
(2) If I give them gravity, they usually end up falling completely through the ground.
(3) If I don’t give them gravity and start them lower in the hopes of them colliding with the ramp to begin with, they usually just intersect and stay stuck in place.
Any advice would be GREATLY appreciated.