1
0
forked from 0ad/0ad

# Basic animation support in new simulation system.

Movement speed.
Improved accuracy of walking.

This was SVN commit r7313.
This commit is contained in:
Ykkrosh 2010-02-07 20:06:16 +00:00
parent ea4c22fc0e
commit d609a9f81c
13 changed files with 155 additions and 22 deletions

View File

@ -36,6 +36,8 @@ UnitAI.prototype.Init = function()
this.attackRechargeTime = 0; this.attackRechargeTime = 0;
// Timer for AttackTimeout // Timer for AttackTimeout
this.attackTimer = undefined; this.attackTimer = undefined;
this.nextAnimation = undefined;
}; };
UnitAI.prototype.OnDestroy = function() UnitAI.prototype.OnDestroy = function()
@ -47,15 +49,27 @@ UnitAI.prototype.OnDestroy = function()
} }
}; };
UnitAI.prototype.OnTurnStart = function()
{
if (this.nextAnimation)
{
this.SelectAnimation(this.nextAnimation.name, this.nextAnimation.once, this.nextAnimation.speed);
this.nextAnimation = undefined;
}
};
//// Interface functions //// //// Interface functions ////
UnitAI.prototype.Walk = function(x, z) UnitAI.prototype.Walk = function(x, z)
{ {
var motion = Engine.QueryInterface(this.entity, IID_UnitMotion); var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
if (!motion) if (!cmpMotion)
return; return;
motion.MoveToPoint(x, z, 0, 0); this.SelectAnimation("walk", false, cmpMotion.GetSpeed());
cmpMotion.MoveToPoint(x, z, 0, 0);
this.state = STATE_WALKING; this.state = STATE_WALKING;
}; };
@ -85,8 +99,31 @@ UnitAI.prototype.Attack = function(target)
this.state = STATE_ATTACKING; this.state = STATE_ATTACKING;
}; };
//// Message handlers ////
UnitAI.prototype.OnMotionStopped = function()
{
this.SelectAnimationDelayed("idle");
};
//// Private functions //// //// Private functions ////
UnitAI.prototype.SelectAnimation = function(name, once, speed)
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
cmpVisual.SelectAnimation(name, once, speed);
this.nextAnimation = undefined;
};
UnitAI.prototype.SelectAnimationDelayed = function(name, once, speed)
{
this.nextAnimation = { "name": name, "once": once, "speed": speed };
}
UnitAI.prototype.MoveToTarget = function(target) UnitAI.prototype.MoveToTarget = function(target)
{ {
var cmpPositionTarget = Engine.QueryInterface(target, IID_Position); var cmpPositionTarget = Engine.QueryInterface(target, IID_Position);
@ -95,8 +132,10 @@ UnitAI.prototype.MoveToTarget = function(target)
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
this.SelectAnimation("walk", false, cmpMotion.GetSpeed());
var pos = cmpPositionTarget.GetPosition(); var pos = cmpPositionTarget.GetPosition();
cmpMotion.MoveToPoint(pos.x, pos.z, 0, 8); cmpMotion.MoveToPoint(pos.x, pos.z, 0, 1);
}; };
UnitAI.prototype.AttackTimeout = function(data) UnitAI.prototype.AttackTimeout = function(data)
@ -126,9 +165,13 @@ UnitAI.prototype.AttackTimeout = function(data)
// Otherwise it's impossible to reach the target, so give up // Otherwise it's impossible to reach the target, so give up
// and switch back to idle // and switch back to idle
this.state = STATE_IDLE; this.state = STATE_IDLE;
this.SelectAnimation("idle");
return; return;
} }
// Play the attack animation
this.SelectAnimationDelayed("melee", false, 1);
// Hit the target // Hit the target
cmpAttack.PerformAttack(data.target); cmpAttack.PerformAttack(data.target);

View File

@ -240,14 +240,18 @@ bool CGame::Update(double deltaTime, bool doInterpolate)
deltaTime *= m_SimRate; deltaTime *= m_SimRate;
bool ok = m_Simulation->Update(deltaTime); bool ok = true;
if (g_UseSimulation2) if (g_UseSimulation2)
m_Simulation2->Update(deltaTime); m_Simulation2->Update(deltaTime);
else
ok = m_Simulation->Update(deltaTime);
if (doInterpolate) if (doInterpolate)
{ {
m_Simulation->Interpolate(deltaTime);
if (g_UseSimulation2) if (g_UseSimulation2)
m_Simulation2->Interpolate(deltaTime); m_Simulation2->Interpolate(deltaTime);
else
m_Simulation->Interpolate(deltaTime);
} }
g_GUI->SendEventToAll("SimulationUpdate"); g_GUI->SendEventToAll("SimulationUpdate");

View File

@ -63,11 +63,12 @@ class CMessageInterpolate : public CMessage
public: public:
DEFAULT_MESSAGE_IMPL(Interpolate) DEFAULT_MESSAGE_IMPL(Interpolate)
CMessageInterpolate(float offset) : CMessageInterpolate(float frameTime, float offset) :
offset(offset) frameTime(frameTime), offset(offset)
{ {
} }
float frameTime;
float offset; float offset;
}; };
@ -139,5 +140,14 @@ public:
entity_angle_t a; entity_angle_t a;
}; };
class CMessageMotionStopped : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(MotionStopped)
CMessageMotionStopped()
{
}
};
#endif // INCLUDED_MESSAGETYPES #endif // INCLUDED_MESSAGETYPES

View File

@ -156,12 +156,12 @@ void CSimulation2Impl::Update(float frameTime)
} }
} }
void CSimulation2Impl::Interpolate(float UNUSED(frameTime)) void CSimulation2Impl::Interpolate(float frameTime)
{ {
// TODO: Use CTurnManager // TODO: Use CTurnManager
double turnLength = TURN_LENGTH / 1000.0; double turnLength = TURN_LENGTH / 1000.0;
float offset = clamp(m_DeltaTime / turnLength + 1.0, 0.0, 1.0); float offset = clamp(m_DeltaTime / turnLength + 1.0, 0.0, 1.0);
m_ComponentManager.BroadcastMessage(CMessageInterpolate(offset)); m_ComponentManager.BroadcastMessage(CMessageInterpolate(frameTime, offset));
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////

View File

@ -37,6 +37,7 @@ MESSAGE(RenderSubmit) // non-deterministic (use with caution)
MESSAGE(Destroy) MESSAGE(Destroy)
MESSAGE(OwnershipChanged) MESSAGE(OwnershipChanged)
MESSAGE(PositionChanged) MESSAGE(PositionChanged)
MESSAGE(MotionStopped)
// TemplateManager must come before all other (non-test) components, // TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized // so that it is the first to be (de)serialized

View File

@ -702,6 +702,14 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
return; return;
} }
// If we're already at the goal tile, then move directly to the exact goal coordinates
if (AtGoal(i0, j0, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards))
{
Waypoint w = { goal.x, goal.z, 0 };
path.m_Waypoints.push_back(w);
return;
}
state.steps = 0; state.steps = 0;
state.tiles = new Grid<PathfindTile>(m_MapSize, m_MapSize); state.tiles = new Grid<PathfindTile>(m_MapSize, m_MapSize);
@ -761,8 +769,17 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
while (ip != i0 || jp != j0) while (ip != i0 || jp != j0)
{ {
PathfindTile& n = state.tiles->get(ip, jp); PathfindTile& n = state.tiles->get(ip, jp);
// Pick the exact point if it's the goal tile, else the tile's centre
entity_pos_t x, z; entity_pos_t x, z;
TileCenter(ip, jp, x, z); if (ip == state.iGoal && jp == state.jGoal)
{
x = goal.x;
z = goal.z;
}
else
{
TileCenter(ip, jp, x, z);
}
Waypoint w = { x, z, n.cost }; Waypoint w = { x, z, n.cost };
path.m_Waypoints.push_back(w); path.m_Waypoints.push_back(w);

View File

@ -44,11 +44,12 @@ public:
ICmpPathfinder::Path m_Path; ICmpPathfinder::Path m_Path;
entity_pos_t m_TargetX, m_TargetZ; // these values contain undefined junk if !HasTarget entity_pos_t m_TargetX, m_TargetZ; // these values contain undefined junk if !HasTarget
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode)) virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{ {
m_Context = &context; m_Context = &context;
m_Speed = CFixed_23_8::FromInt(4);
m_HasTarget = false; m_HasTarget = false;
m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
} }
virtual void Deinit(const CSimContext& UNUSED(context)) virtual void Deinit(const CSimContext& UNUSED(context))
@ -122,11 +123,20 @@ public:
goal.maxRadius = maxRadius; goal.maxRadius = maxRadius;
cmpPathfinder->SetDebugPath(pos.X, pos.Z, goal); cmpPathfinder->SetDebugPath(pos.X, pos.Z, goal);
cmpPathfinder->ComputePath(pos.X, pos.Z, goal, m_Path); cmpPathfinder->ComputePath(pos.X, pos.Z, goal, m_Path);
if (!m_Path.m_Waypoints.empty())
// If there's no waypoints then we've stopped already, otherwise move to the first one
if (m_Path.m_Waypoints.empty())
m_Context->GetComponentManager().BroadcastMessage(CMessageMotionStopped());
else
PickNextWaypoint(pos); PickNextWaypoint(pos);
} }
} }
virtual CFixed_23_8 GetSpeed()
{
return m_Speed;
}
void Move(const CSimContext& context, CFixed_23_8 dt); void Move(const CSimContext& context, CFixed_23_8 dt);
void PickNextWaypoint(const CFixedVector3D& pos); void PickNextWaypoint(const CFixedVector3D& pos);
@ -168,6 +178,7 @@ void CCmpUnitMotion::Move(const CSimContext& context, CFixed_23_8 dt)
{ {
cmpPosition->MoveTo(target.X, target.Z); cmpPosition->MoveTo(target.X, target.Z);
m_HasTarget = false; m_HasTarget = false;
context.GetComponentManager().BroadcastMessage(CMessageMotionStopped());
return; return;
} }

View File

@ -47,6 +47,13 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(VisualActor) DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
CUnit* m_Unit;
// Current animation state
std::string m_AnimName;
bool m_AnimOnce;
float m_AnimSpeed;
CCmpVisualActor() : CCmpVisualActor() :
m_Unit(NULL) m_Unit(NULL)
{ {
@ -63,6 +70,8 @@ public:
return; return;
} }
SelectAnimation("idle", false, 0.f);
m_Unit->SetID(GetEntityId()); // TODO: is it safe to be using entity IDs for unit IDs? m_Unit->SetID(GetEntityId()); // TODO: is it safe to be using entity IDs for unit IDs?
} }
@ -93,6 +102,8 @@ public:
// variations with a 16-bit RNG seed (selected randomly when creating new units, or // variations with a 16-bit RNG seed (selected randomly when creating new units, or
// when someone hits the "randomise" button in the map editor), only overridden with // when someone hits the "randomise" button in the map editor), only overridden with
// a list of strings if it really needs to be a specific variation. // a list of strings if it really needs to be a specific variation.
// TODO: store animation state
} }
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
@ -106,8 +117,8 @@ public:
{ {
case MT_Interpolate: case MT_Interpolate:
{ {
float offset = static_cast<const CMessageInterpolate&> (msg).offset; const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
Interpolate(context, offset); Interpolate(context, msgData.frameTime, msgData.offset);
break; break;
} }
case MT_RenderSubmit: case MT_RenderSubmit:
@ -133,16 +144,29 @@ public:
return m_Unit->GetModel()->GetBounds(); return m_Unit->GetModel()->GetBounds();
} }
private: virtual void SelectAnimation(std::string name, bool once, float speed)
void Interpolate(const CSimContext& context, float frameOffset); {
void RenderSubmit(const CSimContext& context, SceneCollector& collector, const CFrustum& frustum, bool culling); if (!m_Unit)
return;
CUnit* m_Unit; if (!isfinite(speed) || speed < 0) // JS 'undefined' converts to NaN, which causes Bad Things
speed = 0;
m_AnimName = name;
m_AnimOnce = once;
m_AnimSpeed = speed;
m_Unit->SetAnimationState(name, once, speed);
}
private:
void Interpolate(const CSimContext& context, float frameTime, float frameOffset);
void RenderSubmit(const CSimContext& context, SceneCollector& collector, const CFrustum& frustum, bool culling);
}; };
REGISTER_COMPONENT_TYPE(VisualActor) REGISTER_COMPONENT_TYPE(VisualActor)
void CCmpVisualActor::Interpolate(const CSimContext& context, float frameOffset) void CCmpVisualActor::Interpolate(const CSimContext& context, float frameTime, float frameOffset)
{ {
if (m_Unit == NULL) if (m_Unit == NULL)
return; return;
@ -160,6 +184,7 @@ void CCmpVisualActor::Interpolate(const CSimContext& context, float frameOffset)
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset)); CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
m_Unit->GetModel()->SetTransform(transform); m_Unit->GetModel()->SetTransform(transform);
m_Unit->UpdateModel(frameTime);
} }
void CCmpVisualActor::RenderSubmit(const CSimContext& UNUSED(context), SceneCollector& collector, const CFrustum& frustum, bool culling) void CCmpVisualActor::RenderSubmit(const CSimContext& UNUSED(context), SceneCollector& collector, const CFrustum& frustum, bool culling)

View File

@ -23,4 +23,5 @@
BEGIN_INTERFACE_WRAPPER(UnitMotion) BEGIN_INTERFACE_WRAPPER(UnitMotion)
DEFINE_INTERFACE_METHOD_4("MoveToPoint", void, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_4("MoveToPoint", void, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_0("GetSpeed", CFixed_23_8, ICmpUnitMotion, GetSpeed)
END_INTERFACE_WRAPPER(UnitMotion) END_INTERFACE_WRAPPER(UnitMotion)

View File

@ -36,6 +36,8 @@ class ICmpUnitMotion : public IComponent
public: public:
virtual void MoveToPoint(entity_pos_t x, entity_pos_t z, entity_pos_t minRadius, entity_pos_t maxRadius) = 0; virtual void MoveToPoint(entity_pos_t x, entity_pos_t z, entity_pos_t minRadius, entity_pos_t maxRadius) = 0;
virtual CFixed_23_8 GetSpeed() = 0;
DECLARE_INTERFACE_TYPE(UnitMotion) DECLARE_INTERFACE_TYPE(UnitMotion)
}; };

View File

@ -22,4 +22,5 @@
#include "simulation2/system/InterfaceScripted.h" #include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Visual) BEGIN_INTERFACE_WRAPPER(Visual)
DEFINE_INTERFACE_METHOD_3("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, float)
END_INTERFACE_WRAPPER(Visual) END_INTERFACE_WRAPPER(Visual)

View File

@ -34,7 +34,11 @@ public:
*/ */
virtual CBound GetBounds() = 0; virtual CBound GetBounds() = 0;
virtual void SelectAnimation(std::string name, bool once, float speed) = 0;
DECLARE_INTERFACE_TYPE(Visual) DECLARE_INTERFACE_TYPE(Visual)
}; };
// TODO: rename this to VisualActor, because the interface is actor-specific, maybe?
#endif // INCLUDED_ICMPVISUAL #endif // INCLUDED_ICMPVISUAL

View File

@ -83,6 +83,7 @@ CMessage* CMessageUpdate::FromJSVal(ScriptInterface& scriptInterface, jsval val)
jsval CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const jsval CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const
{ {
TOJSVAL_SETUP(); TOJSVAL_SETUP();
SET_MSG_PROPERTY(frameTime);
SET_MSG_PROPERTY(offset); SET_MSG_PROPERTY(offset);
return OBJECT_TO_JSVAL(obj); return OBJECT_TO_JSVAL(obj);
} }
@ -90,8 +91,9 @@ jsval CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const
CMessage* CMessageInterpolate::FromJSVal(ScriptInterface& scriptInterface, jsval val) CMessage* CMessageInterpolate::FromJSVal(ScriptInterface& scriptInterface, jsval val)
{ {
FROMJSVAL_SETUP(); FROMJSVAL_SETUP();
GET_MSG_PROPERTY(float, frameTime);
GET_MSG_PROPERTY(float, offset); GET_MSG_PROPERTY(float, offset);
return new CMessageInterpolate(offset); return new CMessageInterpolate(frameTime, offset);
} }
//////////////////////////////// ////////////////////////////////
@ -150,6 +152,18 @@ CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& UNUSED(scriptInter
return NULL; return NULL;
} }
////////////////////////////////
jsval CMessageMotionStopped::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
return JSVAL_VOID;
}
CMessage* CMessageMotionStopped::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
return NULL;
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val) CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)