主要参考Unity引擎的动画系统的实现,但是Unity引擎的动画系统是一个复杂的系统,涉及到多个组件和类的协作。但我们可以用C#来实现其基本的动画系统逻辑。以下是一个简化的示例,展示了如何用C#实现一个基本的动画系统,包括骨骼动画、帧动画和Tween动画的基本逻辑。
1. 基本结构
我们将创建几个类来自定义引擎的动画系统:
AnimationClip
:表示一个动画剪辑。SkeletonAnimation
:表示骨骼动画。FrameAnimation
:表示帧动画。Tween
:表示插值动画。AnimationEvent
:表示动画事件。
2. 代码实现
以下是一个简单的C#代码示例,自定义引擎的动画系统:
using System;
using System.Collections.Generic;
public class AnimationEvent
{
public string Name { get; set; }
public float Time { get; set; }
public Action Callback { get; set; }
public AnimationEvent(string name, float time, Action callback)
{
Name = name;
Time = time;
Callback = callback;
}
}
public class AnimationClip
{
public List<KeyFrame> KeyFrames { get; private set; } = new List<KeyFrame>();
public List<AnimationEvent> Events { get; private set; } = new List<AnimationEvent>();
public void AddKeyFrame(float time, float value)
{
KeyFrames.Add(new KeyFrame(time, value));
}
public void AddEvent(string name, float time, Action callback)
{
Events.Add(new AnimationEvent(name, time, callback));
}
}
public class KeyFrame
{
public float Time { get; set; }
public float Value { get; set; }
public KeyFrame(float time, float value)
{
Time = time;
Value = value;
}
}
public class SkeletonAnimation
{
private AnimationClip currentClip;
private float currentTime;
public void Play(AnimationClip clip)
{
currentClip = clip;
currentTime = 0f;
}
public void Update(float deltaTime)
{
if (currentClip == null) return;
currentTime += deltaTime;
// 处理关键帧插值
for (int i = 0; i < currentClip.KeyFrames.Count - 1; i++)
{
var startFrame = currentClip.KeyFrames[i];
var endFrame = currentClip.KeyFrames[i + 1];
if (currentTime >= startFrame.Time && currentTime < endFrame.Time)
{
float t = (currentTime - startFrame.Time) / (endFrame.Time - startFrame.Time);
float value = Mathf.Lerp(startFrame.Value, endFrame.Value, t);
Console.WriteLine($"Current Value: {value}");
// 检查并触发事件
foreach (var animEvent in currentClip.Events)
{
if (animEvent.Time >= startFrame.Time && animEvent.Time <= endFrame.Time)
{
animEvent.Callback.Invoke();
}
}
break;
}
}
}
}
public class FrameAnimation
{
private List<string> frames;
private int currentFrame;
private float frameDuration;
private float elapsedTime;
public FrameAnimation(List<string> frames, float frameDuration)
{
this.frames = frames;
this.frameDuration = frameDuration;
currentFrame = 0;
}
public void Update(float deltaTime)
{
elapsedTime += deltaTime;
if (elapsedTime >= frameDuration)
{
currentFrame = (currentFrame + 1) % frames.Count;
Console.WriteLine($"Current Frame: {frames[currentFrame]}");
elapsedTime = 0f;
}
}
}
public static class Mathf
{
public static float Lerp(float a, float b, float t)
{
return a + (b - a) * t;
}
}
// 示例用法
public class Program
{
public static void Main(string[] args)
{
// 创建一个骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip = new AnimationClip();
animationClip.AddKeyFrame(0f, 0f);
animationClip.AddKeyFrame(1f, 1f);
animationClip.AddEvent("OnComplete", 1f, () => Console.WriteLine("Animation Complete!"));
skeletonAnimation.Play(animationClip);
// 更新动画
for (float time = 0; time <= 1.5f; time += 0.1f)
{
skeletonAnimation.Update(0.1f);
}
// 创建一个帧动画
var frameAnimation = new FrameAnimation(new List<string> { "frame1", "frame2", "frame3" }, 0.5f);
for (float time = 0; time <= 2f; time += 0.1f)
{
frameAnimation.Update(0.1f);
}
}
}
3. 代码说明
- AnimationEvent:表示动画事件,包含事件名称、触发时间和回调函数。
- AnimationClip:表示动画剪辑,包含关键帧和事件的列表。
- KeyFrame:表示单个关键帧,包含时间和对应的值。
- SkeletonAnimation:表示骨骼动画,负责播放动画剪辑并更新当前时间,处理关键帧插值和事件触发。
- FrameAnimation:表示帧动画,负责逐帧播放动画。
- Mathf:提供简单的数学函数,例如线性插值。
4. 总结
以上代码是自定义引擎动画系统的一个简化实现,展示了如何使用C#实现基本的动画逻辑。实际的商业引擎动画系统要复杂得多,涉及更多的功能和优化,但这个示例可以帮助你理解动画系统的基本工作原理。
接下来我们可以进一步扩展之前的C#动画系统模拟,增加一些功能和细节,以更好地实现自研引擎的动画系统。我们可以考虑以下几个方面:
- 动画混合:实现多个动画的混合播放。
- 动画状态机:管理不同的动画状态和切换。
- Tween 动画:实现更复杂的插值动画。
- 事件回调:支持在动画播放过程中触发不同的事件。
1. 动画混合
我们可以在 SkeletonAnimation
类中添加一个方法来支持动画混合。这里我们将实现一个简单的线性混合。
public class SkeletonAnimation
{
private AnimationClip currentClip;
private AnimationClip nextClip;
private float currentTime;
private float blendTime;
public void Play(AnimationClip clip, float blendTime = 0f)
{
if (currentClip != null)
{
nextClip = clip;
this.blendTime = blendTime;
}
else
{
currentClip = clip;
currentTime = 0f;
}
}
public void Update(float deltaTime)
{
if (currentClip == null) return;
currentTime += deltaTime;
// 处理关键帧插值
for (int i = 0; i < currentClip.KeyFrames.Count - 1; i++)
{
var startFrame = currentClip.KeyFrames[i];
var endFrame = currentClip.KeyFrames[i + 1];
if (currentTime >= startFrame.Time && currentTime < endFrame.Time)
{
float t = (currentTime - startFrame.Time) / (endFrame.Time - startFrame.Time);
float value = Mathf.Lerp(startFrame.Value, endFrame.Value, t);
Console.WriteLine($"Current Value: {value}");
// 检查并触发事件
foreach (var animEvent in currentClip.Events)
{
if (animEvent.Time >= startFrame.Time && animEvent.Time <= endFrame.Time)
{
animEvent.Callback.Invoke();
}
}
break;
}
}
// 混合动画
if (nextClip != null)
{
float blendFactor = Mathf.Clamp01(currentTime / blendTime);
// 这里可以实现混合逻辑
// 例如,使用当前动画和下一个动画的关键帧进行插值
// 这部分可以根据具体需求进行扩展
}
}
}
2. 动画状态机
我们可以创建一个简单的动画状态机来管理不同的动画状态和切换。
public class AnimationStateMachine
{
private Dictionary<string, AnimationClip> animations = new Dictionary<string, AnimationClip>();
private SkeletonAnimation skeletonAnimation;
private string currentState;
public AnimationStateMachine(SkeletonAnimation skeletonAnimation)
{
this.skeletonAnimation = skeletonAnimation;
}
public void AddAnimation(string state, AnimationClip clip)
{
animations[state] = clip;
}
public void Play(string state)
{
if (currentState != state)
{
currentState = state;
skeletonAnimation.Play(animations[state]);
}
}
public void Update(float deltaTime)
{
skeletonAnimation.Update(deltaTime);
}
}
3. Tween 动画
我们可以实现一个简单的 Tween 动画类,支持不同的插值方式。
public class Tween
{
public static void To(object target, float duration, Action<float> onUpdate, Action onComplete = null)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += 0.1f; // 假设每次调用更新0.1秒
float t = Mathf.Clamp01(elapsed / duration);
onUpdate(t);
// 这里可以添加延迟逻辑
}
onComplete?.Invoke();
}
}
4. 事件回调
在动画中添加事件回调的逻辑已经在之前的代码中实现。可以在关键帧中添加事件,并在播放时触发。
5. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip1 = new AnimationClip();
animationClip1.AddKeyFrame(0f, 0f);
animationClip1.AddKeyFrame(1f, 1f);
animationClip1.AddEvent("OnComplete", 1f, () => Console.WriteLine("Animation 1 Complete!"));
var animationClip2 = new AnimationClip();
animationClip2.AddKeyFrame(0f, 1f);
animationClip2.AddKeyFrame(1f, 0f);
animationClip2.AddEvent("OnComplete", 1f, () => Console.WriteLine("Animation 2 Complete!"));
var stateMachine = new AnimationStateMachine(skeletonAnimation);
stateMachine.AddAnimation("Idle", animationClip1);
stateMachine.AddAnimation("Run", animationClip2);
// 播放动画
stateMachine.Play("Idle");
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
// 切换到另一个动画
stateMachine.Play("Run");
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
}
}
6. 总结
通过以上扩展,我们实现了一个更为复杂的动画系统,支持动画混合、状态机管理、Tween 动画等功能。这些功能使得动画系统更加灵活和强大,能够满足更复杂的游戏需求。
我们可以继续扩展这个动画系统,增加更多的功能和细节。以下是一些可以考虑的扩展:
- 支持不同的插值方式:实现不同的插值方法(如缓动、弹性等)。
- 动画层:支持多个动画层的叠加。
- 动画速度控制:允许动态调整动画播放速度。
- 事件系统:实现更复杂的事件系统,支持事件监听和广播。
- 动画回调:在动画开始、结束和中断时提供回调。
1. 支持不同的插值方式
我们可以扩展 Mathf
类,添加不同的插值方法,例如线性插值、缓动插值等。
public static class Mathf
{
public static float Lerp(float a, float b, float t)
{
return a + (b - a) * t;
}
public static float EaseInOut(float t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
public static float Bounce(float t)
{
if (t < (1 / 2.75f))
{
return 7.5625f * t * t;
}
else if (t < (2 / 2.75f))
{
t -= (1.5f / 2.75f);
return 7.5625f * t * t + .75f;
}
else if (t < (2.5 / 2.75))
{
t -= (2.25f / 2.75f);
return 7.5625f * t * t + .9375f;
}
else
{
t -= (2.625f / 2.75f);
return 7.5625f * t * t + .984375f;
}
}
}
2. 动画层
我们可以实现一个简单的动画层系统,允许多个动画同时播放并叠加。
public class AnimationLayer
{
public string Name { get; private set; }
public SkeletonAnimation Animation { get; private set; }
public float Weight { get; set; }
public AnimationLayer(string name, SkeletonAnimation animation, float weight = 1f)
{
Name = name;
Animation = animation;
Weight = weight;
}
public void Update(float deltaTime)
{
Animation.Update(deltaTime);
}
}
public class AnimationController
{
private List<AnimationLayer> layers = new List<AnimationLayer>();
public void AddLayer(AnimationLayer layer)
{
layers.Add(layer);
}
public void Update(float deltaTime)
{
foreach (var layer in layers)
{
layer.Update(deltaTime);
}
}
}
3. 动画速度控制
我们可以在 SkeletonAnimation
类中添加一个属性来控制动画的播放速度。
public class SkeletonAnimation
{
private AnimationClip currentClip;
private float currentTime;
private float speed = 1f; // 动画速度
public float Speed
{
get => speed;
set => speed = Math.Max(0f, value); // 确保速度不为负
}
public void Update(float deltaTime)
{
if (currentClip == null) return;
currentTime += deltaTime * speed; // 根据速度调整时间
// 处理关键帧插值
// ...(与之前相同的逻辑)
}
}
4. 事件系统
我们可以实现一个简单的事件系统,支持事件的监听和广播。
public class EventManager
{
private Dictionary<string, Action> eventDictionary = new Dictionary<string, Action>();
public void Subscribe(string eventName, Action listener)
{
if (!eventDictionary.ContainsKey(eventName))
{
eventDictionary[eventName] = null;
}
eventDictionary[eventName] += listener;
}
public void Unsubscribe(string eventName, Action listener)
{
if (eventDictionary.ContainsKey(eventName))
{
eventDictionary[eventName] -= listener;
}
}
public void Publish(string eventName)
{
if (eventDictionary.ContainsKey(eventName))
{
eventDictionary[eventName]?.Invoke();
}
}
}
5. 动画回调
我们可以在 AnimationClip
中添加回调,以便在动画开始、结束和中断时触发。
public class AnimationClip
{
public List<KeyFrame> KeyFrames { get; private set; } = new List<KeyFrame>();
public List<AnimationEvent> Events { get; private set; } = new List<AnimationEvent>();
public Action OnStart { get; set; }
public Action OnEnd { get; set; }
public void Play()
{
OnStart?.Invoke();
}
public void Complete()
{
OnEnd?.Invoke();
}
}
6. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建事件管理器
var eventManager = new EventManager();
// 创建骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip1 = new AnimationClip();
animationClip1.AddKeyFrame(0f, 0f);
animationClip1.AddKeyFrame(1f, 1f);
animationClip1.OnStart = () => Console.WriteLine("Animation 1 Started!");
animationClip1.OnEnd = () => Console.WriteLine("Animation 1 Ended!");
var animationClip2 = new AnimationClip();
animationClip2.AddKeyFrame(0f, 1f);
animationClip2.AddKeyFrame(1f, 0f);
animationClip2.OnStart = () => Console.WriteLine("Animation 2 Started!");
animationClip2.OnEnd = () => Console.WriteLine("Animation 2 Ended!");
var stateMachine = new AnimationStateMachine(skeletonAnimation);
stateMachine.AddAnimation("Idle", animationClip1);
stateMachine.AddAnimation("Run", animationClip2);
// 播放动画
stateMachine.Play("Idle");
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
// 切换到另一个动画
stateMachine.Play("Run");
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
}
}
7. 总结
通过以上扩展,我们实现了一个更为复杂的动画系统,支持不同的插值方式、动画层、动画速度控制、事件系统和动画回调。这些功能使得动画系统更加灵活和强大,能够满足更复杂的游戏需求。
我们可以继续扩展这个动画系统,增加更多的功能和细节。以下是一些可以考虑的扩展:
- 动画循环:支持动画的循环播放。
- 动画反向播放:支持动画的反向播放。
- 混合动画的权重控制:允许动态调整混合动画的权重。
- 动画过渡效果:实现动画之间的过渡效果。
- 状态机的条件切换:在状态机中添加条件切换功能。
1. 动画循环
我们可以在 SkeletonAnimation
类中添加一个属性来控制动画是否循环播放。
public class SkeletonAnimation
{
private AnimationClip currentClip;
private float currentTime;
private float speed = 1f; // 动画速度
private bool loop = false; // 是否循环播放
public bool Loop
{
get => loop;
set => loop = value;
}
public void Update(float deltaTime)
{
if (currentClip == null) return;
currentTime += deltaTime * speed; // 根据速度调整时间
if (loop)
{
currentTime %= currentClip.Duration; // 循环播放
}
// 处理关键帧插值
// ...(与之前相同的逻辑)
}
}
2. 动画反向播放
我们可以在 SkeletonAnimation
类中添加一个属性来控制动画的播放方向。
public class SkeletonAnimation
{
private AnimationClip currentClip;
private float currentTime;
private float speed = 1f; // 动画速度
private bool loop = false; // 是否循环播放
private bool reverse = false; // 是否反向播放
public bool Reverse
{
get => reverse;
set => reverse = value;
}
public void Update(float deltaTime)
{
if (currentClip == null) return;
currentTime += (reverse ? -1 : 1) * deltaTime * speed; // 根据方向调整时间
if (loop)
{
currentTime = (currentTime + currentClip.Duration) % currentClip.Duration; // 循环播放
}
// 处理关键帧插值
// ...(与之前相同的逻辑)
}
}
3. 混合动画的权重控制
我们可以在 AnimationLayer
中添加一个方法来动态调整混合动画的权重。
public class AnimationLayer
{
public string Name { get; private set; }
public SkeletonAnimation Animation { get; private set; }
public float Weight { get; set; }
public AnimationLayer(string name, SkeletonAnimation animation, float weight = 1f)
{
Name = name;
Animation = animation;
Weight = weight;
}
public void Update(float deltaTime)
{
Animation.Update(deltaTime);
}
public void SetWeight(float weight)
{
Weight = Mathf.Clamp01(weight); // 确保权重在0到1之间
}
}
4. 动画过渡效果
我们可以在 AnimationController
中实现动画之间的过渡效果。
public class AnimationController
{
private List<AnimationLayer> layers = new List<AnimationLayer>();
private float transitionDuration = 0.5f; // 过渡持续时间
private float transitionTime = 0f; // 当前过渡时间
public void AddLayer(AnimationLayer layer)
{
layers.Add(layer);
}
public void TransitionTo(string newState)
{
// 这里可以实现从当前状态到新状态的过渡逻辑
transitionTime = 0f; // 重置过渡时间
// 其他逻辑...
}
public void Update(float deltaTime)
{
transitionTime += deltaTime;
float t = Mathf.Clamp01(transitionTime / transitionDuration);
foreach (var layer in layers)
{
layer.Update(deltaTime);
// 这里可以根据权重和过渡时间调整动画的混合
}
}
}
5. 状态机的条件切换
我们可以在 AnimationStateMachine
中添加条件切换功能,以便根据特定条件切换动画状态。
public class AnimationStateMachine
{
private Dictionary<string, AnimationClip> animations = new Dictionary<string, AnimationClip>();
private SkeletonAnimation skeletonAnimation;
private string currentState;
public AnimationStateMachine(SkeletonAnimation skeletonAnimation)
{
this.skeletonAnimation = skeletonAnimation;
}
public void AddAnimation(string state, AnimationClip clip)
{
animations[state] = clip;
}
public void Play(string state)
{
if (currentState != state)
{
currentState = state;
skeletonAnimation.Play(animations[state]);
}
}
public void Update(float deltaTime)
{
skeletonAnimation.Update(deltaTime);
// 这里可以添加条件切换逻辑
}
}
6. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip1 = new AnimationClip();
animationClip1.AddKeyFrame(0f, 0f);
animationClip1.AddKeyFrame(1f, 1f);
animationClip1.Loop = true; // 设置为循环播放
var animationClip2 = new AnimationClip();
animationClip2.AddKeyFrame(0f, 1f);
animationClip2.AddKeyFrame(1f, 0f);
animationClip2.Reverse = true; // 设置为反向播放
var stateMachine = new AnimationStateMachine(skeletonAnimation);
stateMachine.AddAnimation("Idle", animationClip1);
stateMachine.AddAnimation("Run", animationClip2);
// 播放动画
stateMachine.Play("Idle");
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
// 切换到另一个动画
stateMachine.Play("Run");
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
}
}
7. 总结
通过以上扩展,我们实现了一个更为复杂的动画系统,支持动画循环、反向播放、混合动画的权重控制、动画过渡效果和状态机的条件切换。这些功能使得动画系统更加灵活和强大,能够满足更复杂的游戏需求。
我们可以继续扩展这个动画系统,增加更多的功能和细节。以下是一些可以考虑的扩展:
- 动画事件:在特定时间点触发事件。
- 动画混合:支持不同动画之间的平滑过渡。
- 状态机的条件切换:根据条件自动切换动画状态。
- 动画参数:允许通过参数控制动画状态。
- 动画的暂停与恢复:支持动画的暂停和恢复功能。
1. 动画事件
我们可以在 AnimationClip
中添加事件系统,以便在特定时间点触发事件。
public class AnimationEvent
{
public float Time { get; set; }
public Action Callback { get; set; }
public AnimationEvent(float time, Action callback)
{
Time = time;
Callback = callback;
}
}
public class AnimationClip
{
public List<KeyFrame> KeyFrames { get; private set; } = new List<KeyFrame>();
public List<AnimationEvent> Events { get; private set; } = new List<AnimationEvent>();
public Action OnStart { get; set; }
public Action OnEnd { get; set; }
public void AddEvent(float time, Action callback)
{
Events.Add(new AnimationEvent(time, callback));
}
public void Play()
{
OnStart?.Invoke();
}
public void Complete()
{
OnEnd?.Invoke();
}
public void Update(float currentTime)
{
foreach (var animationEvent in Events)
{
if (Math.Abs(currentTime - animationEvent.Time) < 0.01f)
{
animationEvent.Callback?.Invoke();
}
}
}
}
2. 动画混合
我们可以在 AnimationController
中实现动画混合的功能,以便在不同动画之间进行平滑过渡。
public class AnimationController
{
private List<AnimationLayer> layers = new List<AnimationLayer>();
private float transitionDuration = 0.5f; // 过渡持续时间
private float transitionTime = 0f; // 当前过渡时间
private string currentState;
public void AddLayer(AnimationLayer layer)
{
layers.Add(layer);
}
public void TransitionTo(string newState)
{
if (currentState != newState)
{
transitionTime = 0f; // 重置过渡时间
currentState = newState;
// 其他逻辑...
}
}
public void Update(float deltaTime)
{
transitionTime += deltaTime;
float t = Mathf.Clamp01(transitionTime / transitionDuration);
foreach (var layer in layers)
{
layer.Update(deltaTime);
// 这里可以根据权重和过渡时间调整动画的混合
}
}
}
3. 状态机的条件切换
我们可以在 AnimationStateMachine
中添加条件切换功能,以便根据特定条件切换动画状态。
public class AnimationStateMachine
{
private Dictionary<string, AnimationClip> animations = new Dictionary<string, AnimationClip>();
private SkeletonAnimation skeletonAnimation;
private string currentState;
public AnimationStateMachine(SkeletonAnimation skeletonAnimation)
{
this.skeletonAnimation = skeletonAnimation;
}
public void AddAnimation(string state, AnimationClip clip)
{
animations[state] = clip;
}
public void Play(string state)
{
if (currentState != state)
{
currentState = state;
skeletonAnimation.Play(animations[state]);
}
}
public void Update(float deltaTime)
{
skeletonAnimation.Update(deltaTime);
// 这里可以添加条件切换逻辑
}
}
4. 动画参数
我们可以在 AnimationStateMachine
中添加参数,以便通过参数控制动画状态。
public class AnimationStateMachine
{
private Dictionary<string, AnimationClip> animations = new Dictionary<string, AnimationClip>();
private SkeletonAnimation skeletonAnimation;
private string currentState;
private Dictionary<string, float> parameters = new Dictionary<string, float>();
public AnimationStateMachine(SkeletonAnimation skeletonAnimation)
{
this.skeletonAnimation = skeletonAnimation;
}
public void AddAnimation(string state, AnimationClip clip)
{
animations[state] = clip;
}
public void SetParameter(string name, float value)
{
parameters[name] = value;
}
public void Update(float deltaTime)
{
// 根据参数决定状态切换
if (parameters.TryGetValue("Speed", out float speed) && speed > 0)
{
Play("Run");
}
else
{
Play("Idle");
}
skeletonAnimation.Update(deltaTime);
}
}
5. 动画的暂停与恢复
我们可以在 SkeletonAnimation
中添加暂停和恢复的功能。
public class SkeletonAnimation
{
private AnimationClip currentClip;
private float currentTime;
private float speed = 1f; // 动画速度
private bool isPaused = false; // 是否暂停
public void Pause()
{
isPaused = true;
}
public void Resume()
{
isPaused = false;
}
public void Update(float deltaTime)
{
if (isPaused || currentClip == null) return;
currentTime += deltaTime * speed; // 根据速度调整时间
// 处理关键帧插值
// ...(与之前相同的逻辑)
}
}
6. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip1 = new AnimationClip();
animationClip1.AddKeyFrame(0f, 0f);
animationClip1.AddKeyFrame(1f, 1f);
animationClip1.AddEvent(0.5f, () => Console.WriteLine("Halfway through Animation 1!"));
animationClip1.OnStart = () => Console.WriteLine("Animation 1 Started!");
animationClip1.OnEnd = () => Console.WriteLine("Animation 1 Ended!");
var animationClip2 = new AnimationClip();
animationClip2.AddKeyFrame(0f, 1f);
animationClip2.AddKeyFrame(1f, 0f);
animationClip2.OnStart = () => Console.WriteLine("Animation 2 Started!");
animationClip2.OnEnd = () => Console.WriteLine("Animation 2 Ended!");
var stateMachine = new AnimationStateMachine(skeletonAnimation);
stateMachine.AddAnimation("Idle", animationClip1);
stateMachine.AddAnimation("Run", animationClip2);
// 设置参数
stateMachine.SetParameter("Speed", 0); // Idle状态
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
// 切换到另一个动画
stateMachine.SetParameter("Speed", 1); // Run状态
stateMachine.Update(0.1f);
// 暂停动画
skeletonAnimation.Pause();
Console.WriteLine("Animation Paused");
// 恢复动画
skeletonAnimation.Resume();
Console.WriteLine("Animation Resumed");
// 更新动画
for (float time = 0; time <= 2f; time += 0.1f)
{
stateMachine.Update(0.1f);
}
}
}
7. 总结
通过以上扩展,我们实现了一个更为复杂的动画系统,支持动画事件、动画混合、状态机的条件切换、动画参数控制以及动画的暂停与恢复。这些功能使得动画系统更加灵活和强大,能够满足更复杂的游戏需求。
我们可以继续深入探讨动画系统的更多功能和实现细节。以下是一些可以进一步扩展的方面:
8. 动画层(Animation Layers)
动画层允许我们在同一角色上叠加多个动画。例如,角色可以同时进行行走和挥手的动画。我们可以为每个动画层设置权重,以控制它们的影响程度。
public class AnimationLayer
{
public string Name { get; private set; }
public AnimationClip Clip { get; private set; }
public float Weight { get; set; } = 1.0f; // 默认权重为1.0
public bool IsActive { get; set; } = true;
public AnimationLayer(string name, AnimationClip clip)
{
Name = name;
Clip = clip;
}
public void Update(float deltaTime)
{
if (IsActive)
{
Clip.Update(deltaTime);
}
}
}
9. 混合动画(Blend Animations)
我们可以实现一个简单的混合动画功能,以便在多个动画层之间进行平滑过渡。
public class AnimationController
{
private List<AnimationLayer> layers = new List<AnimationLayer>();
private float transitionDuration = 0.5f;
private float transitionTime = 0f;
private string currentState;
public void AddLayer(AnimationLayer layer)
{
layers.Add(layer);
}
public void Update(float deltaTime)
{
transitionTime += deltaTime;
float t = Mathf.Clamp01(transitionTime / transitionDuration);
foreach (var layer in layers)
{
layer.Update(deltaTime);
// 这里可以根据权重和过渡时间调整动画的混合
if (layer.IsActive)
{
// 处理混合逻辑
// 例如:根据权重调整当前动画的影响
}
}
}
}
10. 动画反向播放
我们可以为动画添加反向播放的功能,以便在需要时可以反向播放动画。
public class AnimationClip
{
public List<KeyFrame> KeyFrames { get; private set; } = new List<KeyFrame>();
public bool IsPlaying { get; private set; } = false;
public bool IsReversed { get; set; } = false; // 是否反向播放
public void Play()
{
IsPlaying = true;
}
public void Stop()
{
IsPlaying = false;
}
public void Update(float deltaTime)
{
if (!IsPlaying) return;
// 根据播放方向更新当前时间
if (IsReversed)
{
// 反向播放逻辑
}
else
{
// 正向播放逻辑
}
}
}
11. 动画循环与重复
我们可以为动画添加循环和重复的功能,以便在需要时可以重复播放动画。
public class AnimationClip
{
public bool Loop { get; set; } = false; // 是否循环播放
private float currentTime;
public void Update(float deltaTime)
{
if (!IsPlaying) return;
currentTime += deltaTime;
if (Loop)
{
// 如果循环,重置时间
if (currentTime >= GetClipLength())
{
currentTime = 0f;
}
}
else
{
// 非循环播放逻辑
}
}
private float GetClipLength()
{
// 返回动画片段的总时长
return KeyFrames.Last().Time; // 假设最后一个关键帧的时间为动画时长
}
}
12. 动画过渡(Transitions)
我们可以实现动画过渡的功能,以便在不同动画之间进行平滑过渡。
public class AnimationTransition
{
public string FromState { get; private set; }
public string ToState { get; private set; }
public float Duration { get; private set; }
public AnimationTransition(string fromState, string toState, float duration)
{
FromState = fromState;
ToState = toState;
Duration = duration;
}
}
public class AnimationStateMachine
{
private List<AnimationTransition> transitions = new List<AnimationTransition>();
public void AddTransition(string fromState, string toState, float duration)
{
transitions.Add(new AnimationTransition(fromState, toState, duration));
}
public void Update(float deltaTime)
{
// 根据条件检查是否需要进行状态切换
foreach (var transition in transitions)
{
// 检查条件并执行过渡
}
}
}
13. 事件系统
我们可以实现一个事件系统,以便在动画状态变化时触发事件。
public class AnimationEventSystem
{
private Dictionary<string, Action> eventCallbacks = new Dictionary<string, Action>();
public void RegisterEvent(string eventName, Action callback)
{
eventCallbacks[eventName] = callback;
}
public void TriggerEvent(string eventName)
{
if (eventCallbacks.TryGetValue(eventName, out var callback))
{
callback?.Invoke();
}
}
}
14. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建骨骼动画
var skeletonAnimation = new SkeletonAnimation();
var animationClip1 = new AnimationClip();
animationClip1.AddKeyFrame(0f, 0f);
animationClip1.AddKeyFrame(1f, 1f);
animationClip1.Loop = true; // 设置为循环播放
var animationClip2 = new AnimationClip();
animationClip2.AddKeyFrame(0f, 1f);
animationClip2.AddKeyFrame(1f, 0f);
var layer1 = new AnimationLayer("BaseLayer", animationClip1);
var layer2 = new AnimationLayer("UpperBodyLayer", animationClip2);
var controller = new AnimationController();
controller.AddLayer(layer1);
controller.AddLayer(layer2);
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
controller.Update(0.1f);
}
// 切换动画状态
layer1.IsActive = false; // 停止基础层动画
layer2.IsActive = true; // 启动上半身动画
controller.Update(0.1f);
}
}
15. 总结
通过以上扩展,我们实现了一个更为复杂和灵活的动画系统,支持动画层、混合动画、反向播放、循环与重复、动画过渡以及事件系统。这些功能使得动画系统能够更好地适应各种游戏需求,提供更丰富的动画表现。
我们可以继续深入探讨动画系统的其他方面,特别是一些高级功能和优化策略。以下是一些可以进一步扩展的主题:
16. 动画事件(Animation Events)
动画事件允许我们在特定的时间点触发自定义事件。这对于在动画播放过程中执行特定操作(如发出声音、生成粒子效果等)非常有用。
public class AnimationEvent
{
public float Time { get; private set; }
public Action Callback { get; private set; }
public AnimationEvent(float time, Action callback)
{
Time = time;
Callback = callback;
}
}
public class AnimationClip
{
public List<AnimationEvent> Events { get; private set; } = new List<AnimationEvent>();
private float currentTime;
public void AddEvent(float time, Action callback)
{
Events.Add(new AnimationEvent(time, callback));
}
public void Update(float deltaTime)
{
if (!IsPlaying) return;
currentTime += deltaTime;
// 检查并触发事件
foreach (var animationEvent in Events)
{
if (currentTime >= animationEvent.Time)
{
animationEvent.Callback.Invoke();
Events.Remove(animationEvent); // 触发后移除事件
break; // 退出循环以避免修改集合
}
}
}
}
17. 动画混合树(Animation Blend Trees)
混合树允许我们根据输入(如角色速度、方向等)动态选择和混合多个动画。这对于实现自然的角色运动非常重要。
public class BlendTree
{
private List<AnimationClip> clips = new List<AnimationClip>();
private List<float> weights = new List<float>();
public void AddClip(AnimationClip clip, float weight)
{
clips.Add(clip);
weights.Add(weight);
}
public void Update(float deltaTime)
{
float totalWeight = weights.Sum();
foreach (var clip in clips)
{
float blendWeight = weights[clips.IndexOf(clip)] / totalWeight;
clip.Update(deltaTime * blendWeight); // 根据权重更新动画
}
}
}
18. 状态机(State Machine)
状态机可以帮助我们管理角色的不同动画状态(如行走、跑步、跳跃等),并在状态之间进行切换。
public class AnimationState
{
public string Name { get; private set; }
public AnimationClip Clip { get; private set; }
public AnimationState(string name, AnimationClip clip)
{
Name = name;
Clip = clip;
}
}
public class AnimationStateMachine
{
private Dictionary<string, AnimationState> states = new Dictionary<string, AnimationState>();
private AnimationState currentState;
public void AddState(AnimationState state)
{
states[state.Name] = state;
}
public void ChangeState(string newStateName)
{
if (states.TryGetValue(newStateName, out var newState))
{
currentState?.Clip.Stop(); // 停止当前动画
currentState = newState;
currentState.Clip.Play(); // 播放新动画
}
}
public void Update(float deltaTime)
{
currentState?.Clip.Update(deltaTime);
}
}
19. 动画压缩与优化
在实际应用中,动画数据可能会占用大量内存。我们可以实现动画压缩和优化技术,以减少内存占用并提高性能。
- 关键帧压缩:只存储变化的关键帧,减少冗余数据。
- 插值技术:使用插值算法(如线性插值、球面线性插值等)在关键帧之间平滑过渡。
- LOD(Level of Detail):根据角色与摄像机的距离选择不同精度的动画。
20. 多线程与异步加载
为了提高性能,我们可以使用多线程或异步加载技术来处理动画数据的加载和计算。
public class AnimationLoader
{
public async Task<AnimationClip> LoadAnimationAsync(string path)
{
return await Task.Run(() =>
{
// 模拟加载动画的过程
Thread.Sleep(1000); // 假设加载需要1秒
return new AnimationClip(); // 返回加载的动画
});
}
}
21. 调试与可视化
为动画系统添加调试和可视化工具,可以帮助开发者更好地理解动画的状态和行为。
- 动画预览窗口:在编辑器中显示当前动画的预览。
- 调试信息:输出当前动画状态、时间、权重等信息到控制台。
22. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建动画状态机
var stateMachine = new AnimationStateMachine();
// 创建动画片段
var walkClip = new AnimationClip();
walkClip.AddKeyFrame(0f, 0f);
walkClip.AddKeyFrame(1f, 1f);
walkClip.Loop = true;
var runClip = new AnimationClip();
runClip.AddKeyFrame(0f, 1f);
runClip.AddKeyFrame(1f, 0f);
runClip.Loop = true;
// 添加状态
stateMachine.AddState(new AnimationState("Walk", walkClip));
stateMachine.AddState(new AnimationState("Run", runClip));
// 切换状态
stateMachine.ChangeState("Walk");
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
stateMachine.Update(0.1f);
if (time == 2f) // 2秒后切换到跑步状态
{
stateMachine.ChangeState("Run");
}
}
}
}
23. 总结
通过以上扩展,我们实现了一个更为复杂和灵活的动画系统,支持动画事件、混合树、状态机、动画压缩与优化、多线程加载等功能。这些功能使得动画系统能够更好地适应各种游戏需求,提供更丰富的动画表现。
我们可以继续深入探讨动画系统的其他高级功能和实现细节。以下是一些可以进一步扩展的主题:
24. 动画混合与过渡
在复杂的动画系统中,动画混合和过渡是非常重要的功能。我们可以实现更复杂的混合逻辑,以便在不同动画之间进行平滑过渡。
24.1 混合权重
我们可以为每个动画层设置混合权重,以便在播放多个动画时控制它们的影响程度。
public class AnimationLayer
{
public string Name { get; private set; }
public AnimationClip Clip { get; private set; }
public float Weight { get; set; } = 1.0f; // 默认权重为1.0
public bool IsActive { get; set; } = true;
public AnimationLayer(string name, AnimationClip clip)
{
Name = name;
Clip = clip;
}
public void Update(float deltaTime)
{
if (IsActive)
{
Clip.Update(deltaTime * Weight); // 根据权重更新动画
}
}
}
24.2 动画过渡
我们可以实现动画过渡的功能,以便在不同动画之间进行平滑过渡。
public class AnimationTransition
{
public string FromState { get; private set; }
public string ToState { get; private set; }
public float Duration { get; private set; }
private float transitionTime;
public AnimationTransition(string fromState, string toState, float duration)
{
FromState = fromState;
ToState = toState;
Duration = duration;
}
public void Update(float deltaTime)
{
transitionTime += deltaTime;
float t = Mathf.Clamp01(transitionTime / Duration);
// 这里可以实现混合逻辑
}
}
25. 动画反向播放与暂停
我们可以为动画添加反向播放和暂停的功能,以便在需要时可以反向播放动画或暂停动画。
public class AnimationClip
{
public bool IsPlaying { get; private set; } = false;
public bool IsReversed { get; set; } = false; // 是否反向播放
private float currentTime;
public void Play()
{
IsPlaying = true;
}
public void Stop()
{
IsPlaying = false;
}
public void Pause()
{
IsPlaying = false; // 暂停播放
}
public void Resume()
{
IsPlaying = true; // 恢复播放
}
public void Update(float deltaTime)
{
if (!IsPlaying) return;
// 根据播放方向更新当前时间
if (IsReversed)
{
currentTime -= deltaTime;
if (currentTime < 0) currentTime = 0; // 确保时间不小于0
}
else
{
currentTime += deltaTime;
// 这里可以添加动画结束的逻辑
}
}
}
26. 动画层次结构
在复杂的角色动画中,可能需要实现动画的层次结构,例如角色的上半身和下半身可以独立动画。
public class CharacterAnimation
{
private AnimationLayer upperBodyLayer;
private AnimationLayer lowerBodyLayer;
public CharacterAnimation(AnimationLayer upperBody, AnimationLayer lowerBody)
{
upperBodyLayer = upperBody;
lowerBodyLayer = lowerBody;
}
public void Update(float deltaTime)
{
upperBodyLayer.Update(deltaTime);
lowerBodyLayer.Update(deltaTime);
}
}
27. 动画状态机的条件与触发器
在状态机中,我们可以添加条件和触发器,以便在特定条件下切换状态。
public class AnimationStateMachine
{
private Dictionary<string, AnimationState> states = new Dictionary<string, AnimationState>();
private AnimationState currentState;
public void AddState(AnimationState state)
{
states[state.Name] = state;
}
public void ChangeState(string newStateName)
{
if (states.TryGetValue(newStateName, out var newState))
{
currentState?.Clip.Stop(); // 停止当前动画
currentState = newState;
currentState.Clip.Play(); // 播放新动画
}
}
public void Update(float deltaTime)
{
currentState?.Clip.Update(deltaTime);
}
public void CheckTransitions()
{
// 检查条件并执行状态切换
// 例如:如果角色速度超过某个阈值,则切换到跑步状态
}
}
28. 动画的事件系统
我们可以实现一个事件系统,以便在动画状态变化时触发事件。
public class AnimationEventSystem
{
private Dictionary<string, Action> eventCallbacks = new Dictionary<string, Action>();
public void RegisterEvent(string eventName, Action callback)
{
eventCallbacks[eventName] = callback;
}
public void TriggerEvent(string eventName)
{
if (eventCallbacks.TryGetValue(eventName, out var callback))
{
callback?.Invoke();
}
}
}
29. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建动画状态机
var stateMachine = new AnimationStateMachine();
// 创建动画片段
var walkClip = new AnimationClip();
walkClip.AddKeyFrame(0f, 0f);
walkClip.AddKeyFrame(1f, 1f);
walkClip.Loop = true;
var runClip = new AnimationClip();
runClip.AddKeyFrame(0f, 1f);
runClip.AddKeyFrame(1f, 0f);
runClip.Loop = true;
// 添加状态
stateMachine.AddState(new AnimationState("Walk", walkClip));
stateMachine.AddState(new AnimationState("Run", runClip));
// 切换状态
stateMachine.ChangeState("Walk");
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
stateMachine.Update(0.1f);
if (time == 2f) // 2秒后切换到跑步状态
{
stateMachine.ChangeState("Run");
}
}
}
}
30. 总结
通过以上扩展,我们实现了一个更为复杂和灵活的动画系统,支持动画混合与过渡、反向播放与暂停、动画层次结构、状态机的条件与触发器、动画事件系统等功能。这些功能使得动画系统能够更好地适应各种游戏需求,提供更丰富的动画表现。
我们可以继续深入探讨动画系统的其他方面,特别是一些更高级的功能和实现细节。以下是一些可以进一步扩展的主题:
31. 动画的物理交互
在许多游戏中,角色的动画需要与物理系统进行交互。我们可以实现一个简单的物理交互系统,使得动画可以根据物理状态进行调整。
31.1 动画与刚体的结合
我们可以将动画与刚体结合,使得角色在播放动画时能够受到物理影响。
public class RigidBody
{
public Vector3 Velocity { get; set; }
public Vector3 Position { get; set; }
public void ApplyForce(Vector3 force)
{
Velocity += force; // 简单的力学模型
}
public void Update(float deltaTime)
{
Position += Velocity * deltaTime; // 更新位置
}
}
public class Character
{
public RigidBody RigidBody { get; private set; }
public CharacterAnimation Animation { get; private set; }
public Character()
{
RigidBody = new RigidBody();
Animation = new CharacterAnimation(new AnimationLayer("UpperBody", new AnimationClip()), new AnimationLayer("LowerBody", new AnimationClip()));
}
public void Update(float deltaTime)
{
RigidBody.Update(deltaTime);
Animation.Update(deltaTime);
}
}
32. 动画的反向运动学(IK)
反向运动学是一种用于计算关节位置的技术,以便在动画中实现更自然的运动。我们可以实现一个简单的IK系统。
32.1 基本的IK实现
我们可以创建一个简单的IK系统,使得角色的手或脚能够根据目标位置进行调整。
public class IKSystem
{
public void SolveIK(Transform endEffector, Vector3 targetPosition)
{
// 简单的IK求解逻辑
Vector3 direction = (targetPosition - endEffector.Position).normalized;
endEffector.Position = targetPosition; // 将末端执行器移动到目标位置
}
}
33. 动画的动态调整
在游戏中,角色的动画可能需要根据游戏状态进行动态调整。我们可以实现一个系统,根据角色的状态(如受伤、疲劳等)调整动画。
public class CharacterState
{
public bool IsInjured { get; set; }
public bool IsTired { get; set; }
}
public class AnimationAdjuster
{
public void AdjustAnimation(CharacterState state, CharacterAnimation animation)
{
if (state.IsInjured)
{
animation.UpperBodyLayer.Weight = 0.5f; // 减少上半身动画的权重
}
else
{
animation.UpperBodyLayer.Weight = 1.0f; // 恢复权重
}
if (state.IsTired)
{
animation.LowerBodyLayer.Weight = 0.5f; // 减少下半身动画的权重
}
else
{
animation.LowerBodyLayer.Weight = 1.0f; // 恢复权重
}
}
}
34. 动画的声音同步
在动画播放时,可能需要同步播放声音效果。我们可以实现一个声音同步系统。
public class SoundManager
{
public void PlaySound(string soundName)
{
// 播放声音的逻辑
Console.WriteLine($"Playing sound: {soundName}");
}
}
public class AnimationEventSystem
{
private SoundManager soundManager;
public AnimationEventSystem(SoundManager soundManager)
{
this.soundManager = soundManager;
}
public void TriggerSoundEvent(string eventName)
{
if (eventName == "Footstep")
{
soundManager.PlaySound("footstep.wav");
}
}
}
35. 动画的网络同步
在多人游戏中,动画需要在网络上进行同步。我们可以实现一个简单的网络同步系统。
public class NetworkManager
{
public void SendAnimationState(string playerId, string animationState)
{
// 发送动画状态到服务器
Console.WriteLine($"Sending animation state for {playerId}: {animationState}");
}
public void ReceiveAnimationState(string playerId, string animationState)
{
// 接收动画状态并更新本地动画
Console.WriteLine($"Received animation state for {playerId}: {animationState}");
}
}
36. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建角色
var character = new Character();
var animationAdjuster = new AnimationAdjuster();
var soundManager = new SoundManager();
var eventSystem = new AnimationEventSystem(soundManager);
// 更新角色状态
var state = new CharacterState { IsInjured = false, IsTired = true };
animationAdjuster.AdjustAnimation(state, character.Animation);
// 更新角色
for (float time = 0; time <= 5f; time += 0.1f)
{
character.Update(0.1f);
if (time == 2f) // 2秒后触发脚步声事件
{
eventSystem.TriggerSoundEvent("Footstep");
}
}
}
}
37. 总结
通过以上扩展,我们实现了一个更为复杂和灵活的动画系统,支持物理交互、反向运动学、动态调整、声音同步和网络同步等功能。这些功能使得动画系统能够更好地适应各种游戏需求,提供更丰富的动画表现。
我们可以继续深入探讨动画系统的其他方面,特别是一些更高级的功能和实现细节。以下是一些可以进一步扩展的主题:
38. 动画的状态机优化
在复杂的动画系统中,状态机的性能和可维护性是非常重要的。我们可以实现一些优化策略,以提高状态机的效率。
38.1 使用哈希表存储状态
使用哈希表(字典)来存储状态,可以提高状态查找的效率。
public class OptimizedAnimationStateMachine
{
private Dictionary<string, AnimationState> states = new Dictionary<string, AnimationState>();
private AnimationState currentState;
public void AddState(AnimationState state)
{
states[state.Name] = state;
}
public void ChangeState(string newStateName)
{
if (states.TryGetValue(newStateName, out var newState))
{
if (currentState != newState)
{
currentState?.Clip.Stop(); // 停止当前动画
currentState = newState;
currentState.Clip.Play(); // 播放新动画
}
}
}
public void Update(float deltaTime)
{
currentState?.Clip.Update(deltaTime);
}
}
39. 动画的事件系统增强
我们可以增强事件系统,使其支持参数化事件,以便在动画中传递更多信息。
39.1 参数化事件
public class AnimationEventSystem
{
private Dictionary<string, Action<object[]>> eventCallbacks = new Dictionary<string, Action<object[]>>();
public void RegisterEvent(string eventName, Action<object[]> callback)
{
eventCallbacks[eventName] = callback;
}
public void TriggerEvent(string eventName, params object[] parameters)
{
if (eventCallbacks.TryGetValue(eventName, out var callback))
{
callback?.Invoke(parameters);
}
}
}
40. 动画的层级与组合
在复杂的角色动画中,可能需要实现动画的层级与组合,以便更好地管理和复用动画。
40.1 动画组合
我们可以创建一个组合动画类,以便将多个动画组合在一起。
public class CompositeAnimation
{
private List<AnimationClip> clips = new List<AnimationClip>();
public void AddClip(AnimationClip clip)
{
clips.Add(clip);
}
public void Update(float deltaTime)
{
foreach (var clip in clips)
{
clip.Update(deltaTime);
}
}
}
41. 动画的时间缩放
在某些情况下,可能需要根据游戏状态动态调整动画的播放速度。我们可以实现一个时间缩放功能。
public class AnimationClip
{
public float Speed { get; set; } = 1.0f; // 默认速度为1.0
private float currentTime;
public void Update(float deltaTime)
{
currentTime += deltaTime * Speed; // 根据速度更新当前时间
// 这里可以添加动画结束的逻辑
}
}
42. 动画的调试工具
在开发过程中,调试动画系统是非常重要的。我们可以实现一些调试工具,以便更好地监控动画状态。
42.1 动画状态监控
public class AnimationDebugger
{
public void LogCurrentState(OptimizedAnimationStateMachine stateMachine)
{
Console.WriteLine($"Current Animation State: {stateMachine.CurrentState?.Name}");
}
public void LogAnimationProgress(AnimationClip clip)
{
Console.WriteLine($"Current Animation Time: {clip.CurrentTime}");
}
}
43. 示例用法
以下是如何使用这些扩展的示例:
public class Program
{
public static void Main(string[] args)
{
// 创建优化的动画状态机
var stateMachine = new OptimizedAnimationStateMachine();
// 创建动画片段
var walkClip = new AnimationClip();
walkClip.AddKeyFrame(0f, 0f);
walkClip.AddKeyFrame(1f, 1f);
walkClip.Loop = true;
var runClip = new AnimationClip();
runClip.AddKeyFrame(0f, 1f);
runClip.AddKeyFrame(1f, 0f);
runClip.Loop = true;
// 添加状态
stateMachine.AddState(new AnimationState("Walk", walkClip));
stateMachine.AddState(new AnimationState("Run", runClip));
// 切换状态
stateMachine.ChangeState("Walk");
// 创建调试工具
var debugger = new AnimationDebugger();
// 更新动画
for (float time = 0; time <= 5f; time += 0.1f)
{
stateMachine.Update(0.1f);
debugger.LogCurrentState(stateMachine);
debugger.LogAnimationProgress(walkClip);
if (time == 2f) // 2秒后切换到跑步状态
{
stateMachine.ChangeState("Run");
}
}
}
}
44. 总结
通过以上扩展,我们实现了一个更为复杂和灵活的动画系统,支持状态机优化、事件系统增强、动画组合、时间缩放和调试工具等功能。这些功能使得动画系统能够更好地适应各种游戏需求,提供更丰富的动画表现。