mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-10-14 07:17:44 +00:00
# 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:
@@ -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
|
||||
static float DesyncSpeed(float speed, float desync)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// 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)
|
||||
// TODO: props should get a new random animation once they loop, independent
|
||||
// of the object they're propped onto, if we ever bother with animated props
|
||||
|
||||
// If we're going to advance past the action point in this update, then perform the action
|
||||
if (action)
|
||||
{
|
||||
m_Unit.HideAmmunition();
|
||||
|
||||
m_TimeToNextSync -= time;
|
||||
if (!m_ActionSound.empty())
|
||||
{
|
||||
CmpPtr<ICmpSoundManager> cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
|
||||
if (!cmpSoundManager.null())
|
||||
cmpSoundManager->PlaySoundGroup(m_ActionSound, m_Unit.GetID());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: props should get a new random animation once they loop, independent
|
||||
// of the object they're propped onto
|
||||
// Ditto for the second action point
|
||||
if (action2)
|
||||
{
|
||||
m_Unit.ShowAmmunition();
|
||||
}
|
||||
|
||||
model->Update(time);
|
||||
// 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user