Allow to give a frequency to animations, and allow to sync randomly selected animations on base model and prop. Fixes #2324

This was SVN commit r18265.
This commit is contained in:
sanderd17 2016-05-30 11:53:19 +00:00
parent 92e28ba947
commit df971c3885
13 changed files with 115 additions and 49 deletions

View File

@ -38,6 +38,7 @@ element actor {
element animations {
element animation {
attribute name { text } &
attribute frequency { xsd:nonNegativeInteger }? &
attribute file { text }? &
attribute speed { xsd:nonNegativeInteger } &
attribute event { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? &

View File

@ -93,6 +93,11 @@
<element name="animation">
<interleave>
<attribute name="name"/>
<optional>
<attribute name="frequency">
<data type="nonNegativeInteger"/>
</attribute>
</optional>
<optional>
<attribute name="file"/>
</optional>

View File

@ -34,6 +34,7 @@ element variant {
element animations {
element animation {
attribute name { text } &
attribute frequency { xsd:nonNegativeInteger }? &
attribute file { text }? &
attribute speed { xsd:nonNegativeInteger } &
attribute event { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? &

View File

@ -82,6 +82,11 @@ NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file
<element name="animation">
<interleave>
<attribute name="name"/>
<optional>
<attribute name="frequency">
<data type="nonNegativeInteger"/>
</attribute>
</optional>
<optional>
<attribute name="file"/>
</optional>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -255,7 +255,7 @@ const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos)
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos)
{
CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
if (!def)
@ -263,6 +263,7 @@ CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name,
CSkeletonAnim* anim = new CSkeletonAnim();
anim->m_Name = name;
anim->m_Frequency = frequency;
anim->m_AnimDef = def;
anim->m_Speed = speed;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -201,13 +201,14 @@ public:
* animation specific to this model.
* @param pathname animation file to load
* @param name animation name (e.g. "idle")
* @param frequency influences the random choices
* @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]
* @param sound offset of 'sound' event, in range [0, 1]
* @return new animation, or NULL on error
*/
CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos);
CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos);
/**
* Add a prop to the model on the given point.

View File

@ -163,6 +163,8 @@ void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& vari
{
if (ae.Name == at_name)
anim.m_AnimName = ae.Value;
else if (ae.Name == at_frequency)
anim.m_Frequency = ae.Value.ToInt();
else if (ae.Name == at_file)
anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8();
else if (ae.Name == at_speed)

View File

@ -42,9 +42,10 @@ public:
struct Anim
{
// constructor
Anim() : m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {}
Anim() : m_Frequency(0), m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {}
// name of the animation - "Idle", "Run", etc
CStr m_AnimName;
int m_Frequency;
// filename of the animation - manidle.psa, manrun.psa, etc
VfsPath m_FileName;
// animation speed, as specified in XML actor file

View File

@ -161,7 +161,7 @@ bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections
if (!it->second.m_FileName.empty())
{
CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Frequency, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
if (anim)
m_Animations.insert(std::make_pair(name, anim));
}
@ -248,15 +248,24 @@ bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections
CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const
{
SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
size_t count = std::distance(lower, upper);
if (count == 0)
return NULL;
std::vector<CSkeletonAnim*> anims = GetAnimations(animationName);
size_t id = rand(0, count);
std::advance(lower, id);
return lower->second;
int totalFreq = 0;
for (CSkeletonAnim* anim : anims)
totalFreq += anim->m_Frequency;
if (totalFreq == 0)
return anims[rand(0, anims.size())];
int r = rand(0, totalFreq);
for (CSkeletonAnim* anim : anims)
{
r -= anim->m_Frequency;
if (r < 0)
return anim;
}
LOGERROR("No animation found");
return NULL;
}
std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName) const
@ -265,7 +274,23 @@ std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationNam
SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
anims.push_back(it->second);
if (anims.size() == 0)
for (const std::pair<CStr, CSkeletonAnim*>& anim : m_Animations)
if (anim.second->m_Frequency > 0)
anims.push_back(anim.second);
if (anims.size() == 0)
{
SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound("idle");
SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound("idle");
for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
anims.push_back(it->second);
}
ENSURE(anims.size() > 0);
return anims;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -63,11 +63,20 @@ public:
std::wstring m_ProjectileModelName;
// Returns a randomly-chosen animation matching the given name.
// If none is found, returns NULL.
/**
* Returns a randomly-chosen animation matching the given name.
* The chosen animation is picked randomly from the GetAnimations list
* with the frequencies as weights (if there are any defined).
* This method should always return an animation
*/
CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const;
// Returns all the animations matching the given name.
/**
* Returns all the animations matching the given name.
* - Prefers the animations names like the animationName
* - Second choice are animations with a frequency
* - Last choice are the Idle animations (which are always added)
*/
std::vector<CSkeletonAnim*> GetAnimations(const CStr& animationName) const;
// corresponding model

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -34,6 +34,8 @@ class CSkeletonAnim
public:
// the name of the action which uses this animation (e.g. "idle")
CStr m_Name;
// frequency of the animation
int m_Frequency;
// the raw animation frame data; may be NULL if this is a static 'animation'
CSkeletonAnimDef* m_AnimDef;
// speed at which this animation runs, as a factor of the AnimDef default speed

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -41,7 +41,7 @@ static float DesyncSpeed(float speed, float desync)
}
CUnitAnimation::CUnitAnimation(entity_id_t ent, CModel* model, CObjectEntry* object)
: m_Entity(ent), m_State("idle"), m_Looping(true),
: m_Entity(ent), m_State("idle"), m_AnimationName("idle"), m_Looping(true),
m_Speed(1.f), m_SyncRepeatTime(0.f), m_OriginalSpeed(1.f), m_Desync(0.f)
{
ReloadUnit(model, object);
@ -56,26 +56,23 @@ void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object)
{
SModelAnimState state;
state.anims = object->GetAnimations(m_State);
if (state.anims.empty())
state.anims = object->GetAnimations("idle");
ENSURE(!state.anims.empty()); // there must always be an idle animation
state.model = model;
state.animIdx = rand(0, state.anims.size());
state.object = object;
state.anim = object->GetRandomAnimation(m_State);
state.time = 0.f;
state.pastLoadPos = false;
state.pastActionPos = false;
state.pastSoundPos = false;
ENSURE(state.anim != NULL); // there must always be an idle animation
m_AnimStates.push_back(state);
model->SetAnimation(state.anims[state.animIdx], !m_Looping);
model->SetAnimation(state.anim, !m_Looping);
// Detect if this unit has any non-static animations
for (size_t i = 0; i < state.anims.size(); i++)
if (state.anims[i]->m_AnimDef != NULL)
for (CSkeletonAnim* anim : object->GetAnimations(m_State))
if (anim->m_AnimDef != NULL)
m_AnimStatesAreStatic = false;
// Recursively add all props
@ -111,6 +108,7 @@ void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed,
if (name != m_State)
{
m_State = name;
m_AnimationName = name;
ReloadUnit(m_Model, m_Object);
}
@ -129,13 +127,13 @@ void CUnitAnimation::SetAnimationSyncOffset(float actionTime)
// Update all the synced prop models to each coincide with actionTime
for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
{
CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
if (animDef == NULL)
continue; // ignore static animations
float duration = animDef->GetDuration();
float actionPos = it->anims[it->animIdx]->m_ActionPos;
float actionPos = it->anim->m_ActionPos;
bool hasActionPos = (actionPos != -1.f);
if (!hasActionPos)
@ -162,15 +160,15 @@ void CUnitAnimation::Update(float time)
// Advance all of the prop models independently
for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
{
CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
if (animDef == NULL)
continue; // ignore static animations
float duration = animDef->GetDuration();
float actionPos = it->anims[it->animIdx]->m_ActionPos;
float loadPos = it->anims[it->animIdx]->m_ActionPos2;
float soundPos = it->anims[it->animIdx]->m_SoundPos;
float actionPos = it->anim->m_ActionPos;
float loadPos = it->anim->m_ActionPos2;
float soundPos = it->anim->m_SoundPos;
bool hasActionPos = (actionPos != -1.f);
bool hasLoadPos = (loadPos != -1.f);
bool hasSoundPos = (soundPos != -1.f);
@ -180,7 +178,7 @@ void CUnitAnimation::Update(float time)
if (m_SyncRepeatTime && hasActionPos)
speed = duration / m_SyncRepeatTime;
else
speed = m_Speed * it->anims[it->animIdx]->m_Speed;
speed = m_Speed * it->anim->m_Speed;
// Convert from real time to scaled animation time
float advance = time * speed;
@ -232,15 +230,29 @@ void CUnitAnimation::Update(float time)
// Wrap the timer around
it->time = fmod(it->time + advance, duration);
// If there's a choice of multiple animations, pick a new random one
if (it->anims.size() > 1)
// Pick a new random animation
CSkeletonAnim* anim;
if (it->model == m_Model)
{
size_t newAnimIdx = rand(0, it->anims.size());
if (newAnimIdx != it->animIdx)
{
it->animIdx = newAnimIdx;
it->model->SetAnimation(it->anims[it->animIdx], !m_Looping);
// we're handling the root model
// choose animations from the complete state
anim = it->object->GetRandomAnimation(m_State);
m_AnimationName = anim->m_Name;
// if we use a new animation name,
// update the animations of all non-root models
if (it->anim->m_Name != m_AnimationName)
for (SModelAnimState animState : m_AnimStates)
if (animState.model != m_Model)
animState.model->SetAnimation(animState.object->GetRandomAnimation(m_AnimationName));
}
else
// choose animations that match the root
anim = it->object->GetRandomAnimation(m_AnimationName);
if (anim != it->anim)
{
it->anim = anim;
it->model->SetAnimation(anim, !m_Looping);
}
it->pastActionPos = false;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -95,8 +95,8 @@ private:
struct SModelAnimState
{
CModel* model;
std::vector<CSkeletonAnim*> anims;
size_t animIdx;
CSkeletonAnim* anim;
const CObjectEntry* object;
float time;
bool pastLoadPos;
bool pastActionPos;
@ -117,6 +117,7 @@ private:
CModel* m_Model;
const CObjectEntry* m_Object;
CStr m_State;
CStr m_AnimationName;
bool m_Looping;
float m_OriginalSpeed;
float m_Speed;