ゲーム エンジン用のアニメーション コンポーネントを作成しようとしています。アニメーション コンポーネントは、任意のゲーム オブジェクトの任意のメンバーの値を変更 (アニメーション化) する必要があります。問題は、メンバーは通常値型ですが、アニメーション コンポーネントは、変更できるようにそれらへの何らかの参照を必要とすることです。
最初はリフレクションを使おうと思ったのですが、リフレクションが遅すぎます。役立つ可能性のあるC#の他の手法(ポインター、Reflection.Emit、式ツリー、動的メソッド/オブジェクト、デリゲート、ラムダ式、クロージャー...)について読みましたが、これらのことを十分に知りません。問題を解決することができます。
アニメーション コンポーネントには、ランダム オブジェクトのメンバーへの参照を取得して保存し、その値を時間の経過とともにアニメーション化できるメソッドがあります。このように:StartSomeAnimation(ref memberToAnimate)
他のパラメータ(アニメーションの長さなど)がありますが、問題はメンバーを渡すことです。memberToAnimateへの参照は、アニメーション コンポーネントによってフレームごとに更新できるように (値型であっても) 格納する必要があります。
私が自分で解決策に最も近いのは、ラムダ式と Action<> Func<> デリゲートを使用することです (以下の例を参照)。メンバーを直接変更するよりも約 4 倍遅くなります + ガベージ割り当てがいくらか増えます。しかし、上記の例のような単純なメソッド シグネチャを作成することはまだできません。
class Program
{
static void Main(string[] args)
{
GameItem g = new GameItem();
Console.WriteLine("Initialized to:" + g.AnimatedField);
g.StartSomeAnimation();
// NOTE: in real application IntializeAnim method would create new animation object
// and add it to animation component that would call update method until
// animation is complete
Console.WriteLine("Animation started:" + g.AnimatedField);
Animation.Update();
Console.WriteLine("Animation update 1:" + g.AnimatedField);
Animation.Update();
Console.WriteLine("Animation update 2:" + g.AnimatedField);
Animation.Update();
Console.WriteLine("Animation update 3:" + g.AnimatedField);
Console.ReadLine();
}
}
class GameItem
{
public int AnimatedField;// Could be any member of any GameItem class
public void StartSomeAnimation()
{
// Question: can creation of getter and setter be moved inside the InitializeAnim method?
Animation.IntializeAnim(
() => AnimatedField, // return value of our member
(x) => this.AnimatedField = x); // set value of our member
}
}
class Animation // this is static dumb class just for simplicity's sake
{
static Action<int> setter;
static Func<int> getter;
// works fine, but we have to write getters and setters each time we start an animation
public static void IntializeAnim(Func<int> getter, Action<int> setter)
{
Animation.getter = getter;
Animation.setter = setter;
}
// Ideally we would need to pass only a member like this,
// but we get an ERROR: cannot use ref or out parameter inside an anonymous method lambda expression or query expression
public static void IntializeAnim(ref int memberToAnimate)
{
Animation.getter = () => memberToAnimate;
Animation.setter = (x) => memberToAnimate = x;
}
public static void Update()
{
// just some quick test code that queries and changes the value of a member that we animate
int currentValue = getter();
if (currentValue == 0)
{
currentValue = 5;
setter(currentValue);
}
else
setter(currentValue + currentValue);
}
}
編集:うまくいけば、質問をもう少し明確にするために、より完全な例が追加されました。ゲーム アーキテクチャではなく、ラムダ式を使用してクロージャを作成する方法に注目してください。現在、メンバーごとにアニメーション化したいため、新しいアニメーションを開始するたびに 2 つのラムダ式を記述する必要があります ( IntializeAnimメソッド)。アニメーションの開始を簡素化できますか? IntializeAnim
メソッドが現在どのように呼び出されているかを見てください。
class Program
{
static bool GameRunning = true;
static void Main(string[] args)
{
// create game items
Lamp lamp = new Lamp();
GameWolrd.AddGameItem(lamp);
Enemy enemy1 = new Enemy();
Enemy enemy2 = new Enemy();
GameWolrd.AddGameItem(enemy1);
GameWolrd.AddGameItem(enemy2);
// simple game loop
while (GameRunning)
{
GameWolrd.Update();
AnimationComponent.Update();
}
}
}
static class GameWolrd
{
static List<IGameItem> gameItems;
public static void Update()
{
for (int i = 0; i < gameItems.Count; i++)
{
IGameItem gameItem = gameItems[i];
gameItem.Update();
}
}
public static void AddGameItem(IGameItem item)
{
gameItems.Add(item);
}
}
static class AnimationComponent
{
static List<IAnimation> animations;
public static void Update()
{
for (int i = 0; i < animations.Count; i++)
{
IAnimation animation = animations[i];
if (animation.Parent == null ||
animation.Parent.IsAlive == false ||
animation.IsFinished)
{// remove animations we don't need
animations.RemoveAt(i);
i--;
}
else // update animation
animation.Update();
}
}
public static void AddAnimation(IAnimation anim)
{
animations.Add(anim);
}
}
interface IAnimation
{
void Update();
bool IsFinished;
IGameItem Parent;
}
/// <summary>
/// Game items worry only about state changes.
/// Nice state transitions/animations logics reside inside IAnimation objects
/// </summary>
interface IGameItem
{
void Update();
bool IsAlive;
}
#region GameItems
class Lamp : IGameItem
{
public float Intensity;
public float ConeRadius;
public bool IsAlive;
public Lamp()
{
// Question: can be creation of getter and setter moved
// inside the InitializeAnim method?
SineOscillation.IntializeAnim(
() => Intensity, // getter
(x) => this.Intensity = x,// setter
parent: this,
max: 1,
min: 0.3f,
speed: 2);
// use same animation algorithm for different member
SineOscillation.IntializeAnim(
() => ConeRadius, // getter
(x) => this.ConeRadius = x,// setter
parent: this,
max: 50,
min: 20f,
speed: 15);
}
public void Update()
{}
}
class Enemy : IGameItem
{
public float EyesGlow;
public float Health;
public float Size;
public bool IsAlive;
public Enemy()
{
Health = 100f;
Size = 20;
// Question: can creation of getter and setter be moved
// inside the InitializeAnim method?
SineOscillation.IntializeAnim(
() => EyesGlow, // getter
(x) => this.EyesGlow = x,// setter
parent: this,
max: 1,
min: 0.5f,
speed: 0.5f);
}
public void Update()
{
if (GotHitbyPlayer)
{
DecreaseValueAnimation.IntializeAnim(
() => Health, // getter
(x) => this.Health = x,// setter
parent: this,
amount: 10,
speed: 1f);
DecreaseValueAnimation.IntializeAnim(
() => Size, // getter
(x) => this.Size = x,// setter
parent: this,
amount: 1.5f,
speed: 0.3f);
}
}
}
#endregion
#region Animations
public class SineOscillation : IAnimation
{
Action<float> setter;
Func<float> getter;
float max;
float min;
float speed;
bool IsFinished;
IGameItem Parent;
// works fine, but we have to write getters and setters each time we start an animation
public static void IntializeAnim(Func<float> getter, Action<float> setter, IGameItem parent, float max, float min, float speed)
{
SineOscillation anim = new SineOscillation();
anim.getter = getter;
anim.setter = setter;
anim.Parent = parent;
anim.max = max;
anim.min = min;
anim.speed = speed;
AnimationComponent.AddAnimation(anim);
}
public void Update()
{
float calcualtedValue = // calculate value using sine formula (use getter if necessary)
setter(calcualtedValue);
}
}
public class DecreaseValueAnimation : IAnimation
{
Action<float> setter;
Func<float> getter;
float startValue;
float amount;
float speed;
bool IsFinished;
IGameItem Parent;
// works fine, but we have to write getters and setters each time we start an animation
public static void IntializeAnim(Func<float> getter, Action<float> setter, IGameItem parent, float amount, float speed)
{
DecreaseValueAnimation anim = new DecreaseValueAnimation();
anim.getter = getter;
anim.setter = setter;
anim.Parent = parent;
anim.amount = amount;
anim.startValue = getter();
anim.speed = speed;
AnimationComponent.AddAnimation(anim);
}
public void Update()
{
float calcualtedValue = getter() - speed;
if (calcualtedValue <= startValue - amount)
{
calcualtedValue = startValue - amount;
this.IsFinished = true;
}
setter(calcualtedValue);
}
}
#endregion