# Fix animation syncing.
Make animations trigger sound effects. Adjust attack animation timings to match simulation. Simplify the animation speed settings. This was SVN commit r7438.
This commit is contained in:
parent
cfca28cab0
commit
4c0d47707b
@ -4,6 +4,11 @@ Sound.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
||||
Sound.prototype.GetSoundGroup = function(name)
|
||||
{
|
||||
return this.template.SoundGroups[name] || "";
|
||||
};
|
||||
|
||||
Sound.prototype.PlaySoundGroup = function(name)
|
||||
{
|
||||
if (name in this.template.SoundGroups)
|
||||
|
@ -169,8 +169,11 @@ UnitAI.prototype.OnMotionChanged = function(msg)
|
||||
var time = Math.max(timers.prepare, this.attackRechargeTime - cmpTimer.GetTime());
|
||||
this.attackTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "AttackTimeout", time, {});
|
||||
|
||||
// Start the idle animation before we switch to the attack
|
||||
this.SelectAnimation("idle");
|
||||
// Start the attack animation and sound, but synced to the timers
|
||||
this.SelectAnimation("melee", false, 1.0, "attack");
|
||||
this.SetAnimationSync(time, timers.repeat);
|
||||
// TODO: this drifts since the sim is quantised to sim turns and these timers aren't
|
||||
// TODO: we should probably only bother syncing projectile attacks, not melee
|
||||
}
|
||||
else if (this.state == STATE_REPAIRING)
|
||||
{
|
||||
@ -185,8 +188,8 @@ UnitAI.prototype.OnMotionChanged = function(msg)
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.repairTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "RepairTimeout", 1000, {});
|
||||
|
||||
// Start the repair/build animation
|
||||
this.SelectAnimation("build");
|
||||
// Start the repair/build animation and sound
|
||||
this.SelectAnimation("build", false, 1.0, "build");
|
||||
}
|
||||
else if (this.state == STATE_GATHERING)
|
||||
{
|
||||
@ -207,8 +210,8 @@ UnitAI.prototype.OnMotionChanged = function(msg)
|
||||
|
||||
this.gatherTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "GatherTimeout", 1000, {"typename": typename});
|
||||
|
||||
// Start the gather animation
|
||||
this.SelectAnimation(typename);
|
||||
// Start the gather animation and sound
|
||||
this.SelectAnimation(typename, false, 1.0, typename);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -353,13 +356,30 @@ UnitAI.prototype.MoveIntoGatherRange = function()
|
||||
|
||||
// TODO: refactor all this repetitive code
|
||||
|
||||
UnitAI.prototype.SelectAnimation = function(name, once, speed)
|
||||
UnitAI.prototype.SelectAnimation = function(name, once, speed, sound)
|
||||
{
|
||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (!cmpVisual)
|
||||
return;
|
||||
|
||||
cmpVisual.SelectAnimation(name, once, speed);
|
||||
var soundgroup = "";
|
||||
if (sound)
|
||||
{
|
||||
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
|
||||
if (cmpSound)
|
||||
soundgroup = cmpSound.GetSoundGroup(sound);
|
||||
}
|
||||
|
||||
cmpVisual.SelectAnimation(name, once, speed, soundgroup);
|
||||
};
|
||||
|
||||
UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime)
|
||||
{
|
||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (!cmpVisual)
|
||||
return;
|
||||
|
||||
cmpVisual.SetAnimationSync(actiontime, repeattime);
|
||||
};
|
||||
|
||||
UnitAI.prototype.MoveToTarget = function(target, range)
|
||||
@ -386,12 +406,6 @@ UnitAI.prototype.AttackTimeout = function(data)
|
||||
|
||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
|
||||
// Play the attack animation
|
||||
this.SelectAnimation("melee", false, 1);
|
||||
// Play the sound
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
PlaySound("attack", this.entity);
|
||||
|
||||
// Hit the target
|
||||
cmpAttack.PerformAttack(this.attackTarget);
|
||||
|
||||
@ -416,11 +430,7 @@ UnitAI.prototype.RepairTimeout = function(data)
|
||||
|
||||
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
|
||||
|
||||
// Play the sound
|
||||
PlaySound("build", this.entity);
|
||||
|
||||
// Repair/build the target
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
var status = cmpBuilder.PerformBuilding(this.repairTarget);
|
||||
|
||||
// If the target is fully built and repaired, then stop and go back to idle
|
||||
@ -449,10 +459,6 @@ UnitAI.prototype.GatherTimeout = function(data)
|
||||
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
|
||||
// Play the gather_* sound
|
||||
// TODO: these sounds should be triggered by the animation instead
|
||||
PlaySound(data.typename, this.entity);
|
||||
|
||||
// Gather from the target
|
||||
var status = cmpResourceGatherer.PerformGather(this.gatherTarget);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -213,18 +213,18 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// BuildAnimation: load raw animation frame animation from given file, and build a
|
||||
// animation specific to this model
|
||||
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const char* name, float speed, double actionpos, double actionpos2)
|
||||
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const char* name, float speed, float actionpos, float actionpos2)
|
||||
{
|
||||
CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
|
||||
if (!def) return NULL;
|
||||
if (!def)
|
||||
return NULL;
|
||||
|
||||
|
||||
CSkeletonAnim* anim=new CSkeletonAnim;
|
||||
CSkeletonAnim* anim = new CSkeletonAnim();
|
||||
anim->m_Name = name;
|
||||
anim->m_AnimDef = def;
|
||||
anim->m_Speed = speed;
|
||||
anim->m_ActionPos=(float)( actionpos /* * anim->m_AnimDef->GetDuration() */ / speed );
|
||||
anim->m_ActionPos2=(float)( actionpos2 /* * anim->m_AnimDef->GetDuration() */ / speed );
|
||||
anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration();
|
||||
anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration();
|
||||
|
||||
anim->m_ObjectBounds.SetEmpty();
|
||||
InvalidateBounds();
|
||||
@ -233,21 +233,18 @@ CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const char* name,
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Update: update this model by the given time, in seconds
|
||||
// Update: update this model by the given time, in msec
|
||||
void CModel::Update(float time)
|
||||
{
|
||||
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
|
||||
{
|
||||
// adjust for animation speed
|
||||
float animTimeDelta = time*m_AnimSpeed;
|
||||
|
||||
float oldAnimTime = m_AnimTime;
|
||||
|
||||
// update animation time, but don't calculate bone matrices - do that (lazily) when
|
||||
// something requests them; that saves some calculation work for offscreen models,
|
||||
// and also assures the world space, inverted bone matrices (required for normal
|
||||
// skinning) are up to date with respect to m_Transform
|
||||
m_AnimTime += animTimeDelta;
|
||||
m_AnimTime += time;
|
||||
|
||||
float duration = m_Anim->m_AnimDef->GetDuration();
|
||||
if (m_AnimTime > duration)
|
||||
@ -291,17 +288,26 @@ bool CModel::NeedsNewAnim(float time) const
|
||||
|
||||
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
|
||||
{
|
||||
// adjust for animation speed
|
||||
float animtime = time * m_AnimSpeed;
|
||||
|
||||
float duration = m_Anim->m_AnimDef->GetDuration();
|
||||
if (m_AnimTime + animtime > duration)
|
||||
if (m_AnimTime + time > duration)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CModel::CheckActionTriggers(float time, bool& action, bool& action2) const
|
||||
{
|
||||
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
|
||||
{
|
||||
if (m_AnimTime <= m_Anim->m_ActionPos && m_AnimTime + time > m_Anim->m_ActionPos)
|
||||
action = true;
|
||||
|
||||
if (m_AnimTime <= m_Anim->m_ActionPos2 && m_AnimTime + time > m_Anim->m_ActionPos2)
|
||||
action2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// InvalidatePosition
|
||||
void CModel::InvalidatePosition()
|
||||
@ -369,7 +375,7 @@ void CModel::ValidatePosition()
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SetAnimation: set the given animation as the current animation on this model;
|
||||
// return false on error, else true
|
||||
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, float speed, CSkeletonAnim* next)
|
||||
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, CSkeletonAnim* next)
|
||||
{
|
||||
m_Anim=NULL; // in case something fails
|
||||
|
||||
@ -406,9 +412,6 @@ bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, float speed, CSkeleton
|
||||
|
||||
// start anim from beginning
|
||||
m_AnimTime = 0;
|
||||
|
||||
// Adjust speed by animation base rate.
|
||||
m_AnimSpeed = speed * anim->m_Speed;
|
||||
}
|
||||
|
||||
m_Anim = anim;
|
||||
@ -423,7 +426,6 @@ void CModel::CopyAnimationFrom(CModel* source)
|
||||
m_Anim = source->m_Anim;
|
||||
m_NextAnim = source->m_NextAnim;
|
||||
m_AnimTime = source->m_AnimTime;
|
||||
m_AnimSpeed = source->m_AnimSpeed;
|
||||
|
||||
m_Flags &= ~MODELFLAG_CASTSHADOWS;
|
||||
if (source->m_Flags & MODELFLAG_CASTSHADOWS)
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -73,6 +73,9 @@ public:
|
||||
// returns true if Update(time) will require a new animation (due to the
|
||||
// current one ending)
|
||||
bool NeedsNewAnim(float time) const;
|
||||
// sets the bool& arguments if the given time update will cross the trigger points for those actions;
|
||||
// otherwise leaves the arguments unchanged
|
||||
void CheckActionTriggers(float time, bool& action, bool& action2) const;
|
||||
|
||||
// get the model's geometry data
|
||||
CModelDefPtr GetModelDef() { return m_pModelDef; }
|
||||
@ -96,7 +99,7 @@ public:
|
||||
CColor GetShadingColor() { return m_ShadingColor; }
|
||||
|
||||
// set the given animation as the current animation on this model
|
||||
bool SetAnimation(CSkeletonAnim* anim, bool once = false, float speed = 1000.0f, CSkeletonAnim* next = NULL);
|
||||
bool SetAnimation(CSkeletonAnim* anim, bool once = false, CSkeletonAnim* next = NULL);
|
||||
|
||||
// get the currently playing animation, if any
|
||||
CSkeletonAnim* GetAnimation() const { return m_Anim; }
|
||||
@ -146,9 +149,17 @@ public:
|
||||
return m_InverseBindBoneMatrices;
|
||||
}
|
||||
|
||||
// load raw animation frame animation from given file, and build a
|
||||
// animation specific to this model
|
||||
CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const char* name, float speed, double actionpos, double actionpos2);
|
||||
/**
|
||||
* Load raw animation frame animation from given file, and build an
|
||||
* animation specific to this model.
|
||||
* @param pathname animation file to load
|
||||
* @param name animation name (e.g. "idle")
|
||||
* @param speed animation speed as a factor of the default animation speed
|
||||
* @param actionpos offset of 'action' event, in range [0, 1]
|
||||
* @param actionpos2 offset of 'action2' event, in range [0, 1]
|
||||
* @return new animation, or NULL on error
|
||||
*/
|
||||
CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const char* name, float speed, float actionpos, float actionpos2);
|
||||
|
||||
// add a prop to the model on the given point
|
||||
void AddProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry);
|
||||
@ -199,8 +210,6 @@ private:
|
||||
CSkeletonAnim* m_Anim;
|
||||
// animation to switch back to when the current one finishes
|
||||
CSkeletonAnim* m_NextAnim;
|
||||
// rate at which the animation should play
|
||||
float m_AnimSpeed;
|
||||
// time (in MS) into the current animation
|
||||
float m_AnimTime;
|
||||
// current state of all bones on this model; null if associated modeldef isn't skeletal
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -34,12 +34,13 @@ class CSkeletonAnim
|
||||
public:
|
||||
// the name of the action which uses this animation (e.g. "idle")
|
||||
CStr m_Name;
|
||||
// the raw animation frame data
|
||||
// the raw animation frame data; may be NULL if this is a static 'animation'
|
||||
CSkeletonAnimDef* m_AnimDef;
|
||||
// speed at which this animation runs
|
||||
// speed at which this animation runs, as a factor of the AnimDef default speed
|
||||
// (treated as 0 if m_AnimDef == NULL)
|
||||
float m_Speed;
|
||||
// Times during the animation at which the interesting bits happen. Measured
|
||||
// as fractions (0..1) of the total animation length.
|
||||
// Times during the animation at which the interesting bits happen,
|
||||
// as msec times in the range [0, AnimDef->GetDuration].
|
||||
// ActionPos is used for melee hits, projectile launches, etc.
|
||||
// ActionPos2 is used for loading projectile ammunition.
|
||||
float m_ActionPos;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -112,22 +112,19 @@ static CSkeletonAnim* GetRandomAnimation(const CStr& name, CObjectEntry* object)
|
||||
return anim;
|
||||
}
|
||||
|
||||
static bool SetRandomAnimation(const CStr& name, bool once, float speed,
|
||||
static bool SetRandomAnimation(const CStr& name, bool once,
|
||||
CModel* model, CObjectEntry* object)
|
||||
{
|
||||
CSkeletonAnim* anim = GetRandomAnimation(name, object);
|
||||
if (anim)
|
||||
{
|
||||
float actualSpeed = 1000.f;
|
||||
if (speed && anim->m_AnimDef)
|
||||
actualSpeed = speed * anim->m_AnimDef->GetDuration();
|
||||
model->SetAnimation(anim, once, actualSpeed);
|
||||
model->SetAnimation(anim, once);
|
||||
|
||||
// Recursively apply the animation name to props
|
||||
const std::vector<CModel::Prop>& props = model->GetProps();
|
||||
for (std::vector<CModel::Prop>::const_iterator it = props.begin(); it != props.end(); ++it)
|
||||
{
|
||||
bool ok = SetRandomAnimation(name, once, speed, it->m_Model, it->m_ObjectEntry);
|
||||
bool ok = SetRandomAnimation(name, once, it->m_Model, it->m_ObjectEntry);
|
||||
if (! ok)
|
||||
return false;
|
||||
}
|
||||
@ -144,9 +141,9 @@ static bool SetRandomAnimation(const CStr& name, bool once, float speed,
|
||||
|
||||
|
||||
|
||||
bool CUnit::SetRandomAnimation(const CStr& name, bool once, float speed)
|
||||
bool CUnit::SetRandomAnimation(const CStr& name, bool once)
|
||||
{
|
||||
return ::SetRandomAnimation(name, once, speed, m_Model, m_Object);
|
||||
return ::SetRandomAnimation(name, once, m_Model, m_Object);
|
||||
}
|
||||
|
||||
CSkeletonAnim* CUnit::GetRandomAnimation(const CStr& name)
|
||||
@ -164,19 +161,19 @@ bool CUnit::IsPlayingAnimation(const CStr& name)
|
||||
return (m_Model->GetAnimation() && m_Model->GetAnimation()->m_Name == name);
|
||||
}
|
||||
|
||||
void CUnit::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection)
|
||||
void CUnit::SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& soundgroup)
|
||||
{
|
||||
m_Animation->SetAnimationState(name, once, speed, keepSelection);
|
||||
m_Animation->SetAnimationState(name, once, speed, desync, keepSelection, soundgroup);
|
||||
}
|
||||
|
||||
void CUnit::SetAnimationSync(float timeUntilActionPos)
|
||||
void CUnit::SetAnimationSync(float actionTime, float repeatTime)
|
||||
{
|
||||
m_Animation->SetAnimationSync(timeUntilActionPos);
|
||||
m_Animation->SetAnimationSync(actionTime, repeatTime);
|
||||
}
|
||||
|
||||
void CUnit::UpdateModel(float frameTime)
|
||||
{
|
||||
m_Animation->Update(frameTime);
|
||||
m_Animation->Update(frameTime*1000.0f);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -68,19 +68,18 @@ public:
|
||||
void ShowAmmunition();
|
||||
void HideAmmunition();
|
||||
|
||||
// Sets the animation a random one matching 'name'. If none is found,
|
||||
// sets to idle instead. Applies recursively to props.
|
||||
// SetEntitySelection(name) should typically be used before this.
|
||||
bool SetRandomAnimation(const CStr& name, bool once = false, float speed = 0.0f);
|
||||
/// See CUnitAnimation::SetAnimationState
|
||||
void SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& soundgroup);
|
||||
|
||||
void SetAnimationState(const CStr& name, bool once = false, float speed = 0.0f, bool keepSelection = false);
|
||||
void SetAnimationSync(float timeUntilActionPos);
|
||||
/// See CUnitAnimation::SetAnimationSync
|
||||
void SetAnimationSync(float actionTime, float repeatTime);
|
||||
|
||||
/**
|
||||
* Update the model's animation.
|
||||
* @param frameTime time in seconds
|
||||
*/
|
||||
void UpdateModel(float frameTime);
|
||||
|
||||
// Returns a random animation matching 'name'. If none is found,
|
||||
// returns idle instead.
|
||||
CSkeletonAnim* GetRandomAnimation(const CStr& name);
|
||||
|
||||
bool HasAnimation(const CStr& name);
|
||||
|
||||
// Sets the entity-selection, and updates the unit to use the new
|
||||
@ -131,7 +130,18 @@ private:
|
||||
// object manager which looks after this unit's objectentry
|
||||
CObjectManager& m_ObjectManager;
|
||||
|
||||
// Sets the animation a random one matching 'name'. If none is found,
|
||||
// sets to idle instead. Applies recursively to props.
|
||||
// SetEntitySelection(name) should typically be used before this.
|
||||
bool SetRandomAnimation(const CStr& name, bool once = false);
|
||||
|
||||
// Returns a random animation matching 'name'. If none is found,
|
||||
// returns idle instead.
|
||||
CSkeletonAnim* GetRandomAnimation(const CStr& name);
|
||||
|
||||
void ReloadObject();
|
||||
|
||||
friend class CUnitAnimation;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -23,93 +23,121 @@
|
||||
#include "graphics/SkeletonAnim.h"
|
||||
#include "graphics/SkeletonAnimDef.h"
|
||||
#include "graphics/Unit.h"
|
||||
#include "lib/rand.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/Game.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/components/ICmpSoundManager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Randomly modify the speed, so that units won't stay perfectly
|
||||
// synchronised if they're playing animations of the same length
|
||||
float DesyncSpeed(float speed)
|
||||
static float DesyncSpeed(float speed, float desync)
|
||||
{
|
||||
// const float var = 0.05f; // max fractional variation from default
|
||||
// return speed * (1.f - var + 2.f*var*(rand(0, 256)/255.f));
|
||||
// TODO: enable this desyncing for cases where we don't care about
|
||||
// accurate looping, and just don't do it for e.g. projectile-launchers
|
||||
// where we do care
|
||||
if (desync == 0.0f)
|
||||
return speed;
|
||||
}
|
||||
|
||||
return speed * (1.f - desync + 2.f*desync*(rand(0, 256)/255.f));
|
||||
}
|
||||
|
||||
CUnitAnimation::CUnitAnimation(CUnit& unit)
|
||||
: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(0.f), m_OriginalSpeed(0.f), m_TimeToNextSync(0.f)
|
||||
: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(1.f), m_OriginalSpeed(1.f), m_Desync(0.f)
|
||||
{
|
||||
}
|
||||
|
||||
void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection)
|
||||
void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& actionSound)
|
||||
{
|
||||
if (name == m_State)
|
||||
return;
|
||||
|
||||
m_State = name;
|
||||
m_Looping = !once;
|
||||
m_Speed = m_OriginalSpeed = speed;
|
||||
m_TimeToNextSync = 0.f;
|
||||
m_OriginalSpeed = speed;
|
||||
m_Desync = desync;
|
||||
m_ActionSound = actionSound;
|
||||
|
||||
m_Speed = DesyncSpeed(m_OriginalSpeed, m_Desync);
|
||||
|
||||
if (! keepSelection)
|
||||
m_Unit.SetEntitySelection(name);
|
||||
|
||||
m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
|
||||
m_Unit.SetRandomAnimation(m_State, !m_Looping);
|
||||
}
|
||||
|
||||
void CUnitAnimation::SetAnimationSync(float timeUntilActionPos)
|
||||
void CUnitAnimation::SetAnimationSync(float actionTime, float repeatTime)
|
||||
{
|
||||
// We need to finish looping our animation at the specified time from now.
|
||||
// Assume it's playing at nearly the right speed, and we just need to perhaps
|
||||
// shift it a little bit to stay in sync.
|
||||
|
||||
m_TimeToNextSync = timeUntilActionPos;
|
||||
|
||||
CModel* model = m_Unit.GetModel();
|
||||
float duration = model->m_Anim->m_AnimDef->GetDuration();
|
||||
|
||||
// Calculate the required playback speed so ActionPos coincides with timeUntilActionPos
|
||||
float currentPos = model->m_AnimTime / model->m_Anim->m_AnimDef->GetDuration();
|
||||
float length = (model->m_Anim->m_ActionPos - currentPos);
|
||||
if (length < 0.f)
|
||||
length += 1.f;
|
||||
float requiredSpeed = length / m_TimeToNextSync;
|
||||
// Set the speed so it loops once in repeatTime
|
||||
float speed = duration / repeatTime;
|
||||
|
||||
// Shift in the right direction
|
||||
if (requiredSpeed > m_OriginalSpeed)
|
||||
m_Speed = std::min(requiredSpeed, m_OriginalSpeed*1.1f);
|
||||
else if (requiredSpeed < m_OriginalSpeed)
|
||||
m_Speed = std::max(requiredSpeed, m_OriginalSpeed*0.9f);
|
||||
// Need to offset so that start+actionTime*speed = ActionPos
|
||||
float start = model->m_Anim->m_ActionPos - actionTime*speed;
|
||||
// Wrap it so that it's within the animation
|
||||
start = fmodf(start, duration);
|
||||
if (start < 0)
|
||||
start += duration;
|
||||
|
||||
model->m_AnimSpeed = m_Speed * model->m_Anim->m_AnimDef->GetDuration() * model->m_Anim->m_Speed;
|
||||
|
||||
// TODO: this should use the ActionPos2, instead of totally ignoring it
|
||||
m_Unit.ShowAmmunition();
|
||||
// Make the animation run at the computed timings
|
||||
model->m_AnimTime = start;
|
||||
m_Speed = m_OriginalSpeed = speed;
|
||||
m_Desync = 0.f;
|
||||
}
|
||||
|
||||
void CUnitAnimation::Update(float time)
|
||||
{
|
||||
CModel* model = m_Unit.GetModel();
|
||||
|
||||
// Convert from real time to scaled animation time
|
||||
float advance = time*m_Speed;
|
||||
|
||||
// Check if the current animation will trigger the action events
|
||||
bool action = false;
|
||||
bool action2 = false;
|
||||
m_Unit.GetModel()->CheckActionTriggers(advance, action, action2);
|
||||
|
||||
// Choose a new random animation if we're going to loop
|
||||
if (m_Looping && model->NeedsNewAnim(time))
|
||||
{
|
||||
m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
|
||||
// TODO: this really ought to transition smoothly into the new animation,
|
||||
// Re-desynchronise the animation, to keep the irregular drifting between separate units
|
||||
m_Speed = DesyncSpeed(m_OriginalSpeed, m_Desync);
|
||||
advance = time*m_Speed;
|
||||
// TODO: should subtract time remaining in previous animation from advance, to be a bit more precise
|
||||
|
||||
m_Unit.SetRandomAnimation(m_State, false);
|
||||
// TODO: this really ought to interpolate smoothly into the new animation,
|
||||
// instead of just cutting off the end of the previous one and jumping
|
||||
// straight into the new.
|
||||
|
||||
// Check if the start of the new animation will trigger the action events
|
||||
// (This is additive with the previous CheckActionTriggers)
|
||||
m_Unit.GetModel()->CheckActionTriggers(advance, action, action2);
|
||||
}
|
||||
|
||||
if (m_TimeToNextSync >= 0.0 && m_TimeToNextSync-time < 0.0)
|
||||
m_Unit.HideAmmunition();
|
||||
|
||||
m_TimeToNextSync -= time;
|
||||
|
||||
// TODO: props should get a new random animation once they loop, independent
|
||||
// of the object they're propped onto
|
||||
// of the object they're propped onto, if we ever bother with animated props
|
||||
|
||||
model->Update(time);
|
||||
// If we're going to advance past the action point in this update, then perform the action
|
||||
if (action)
|
||||
{
|
||||
m_Unit.HideAmmunition();
|
||||
|
||||
if (!m_ActionSound.empty())
|
||||
{
|
||||
CmpPtr<ICmpSoundManager> cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
|
||||
if (!cmpSoundManager.null())
|
||||
cmpSoundManager->PlaySoundGroup(m_ActionSound, m_Unit.GetID());
|
||||
}
|
||||
}
|
||||
|
||||
// Ditto for the second action point
|
||||
if (action2)
|
||||
{
|
||||
m_Unit.ShowAmmunition();
|
||||
}
|
||||
|
||||
// TODO: some animations (e.g. wood chopping) have two action points that should trigger sounds,
|
||||
// so we ought to support that somehow
|
||||
|
||||
model->Update(advance);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -22,25 +22,61 @@
|
||||
|
||||
class CUnit;
|
||||
|
||||
/**
|
||||
* Deals with synchronisation issues between raw animation data (CModel, CSkeletonAnim)
|
||||
* and the simulation system (via CUnit), providing a simple fire-and-forget API to play animations.
|
||||
* (This is really just a component of CUnit and could probably be merged back into that class.)
|
||||
*/
|
||||
class CUnitAnimation
|
||||
{
|
||||
NONCOPYABLE(CUnitAnimation);
|
||||
public:
|
||||
/**
|
||||
* Construct for a given unit, defaulting to the "idle" animation.
|
||||
*/
|
||||
CUnitAnimation(CUnit& unit);
|
||||
|
||||
// (All times are measured in seconds)
|
||||
/**
|
||||
* Start playing an animation.
|
||||
* The unit's actor defines the available animations, and if more than one is available
|
||||
* then one is picked at random (with a new random choice each loop).
|
||||
* By default, animations start immediately and run at the given speed with no syncing.
|
||||
* Use SetAnimationSync after this to force a specific timing, if it needs to match the
|
||||
* simulation timing.
|
||||
* Alternatively, set @p desync to a non-zero value (e.g. 0.05) to slightly randomise the
|
||||
* offset and speed, so units don't all move in lockstep.
|
||||
* @param name animation's name ("idle", "walk", etc)
|
||||
* @param once if true then the animation freezes on its last frame; otherwise it loops
|
||||
* @param speed fraction of actor-defined speed to play back at (should typically be 1.0)
|
||||
* @param desync maximum fraction of length/speed to randomly adjust timings (or 0.0 for no desyncing)
|
||||
* @param keepSelection if false then the random actor variation will have the selection @p name added
|
||||
* @param actionSound sound group name to be played at the 'action' point in the animation, or empty string
|
||||
*/
|
||||
void SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& actionSound);
|
||||
|
||||
void SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection);
|
||||
void SetAnimationSync(float timeUntilActionPos);
|
||||
/**
|
||||
* Adjust the timing of the current animation, so that Update(actionTime) will advance it
|
||||
* to the 'action' point defined in the actor, and so that Update(repeatTime) will do a
|
||||
* complete animation loop.
|
||||
* @param actionTime time between now and when the action should occur, in msec
|
||||
* @param repeatTime time for complete loop of animation, in msec
|
||||
*/
|
||||
void SetAnimationSync(float actionTime, float repeatTime);
|
||||
|
||||
/**
|
||||
* Advance the animation state.
|
||||
* @param time advance time in msec
|
||||
*/
|
||||
void Update(float time);
|
||||
|
||||
private:
|
||||
CUnit& m_Unit;
|
||||
CStr m_State;
|
||||
bool m_Looping;
|
||||
float m_Speed;
|
||||
float m_OriginalSpeed;
|
||||
float m_TimeToNextSync;
|
||||
float m_Speed;
|
||||
float m_Desync;
|
||||
CStrW m_ActionSound;
|
||||
};
|
||||
|
||||
#endif // INCLUDED_UNITANIMATION
|
||||
|
@ -331,7 +331,7 @@ void CEntity::Kill(bool keepActor)
|
||||
|
||||
// Play death animation and keep the actor in the game in a dead state
|
||||
// (TODO: remove the actor after some time through some kind of fading mechanism)
|
||||
m_actor->SetAnimationState( "death", true );
|
||||
m_actor->SetAnimationState( "death", true, 1.f, 0.f, false, L"" );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -629,7 +629,7 @@ void CEntity::UpdateOrders( int timestep )
|
||||
{
|
||||
if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() )
|
||||
{
|
||||
m_actor->SetAnimationState( "idle" );
|
||||
m_actor->SetAnimationState( "idle", false, 1.f, 0.f, false, L"" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ float CEntity::ChooseMovementSpeed( float distance )
|
||||
{
|
||||
if ( !m_actor->IsPlayingAnimation( anim_name ) )
|
||||
{
|
||||
m_actor->SetAnimationState( anim_name, false, speed );
|
||||
m_actor->SetAnimationState( anim_name, false, speed, 0.f, false, L"" );
|
||||
|
||||
// Animation desync
|
||||
m_actor->GetModel()->Update( rand( 0, 1000 ) / 1000.0f );
|
||||
@ -518,7 +518,7 @@ bool CEntity::ProcessContactActionNoPathing( CEntityOrder* current, int timestep
|
||||
// Cancel current order
|
||||
entf_clear(ENTF_IS_RUNNING);
|
||||
entf_clear(ENTF_SHOULD_RUN);
|
||||
m_actor->SetAnimationState( "idle" );
|
||||
m_actor->SetAnimationState( "idle", false, 1.f, 0.f, false, L"" );
|
||||
PopOrder();
|
||||
if( m_orderQueue.empty() && target.IsValid() )
|
||||
{
|
||||
@ -571,7 +571,7 @@ bool CEntity::ProcessContactActionNoPathing( CEntityOrder* current, int timestep
|
||||
if( current->m_source == CEntityOrder::SOURCE_UNIT_AI && !m_stance->AllowsMovement() )
|
||||
{
|
||||
PopOrder();
|
||||
m_actor->SetAnimationState( "idle" );
|
||||
m_actor->SetAnimationState( "idle", false, 1.f, 0.f, false, L"" );
|
||||
return false; // We're not allowed to move at all by the current stance
|
||||
}
|
||||
|
||||
@ -581,7 +581,7 @@ bool CEntity::ProcessContactActionNoPathing( CEntityOrder* current, int timestep
|
||||
// The pathfinder will push its result in front of the current order
|
||||
if( !g_Pathfinder.RequestAvoidPath( me, current, action->m_MinRange + 2.0f ) )
|
||||
{
|
||||
m_actor->SetAnimationState( "idle" ); // Nothing we can do.. maybe we'll find a better target
|
||||
m_actor->SetAnimationState( "idle", false, 1.f, 0.f, false, L"" ); // Nothing we can do.. maybe we'll find a better target
|
||||
PopOrder();
|
||||
}
|
||||
|
||||
@ -664,8 +664,8 @@ bool CEntity::ProcessContactActionNoPathing( CEntityOrder* current, int timestep
|
||||
entf_clear(ENTF_IS_RUNNING);
|
||||
}
|
||||
|
||||
m_actor->SetAnimationState( animation, false, 1000.f / (float)action->m_Speed );
|
||||
m_actor->SetAnimationSync( (float)( action->m_Speed / 2) / 1000.f );
|
||||
m_actor->SetAnimationState( animation, false, 1000.f / (float)action->m_Speed, 0.f, false, L"" );
|
||||
// m_actor->SetAnimationSync( (float)( action->m_Speed / 2) / 1000.f );
|
||||
|
||||
m_fsm_cyclepos = 0;
|
||||
|
||||
|
@ -53,6 +53,7 @@ public:
|
||||
std::string m_AnimName;
|
||||
bool m_AnimOnce;
|
||||
float m_AnimSpeed;
|
||||
std::wstring m_SoundGroup;
|
||||
|
||||
CCmpVisualActor() :
|
||||
m_Unit(NULL)
|
||||
@ -80,7 +81,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
SelectAnimation("idle", false, 0.f);
|
||||
SelectAnimation("idle", false, 0.f, L"");
|
||||
|
||||
m_Unit->SetID(GetEntityId()); // TODO: is it safe to be using entity IDs for unit IDs?
|
||||
}
|
||||
@ -163,19 +164,30 @@ public:
|
||||
return m_Unit->GetModel()->GetTransform().GetTranslation();
|
||||
}
|
||||
|
||||
virtual void SelectAnimation(std::string name, bool once, float speed)
|
||||
virtual void SelectAnimation(std::string name, bool once, float speed, std::wstring soundgroup)
|
||||
{
|
||||
if (!m_Unit)
|
||||
return;
|
||||
|
||||
if (!isfinite(speed) || speed < 0) // JS 'undefined' converts to NaN, which causes Bad Things
|
||||
speed = 0;
|
||||
speed = 1.f;
|
||||
|
||||
float desync = 0.05f; // TODO: make this an argument
|
||||
|
||||
m_AnimName = name;
|
||||
m_AnimOnce = once;
|
||||
m_AnimSpeed = speed;
|
||||
m_SoundGroup = soundgroup;
|
||||
|
||||
m_Unit->SetAnimationState(name, once, speed);
|
||||
m_Unit->SetAnimationState(name, once, speed, desync, false, soundgroup.c_str());
|
||||
}
|
||||
|
||||
virtual void SetAnimationSync(float actiontime, float repeattime)
|
||||
{
|
||||
if (!m_Unit)
|
||||
return;
|
||||
|
||||
m_Unit->SetAnimationSync(actiontime, repeattime);
|
||||
}
|
||||
|
||||
virtual void SetShadingColour(CFixed_23_8 r, CFixed_23_8 g, CFixed_23_8 b, CFixed_23_8 a)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "simulation2/system/InterfaceScripted.h"
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(Visual)
|
||||
DEFINE_INTERFACE_METHOD_3("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, float)
|
||||
DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, float, std::wstring)
|
||||
DEFINE_INTERFACE_METHOD_2("SetAnimationSync", void, ICmpVisual, SetAnimationSync, float, float)
|
||||
DEFINE_INTERFACE_METHOD_4("SetShadingColour", void, ICmpVisual, SetShadingColour, CFixed_23_8, CFixed_23_8, CFixed_23_8, CFixed_23_8)
|
||||
END_INTERFACE_WRAPPER(Visual)
|
||||
|
@ -44,11 +44,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 some kind of animation speed multiplier (TODO: work out exactly what the scale is)
|
||||
* @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(std::string name, bool once, float speed) = 0;
|
||||
virtual void SelectAnimation(std::string name, bool once, float speed, std::wstring soundgroup) = 0;
|
||||
|
||||
/**
|
||||
* Adjust the timing (offset and speed) of the current animation, so it can match
|
||||
* simulation events.
|
||||
* @param actiontime time between now and when the 'action' event should occur, in msec
|
||||
* @param repeattime time for complete loop of animation, in msec
|
||||
*/
|
||||
virtual void SetAnimationSync(float actiontime, float repeattime) = 0;
|
||||
|
||||
/**
|
||||
* Set the shading colour that will be modulated with the model's textures.
|
||||
|
@ -183,18 +183,28 @@ void ActorViewer::SetActor(const CStrW& name, const CStrW& animation)
|
||||
// same for all units. We ought to get it from the entity definition
|
||||
// (if there is one)
|
||||
if (anim == "walk")
|
||||
{
|
||||
speed = 7.f;
|
||||
else if (anim == "run")
|
||||
speed = 12.f;
|
||||
else
|
||||
speed = 0.f;
|
||||
m.CurrentSpeed = speed;
|
||||
}
|
||||
else if (anim == "run")
|
||||
{
|
||||
speed = 12.f;
|
||||
m.CurrentSpeed = speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Play the animation at normal speed, but movement speed is zero
|
||||
speed = 1.f;
|
||||
m.CurrentSpeed = 0.f;
|
||||
}
|
||||
|
||||
CmpPtr<ICmpVisual> cmpVisual(m.Simulation2, m.Entity);
|
||||
if (!cmpVisual.null())
|
||||
{
|
||||
// TODO: SetEntitySelection(anim)
|
||||
cmpVisual->SelectAnimation(anim, false, speed);
|
||||
// TODO: maybe we could play soundgroups from entities in here, to help test timings?
|
||||
cmpVisual->SelectAnimation(anim, false, speed, L"");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user