1
1
forked from 0ad/0ad

UnitMotion / AI - remove the special 'move' animation, make UnitMotion inform the visual actor directly.

Use UpdateMovementState to inform the visual actor of the unit's speed,
which cill update the movement animation accordingly.
The removes the need for UnitAI to handle movement animation using the
special "move" state.

Differential Revision: https://code.wildfiregames.com/D1901
This was SVN commit r22446.
This commit is contained in:
wraitii 2019-07-09 19:56:28 +00:00
parent 09e129bce2
commit a1dc9cadd8
5 changed files with 38 additions and 98 deletions

View File

@ -1274,8 +1274,6 @@ UnitAI.prototype.UnitFsmSpec = {
"enter": function() {
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z);
this.SelectAnimation("move");
},
// Occurs when the unit has reached its destination and the controller
@ -1300,12 +1298,12 @@ UnitAI.prototype.UnitFsmSpec = {
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
if (cmpFormation)
cmpFormation.UnsetInPosition(this.entity);
if (!this.MoveTo(this.order.data))
{
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"MovementUpdate": function() {
@ -1465,10 +1463,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.RespondToHealableEntities(msg.data.added);
},
"MoveCompleted": function() {
this.SelectAnimation("idle");
},
"Timer": function(msg) {
if (!this.isIdle)
{
@ -1485,11 +1479,9 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function () {
this.SelectAnimation("idle");
this.StopMoving();
},
@ -1510,7 +1502,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.SetAnimationVariant("combat");
this.StartTimer(0, 1000);
this.SelectAnimation("move");
},
"Timer": function(msg) {
@ -1549,7 +1540,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.StartTimer(0, 1000);
this.SetAnimationVariant("combat");
this.SelectAnimation("move");
},
"leave": function() {
@ -1593,7 +1583,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.SetAnimationVariant("combat");
this.StartTimer(0, 1000);
this.SelectAnimation("move");
this.SetHeldPositionOnEntity(this.isGuardOf);
return false;
},
@ -1641,7 +1630,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.StartTimer(1000, 1000);
this.SetHeldPositionOnEntity(this.entity);
this.SetAnimationVariant("combat");
this.SelectAnimation("idle");
return false;
},
@ -1700,7 +1688,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.PlaySound("panic");
// Run quickly
this.SelectAnimation("move");
this.SetSpeedMultiplier(this.GetRunMultiplier());
},
@ -1746,7 +1733,6 @@ UnitAI.prototype.UnitFsmSpec = {
// Show weapons rather than carried resources.
this.SetAnimationVariant("combat");
this.SelectAnimation("move");
this.StartTimer(1000, 1000);
},
@ -1882,6 +1868,7 @@ UnitAI.prototype.UnitFsmSpec = {
cmpBuildingAI.SetUnitAITarget(0);
this.StopTimer();
this.SetDefaultAnimationVariant();
this.ResetAnimation();
},
"Timer": function(msg) {
@ -2001,7 +1988,6 @@ UnitAI.prototype.UnitFsmSpec = {
// Show weapons rather than carried resources.
this.SetAnimationVariant("combat");
this.SelectAnimation("move");
var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
if (cmpUnitAI && cmpUnitAI.IsFleeing())
{
@ -2055,8 +2041,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.SetNextState("GATHERING");
return true;
}
this.SelectAnimation("move");
return false;
},
@ -2088,7 +2072,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
@ -2175,6 +2158,7 @@ UnitAI.prototype.UnitFsmSpec = {
delete this.gatheringTarget;
// Show the carried resource, if we've gathered anything.
this.ResetAnimation();
this.SetDefaultAnimationVariant();
},
@ -2347,7 +2331,6 @@ UnitAI.prototype.UnitFsmSpec = {
return true;
}
this.SelectAnimation("move");
this.StartTimer(1000, 1000);
},
@ -2398,6 +2381,7 @@ UnitAI.prototype.UnitFsmSpec = {
},
"leave": function() {
this.ResetAnimation();
this.StopTimer();
},
@ -2458,13 +2442,10 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
// Switch back to idle animation to guarantee we won't
// get stuck with the carry animation after stopping moving
this.SelectAnimation("idle");
this.StopMoving();
},
"MovementUpdate": function() {
@ -2523,7 +2504,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
@ -2563,7 +2543,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
@ -2625,6 +2604,7 @@ UnitAI.prototype.UnitFsmSpec = {
cmpBuilderList.RemoveBuilder(this.entity);
delete this.repairTarget;
this.StopTimer();
this.ResetAnimation();
},
"Timer": function(msg) {
@ -2769,7 +2749,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
@ -2892,6 +2871,7 @@ UnitAI.prototype.UnitFsmSpec = {
"leave": function() {
this.StopTimer();
this.ResetAnimation();
var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
cmpDamageReceiver.SetInvulnerability(false);
},
@ -2949,7 +2929,6 @@ UnitAI.prototype.UnitFsmSpec = {
this.FinishOrder();
return true;
}
this.SelectAnimation("move");
},
"leave": function() {
@ -2969,7 +2948,6 @@ UnitAI.prototype.UnitFsmSpec = {
"LOADING": {
"enter": function() {
this.StopMoving();
this.SelectAnimation("idle");
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull())
{
@ -3030,7 +3008,6 @@ UnitAI.prototype.UnitFsmSpec = {
"ROAMING": {
"enter": function() {
// Walk in a random direction
this.SelectAnimation("move", false, 1);
this.SetFacePointAfterMove(false);
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
@ -3083,6 +3060,7 @@ UnitAI.prototype.UnitFsmSpec = {
},
"leave": function() {
this.ResetAnimation();
this.StopTimer();
},
@ -4106,20 +4084,20 @@ UnitAI.prototype.SetDefaultAnimationVariant = function()
this.SetAnimationVariant("");
};
UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0)
UnitAI.prototype.ResetAnimation = function()
{
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
// Special case: the "move" animation gets turned into a special
// movement mode that deals with speeds and walk/run automatically
if (name == "move")
{
// Speed to switch from walking to running animations
cmpVisual.SelectMovementAnimation(this.GetWalkSpeed());
cmpVisual.SelectAnimation("idle", false, 1.0);
};
UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0)
{
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
}
cmpVisual.SelectAnimation(name, once, speed);
};

View File

@ -27,6 +27,7 @@
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/MessageTypes.h"
@ -517,18 +518,26 @@ private:
void UpdateMovementState(entity_pos_t speed)
{
CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
// Moved last turn, didn't this turn.
if (speed == fixed::Zero() && m_CurSpeed > fixed::Zero())
{
if (cmpObstruction)
cmpObstruction->SetMovingFlag(false);
if (cmpVisual)
cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1));
}
// Moved this turn, didn't last turn
else if (speed > fixed::Zero() && m_CurSpeed == fixed::Zero())
{
if (cmpObstruction)
cmpObstruction->SetMovingFlag(true);
if (cmpVisual)
cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed);
}
// Speed change, update the visual actor if necessary.
else if (speed != m_CurSpeed && cmpVisual)
cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed);
m_CurSpeed = speed;
}

View File

@ -55,7 +55,6 @@ class CCmpVisualActor : public ICmpVisual
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Update_Final);
componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
@ -75,7 +74,6 @@ private:
fixed m_R, m_G, m_B; // shading color
// Current animation state
fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
std::string m_AnimName;
bool m_AnimOnce;
fixed m_AnimSpeed;
@ -226,7 +224,6 @@ public:
serialize.NumberFixed_Unbounded("g", m_G);
serialize.NumberFixed_Unbounded("b", m_B);
serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold);
serialize.StringASCII("anim name", m_AnimName, 0, 256);
serialize.Bool("anim once", m_AnimOnce);
serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
@ -281,12 +278,6 @@ public:
{
switch (msg.GetType())
{
case MT_Update_Final:
{
const CMessageUpdate_Final& msgData = static_cast<const CMessageUpdate_Final&> (msg);
Update(msgData.turnLength);
break;
}
case MT_OwnershipChanged:
{
if (!m_Unit)
@ -437,7 +428,6 @@ public:
virtual void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1))
{
m_AnimRunThreshold = fixed::Zero();
m_AnimName = name;
m_AnimOnce = once;
m_AnimSpeed = speed;
@ -458,9 +448,12 @@ public:
m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
}
virtual void SelectMovementAnimation(fixed runThreshold)
virtual void SelectMovementAnimation(const std::string& name, fixed speed)
{
m_AnimRunThreshold = runThreshold;
ENSURE(name == "idle" || name == "walk" || name == "run");
if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run")
return;
SelectAnimation(name, false, speed);
}
virtual void SetAnimationSyncRepeat(fixed repeattime)
@ -541,8 +534,6 @@ private:
// ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical.
// It is also used by ReloadActor.
void ReloadUnitAnimation();
void Update(fixed turnLength);
};
REGISTER_COMPONENT_TYPE(VisualActor)
@ -747,42 +738,3 @@ void CCmpVisualActor::ReloadUnitAnimation()
if (!m_AnimSyncOffsetTime.IsZero())
m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
}
void CCmpVisualActor::Update(fixed UNUSED(turnLength))
{
// This function is currently only used to update the animation if the speed in
// CCmpUnitMotion changes. This also only happens in the "special movement mode"
// triggered by SelectMovementAnimation.
// TODO: This should become event based, in order to save performance and to make the code
// far less hacky. We should also take into account the speed when the animation is different
// from the "special movement mode" walking animation.
// If we're not in the special movement mode, nothing to do.
if (m_AnimRunThreshold.IsZero())
return;
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetEntityHandle());
if (!cmpUnitMotion)
return;
fixed speed = cmpUnitMotion->GetCurrentSpeed();
std::string name;
if (speed.IsZero())
{
speed = fixed::FromFloat(1.f);
name = "idle";
}
else
name = speed < m_AnimRunThreshold ? "walk" : "run";
// Selecting the animation is going to reset the anim run threshold, so save it
fixed runThreshold = m_AnimRunThreshold;
SelectAnimation(name, false, speed);
m_AnimRunThreshold = runThreshold;
}

View File

@ -27,7 +27,6 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetAnimationName", std::string, ICmpVisual, Get
DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileActor", std::wstring, ICmpVisual, GetProjectileActor)
DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileLaunchPoint", CFixedVector3D, ICmpVisual, GetProjectileLaunchPoint)
DEFINE_INTERFACE_METHOD_3("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed)
DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)
DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed)
DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed)
DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed)

View File

@ -96,20 +96,22 @@ public:
/**
* Start playing the given animation. If there are multiple possible animations then it will
* pick one at random (not network-synchronised).
* If @p soundgroup is specified, then the sound will be played at each 'event' point in the
* animation cycle.
* @param name animation name (e.g. "idle", "walk", "melee"; the names are determined by actor XML files)
* @param once if true then the animation will play once and freeze at the final frame, else it will loop
* @param speed animation speed multiplier (typically 1.0 for the default speed)
* @param soundgroup VFS path of sound group .xml, relative to audio/, or empty string for none
*/
virtual void SelectAnimation(const std::string& name, bool once, fixed speed) = 0;
/**
* Start playing the walk/run animations, scaled to the unit's movement speed.
* @param runThreshold movement speed at which to switch to the run animation
* Start playing the given movement animation unless we are currently playing a non-movement animation.
* This is necessary so UnitMotion can set the movement animations without overwriting specific animations
* that might have been set by other components.
* TODO: Non-movement animations should probably be made into variants, defining "idle" (really "default"), "walk" and "run" as appropriate,
* and this would no longer be necessary.
* @param name animation name (i.e. one of "idle", "walk", "run").
* @param speed animation speed multiplier (typically 1.0 for the default speed)
*/
virtual void SelectMovementAnimation(fixed runThreshold) = 0;
virtual void SelectMovementAnimation(const std::string& name, fixed speed) = 0;
/**
* Adjust the speed of the current animation, so it can match simulation events.