我第三次遇到这个设计问题,我觉得有一个解决方案,我根本无法弄清楚。我对以前解决它的方式不满意,所以这就是你进来的地方。
假设我正在设计一个与其使用的系统无关的(C#)库。所以我有以下类:
public interface IAction
{
void Execute();
}
public class Trigger
{
private List<IAction> actions;
public void CheckConditions()
{
if (AllConditionsMet())
{
ExecuteAllActions();
}
}
private void ExecuteAllActions()
{
foreach (IAction action in actions)
{
action.Execute();
}
}
protected abstract bool AllConditionsMet();
}
public class BigBulkySystem
{
private List<Trigger> triggers;
public void CheckAllTriggers()
{
foreach (Trigger trigger in triggers)
{
trigger.CheckConditions();
}
}
}
到目前为止一切顺利。我们在BigBulkySystem中有一个触发器列表,我们经常检查它们的条件。如果满足条件,触发器将负责执行其操作列表。
我现在遇到的问题是我想实现专为Unity设计的图层。在Unity中,您使用coroutines以便能够在继续执行方法之前等待几帧。这正是我想要的Trigger.ExecuteAllActions方法。我想等待每个动作完成执行,然后再转到下一个。为了做到这一点,我需要为Trigger和IAction提供不同的签名。以下是Unity中的情况:
public interface IAction
{
IEnumerator Execute();
}
public class Trigger
{
private List<IAction> actions;
public void CheckConditions()
{
if (AllConditionsMet())
{
StartCoroutine(ExecuteAllActions());
}
}
public IEnunmerator ExecuteAllActions()
{
foreach (IAction action in actions)
{
yield return StartCoroutine(action.Execute());
}
}
}
请注意,现在有两种方法返回IEnumerator而不是void,这就是在Unity中定义协同程序的方式。
以下是Unity动作的示例,该动作在3秒延迟后的5秒内将玩家对象从位置(0,0,0)移动到(1,1,1):
public class MovePlayerAction : IAction
{
public GameObject playerObject;
public IEnumerator Execute()
{
yield return StartCoroutine(WaitFor(3)); // Wait 3 secs
float animDuration = 5.0f;
Vector3 initialPos = Vector3.zero;
Vector3 finalPos = Vector3.one;
float t = 0;
while (t < animDuration)
{
playerObject.transform.position = Vector3.Lerp(initialPos, finalPos, t / animDuration); // Lerp between initial and final
yield return null; // Wait a frame
t += Time.deltaTime;
}
}
private IEnumerator WaitFor(float secs)
{
float t = 0;
while (t < secs)
{
yield return null; // Wait a single frame
t += Time.deltaTime;
}
}
}
所以,我的问题是:有没有办法以这样一种方式设计系统,即我可以让非Unity层免受IEnumerator的影响,但Unity层也可以使用它们吗? < / p>
我觉得有某种中间层,但即便如此,我也无法弄清楚IAction如何同时拥有void Execute()
和IEnumerator Execute()
。
谢谢!
答案 0 :(得分:1)
当您使用与所需接口不同的接口的类时,您需要适配器模式。这有几个变种,但对于你的情况,一个简单的变体看起来像这样:
public interface IAction
{
void Execute();
}
public interface IUnityAction
{
IEnumerator Execute();
}
public class UnityActionAdapter : IUnityAction
{
private readonly IAction Action;
public UnityActionAdapter(IAction action)
{
Action = action;
}
public IEnumerator Execute()
{
Action.Execute();
yield break; //Or whatever you need to yield here
}
}
<强>更新强>
在你的情况下,看起来你正在适应另一个方向,所以:
public class UnityActionAdapter : IAction
{
private readonly IUnityAction UnityAction;
public UnityActionAdapter(IAction unityAction)
{
UnityAction = action;
}
public void Execute()
{
StartCoroutine(UnityAction.Execute()); //If I'm correctly understanding how you'd execute an action here
}
}
然后你有MovePlayerAction
实施IUnityAction
,而不是直接将MovePlayerAction
传递给你的其他图层,而是将其包装在UnityActionAdapter
中相反,传递它。
这假设您确实需要在Unity层的界面中维护IEnumerator
表单。通过执行以下操作,您可以在不需要适配器的情况下实现相同的目标:
public class MovePlayerAction : IAction
{
public GameObject playerObject;
private IEnumerator Coroutine()
{
yield return StartCoroutine(WaitFor(3)); // Wait 3 secs
float animDuration = 5.0f;
Vector3 initialPos = Vector3.zero;
Vector3 finalPos = Vector3.one;
float t = 0;
while (t < animDuration)
{
playerObject.transform.position = Vector3.Lerp(initialPos, finalPos, t / animDuration); // Lerp between initial and final
yield return null; // Wait a frame
t += Time.deltaTime;
}
}
public void Execute()
{
StartCoroutine(Coroutine());
}
private IEnumerator WaitFor(float secs)
{
float t = 0;
while (t < secs)
{
yield return null; // Wait a single frame
t += Time.deltaTime;
}
}
}
答案 1 :(得分:0)
如果您使用任务并行库怎么办?
Trigger的示例代码,我看到我们可以实现这个(考虑你的非IEnumerator代码):
public class Trigger
{
private readonly List<Task> TaskActions = new List<Task>();
public void AddAction(IAction action)
{
TaskActions.Add(new Task(()=>{action.Execute();})
}
public void CheckConditions()
{
if (AllConditionsMet())
{
ExecuteAllActions();
}
}
private void ExecuteAllActions()
{
foreach(Task task in TaskActions)
{
task.Start();
}
Task.WaitAll(TaskActions);
}
protected abstract bool AllConditionsMet();
}
此致