using System; using AtomicEngine; public class Character : CSComponent { void Start() { SubscribeToEvent("NodeCollision", HandleNodeCollision); } void FixedUpdate(float timeStep) { /// TODO: Could cache the components for faster access instead of finding them each frame /// Also, switch to generic version of GetComponent /// RigidBody body = (RigidBody)Node.GetComponent("RigidBody"); AnimationController animCtrl = (AnimationController)Node.GetComponent("AnimationController", true); // Update the in air timer. Reset if grounded if (!onGround) inAirTimer += timeStep; else inAirTimer = 0.0f; // When character has been in air less than 1/10 second, it's still interpreted as being on ground bool softGrounded = inAirTimer < INAIR_THRESHOLD_TIME; // Update movement & animation Quaternion rot = Node.Rotation; Vector3 moveDir = Vector3.Zero; Vector3 velocity = body.LinearVelocity; // Velocity on the XZ plane Vector3 planeVelocity = new Vector3(velocity.X, 0.0f, velocity.Z); var input = GetSubsystem(); if (input.GetKeyDown(Constants.KEY_W)) { moveDir += Vector3.Forward; } if (input.GetKeyDown(Constants.KEY_S)) { moveDir += Vector3.Back; } if (input.GetKeyDown(Constants.KEY_A)) { moveDir += Vector3.Left; } if (input.GetKeyDown(Constants.KEY_D)) { moveDir += Vector3.Right; } float breakAdjust = ((float)input.MouseMoveWheel) * timeStep * 4.0f; breakForce -= breakAdjust; breakForce = Clamp(breakForce, MIN_BRAKE_FORCE, MAX_BRAKE_FORCE); // Normalize move vector so that diagonal strafing is not faster if (moveDir.LengthSquared > 0.0f) moveDir.Normalize(); // If in air, allow control, but slower than when on ground body.ApplyImpulse(rot * moveDir * (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE)); if (softGrounded) { // When on ground, apply a braking force to limit maximum ground velocity Vector3 brakeForce = -planeVelocity * breakForce; body.ApplyImpulse(brakeForce); // Jump. Must release jump control inbetween jumps if (input.GetKeyDown(Constants.KEY_SPACE)) { if (okToJump) { body.ApplyImpulse(Vector3.Up * JUMP_FORCE); okToJump = false; animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f); } } else okToJump = true; } if (!onGround) { animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f); } else { // Play walk animation if moving on ground, otherwise fade it out if (softGrounded && !moveDir.Equals(Vector3.Zero)) animCtrl.PlayExclusive("Models/Mutant/Mutant_Run.ani", 0, true, 0.2f); else animCtrl.PlayExclusive("Models/Mutant/Mutant_Idle0.ani", 0, true, 0.2f); // Set walk animation speed proportional to velocity animCtrl.SetSpeed("Models/Mutant/Mutant_Run.ani", planeVelocity.Length * 0.3f); } // Reset grounded flag for next frame onGround = false; } void HandleNodeCollision(uint eventType, ScriptVariantMap eventData) { PhysicsNodeCollision nodeCollision = eventData.GetPtr("PhysicsNodeCollision"); if (nodeCollision == null) return; // TODO: We need to be able to subscribe to specific object's events if (nodeCollision.OtherNode == Node) { var nodePosition = Node.Position; for (uint i = 0; i < nodeCollision.Contacts.Size; i++) { var contact = nodeCollision.Contacts.At(i); var contactPosition = contact.Position; // TODO: This needs to be flipped when can listen to specific node // If contact is below node center and pointing up, assume it's a ground contact if (contactPosition.Y > (nodePosition.Y - 1.0f)) { float level = contact.Normal.Y; if (level < 0.75f) onGround = true; } } } } const float MOVE_FORCE = 0.8f; const float INAIR_MOVE_FORCE = 0.02f; const float JUMP_FORCE = 7.0f; const float YAW_SENSITIVITY = 0.1f; const float INAIR_THRESHOLD_TIME = 0.1f; const float MIN_BRAKE_FORCE = 0.1f; const float MAX_BRAKE_FORCE = 0.4f; float breakForce = 0.2f; /// Grounded flag for movement. bool onGround = false; /// Jump flag. bool okToJump = true; /// In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move. float inAirTimer = 0.0f; // TODO: add this to a utility class static T Clamp(T val, T min, T max) where T : IComparable { if (val.CompareTo(min) < 0) return min; else if (val.CompareTo(max) > 0) return max; else return val; } }