我正在构建一个由手机组成的简单游戏 - 游戏角色(Mobs)。每个暴徒都可以执行某些功能。为了向Mob提供该功能,我创建了一个Behavior。
例如,假设一个暴徒需要在游戏领域移动,我会给它一个MoveBehavior - 这被添加到一个内部的暴徒类行为列表中:
// Defined in the Mob class
List<Behavior> behaviors;
// Later on, add behavior...
movingMob.addBehavior(new MovingBehavior());
我的问题是这个。大多数行为都会操纵一些关于暴民的事情。在MoveBehavior示例中,它将改变世界中暴徒的X,Y位置。但是,每个行为都需要特定的信息,例如“movementRate” - 应该在哪里存储movingRate?
它应该存储在Mob类中吗?其他怪物可能会通过减慢/加速暴徒来尝试与它进行交互,并且在暴徒级别更容易进入......但并非所有怪物都有动作速度,因此会造成混乱。
或者它应该存储在MoveBehavior类中?这会将它隐藏起来,使其他怪物与之交互更加困难 - 但它不会使具有额外和未使用属性的非移动怪物混乱(例如,不移动的塔将永远不需要使用movementRate)。
答案 0 :(得分:4)
这是典型的“行为构成”问题。权衡是行为越独立,他们彼此互动就越困难。
从游戏编程的角度来看,最简单的解决方案是决定一组“核心”或“引擎”数据,并将其放在主要对象中,然后让行为能够访问和修改这些数据 - 可能通过功能界面。
如果您想要特定于行为的数据,那很好,但为了避免变量名称中的冲突,您可能希望使用于访问它的接口包括行为名称。像:
obj.getBehaviorValue("movement", "speed")
obj.setBehaviorValue("movement", "speed", 4)
这样两种行为都可以定义自己的变量,称为速度而不是碰撞。这种类型的跨行为getter和setter将允许在需要时进行通信。
我建议你看一下像Lua或Python这样的脚本语言。
答案 1 :(得分:2)
如果它与行为有关,并且只在该行为的上下文中有意义,那么它应该作为其一部分存储。
移动速度只对可以移动的东西有意义。这意味着它应该存储为代表其移动能力的对象的一部分,这似乎是你的MoveBehavior。
如果这样做太难以访问,那听起来更像是您设计的问题。那么问题不是“我应该作弊,并将一些变量放在Mob类中而不是它所属的行为中”,而是“如何让它更容易与这些个体行为进行交互”。
我可以想到几种实现方法。显而易见的是Mob类上的一个简单的成员函数,它允许您选择单个行为,如下所示:
class Mob {
private List<Behavior> behaviors;
public T Get<T>(); // try to find the requested behavior type, and return it if it exists
}
其他人可以这样做:
Mob m;
MovementBehavior b = m.Get<MovementBehavior();
if (b != null) {
b.SetMovementRate(1.20f);
}
你也可以在Mob类之外放置一些,创建一个帮助函数,如果它存在则修改移动速率,否则什么都不做:
static class MovementHelper {
public static SetMovementRate(Mob m, float movementrate){
MovementBehavior b = m.Get<MovementBehavior();
if (b != null) {
b.SetMovementRate(1.20f);
}
}
}
然后其他人可以像这样使用它:
MovementHelper.SetMovementRate(m, 1.20f);
可以轻松访问修改行为,但不会使Mob类混乱。 (当然,将它转换为扩展方法很诱人,但这可能会导致Mob类的公共接口中出现过多的混乱。最好明确指出这是辅助功能,它位于外部Mob类本身)
答案 2 :(得分:2)
您可以从WPF(附加属性)借用模式。 WPF人员需要一种方法来在运行时将属性附加到控件。 (例如,如果你将一个控件放在一个网格中,那么控件有一个Row属性会很好 - 他们伪做了附加属性。
它的工作原理如下:(请注意,这可能与WPF的实现并不完全匹配,而且我正在省略依赖属性注册,因为您没有使用XAML)
public class MoveBehavior: Behavior
{
private static Dictionary<Mob, int> MovementRateProperty;
public static void SetMovementRate(Mob theMob, int theRate)
{
MovementRateProperty[theMob] = theRate;
}
public static int GetMovementRate(Mob theMob)
{
// note, you will need handling for cases where it doesn't exist, etc
return MovementRateProperty[theMob];
}
}
这里的事情是,行为拥有该属性,但你不必去探索它以获取它这里有一些代码来检索暴徒的移动速度:
// retrieve the rate for a given mob
int rate = MoveBehavior.GetMovementRate(theMob);
// set the rate for a given mob
MoveBehavior.SetMovementRate(mob, 5);
答案 3 :(得分:2)
看看组件系统/实体系统设计:
http://www.devmaster.net/articles/oo-game-design/
到目前为止我见过的最好的。
聪明的人说这是使用大型游戏的唯一方式,但它需要改变你对OOP的看法。
答案 4 :(得分:0)
您还可以创建一个基本Mob类,并派生一个MovingMob类。但我猜,这并不适用,一旦显然你可以有不同行为的任意组合......
- 编辑 -
首先,显然你不会在同一个Mob中有两次相同类型的行为(比如,没有暴徒在同一类型下有两个movingBehaviors),所以在这种情况下,一个集合是更好的选择,因为它避免了重复
你可以在每个暴徒中使用一种方法,如
public Behavior GetBehavior(Type type)
{
foreach (var behavior in behaviorHashSet)
{
if ( behavior.GetType() == type)
return behavior;
}
return null;
}
一旦你有了Mob,你可以用这种行为做任何你想做的事。此外,您可以更改GetHashCode()和Equals()方法以确保您没有重复,或使GetBehavior方法更快(恒定时间)
答案 5 :(得分:0)
编辑:如果你没有为每个暴徒实例化一个新的MovingBehavior,但只有一个单独的MovingBehavior,下面的答案才真正有意义。
我会说,当调用addBehavior()
时,暴徒(呃,我讨厌游戏NPC的那个词,但这不是你的错)应该得到一个从addBehavior返回的BehaviorState
对象()并且它保持不变,并且与所添加的行为有关。然后为MovingBehavior
提供一个界面,以便从BehaviorState
轻松检索其movingMob
对象,并存储在那里存储的任何内容。
答案 6 :(得分:0)
存储移动速率数据的最简单方法是什么?
如果仅在MoveBehavior类中需要它,那么它应该在那里:
public class MoveBehavior {
public int MovementRate { get; set; }
}
如果Mob类本身需要它,那么它将更容易通过Mob类暴露:
public class Mob {
public int MovementRate { get; set; }
}
public class MoveBehavior {
public MoveBehavior(Mob mob) { MobInEffect = mob; }
public Mob MobInEffect {get; set;}
// can access MovementRate through MovInEffect.MovementRate
}
所以这一切都取决于你用这种行为逻辑试图实现的目标。我建议你推动设计决策,直到你真的需要这样或那样做。首先要集中精力做事,然后重构。通常情况下,早期的设计猜测可能会导致架构过于复杂。
我的意思是你首先在Mob
课程中实现你想要的任何动作:
public class Mob {
// Constructors and stuff here
public void Move(long ticks)
{
// do some voodoo magic with movement and MovementRate here
}
protected int MovementRate { get; set; }
}
当有效时,如果你真的需要,请将该实现删除到MoveBehavior
类:
public class Mob {
// Constructors and stuff here
public MoveBehavior Moving { set; get; }
public void Move(long ticks)
{
Moving.Move(ticks, this);
}
}
public class MoveBehavior {
protected int MovementRate { get; set; }
public void Move(long ticks, Mob mob)
{
// logic moved over here now
}
}
之后,如果你确实需要做多种行为,但是他们共享一个公共接口,那么之后创建该接口并让行为实现它。
答案 7 :(得分:0)
如果我正在设计这样的东西,我会尝试使用接口来定义暴徒有哪些行为:
public interface IMovable
{
int MovementRate { get; set; }
void MoveTo(int x, int y);
}
public class Monster : Mob, IMovable
{
public int MovementRate { get; set; }
public void MoveTo(int x, int y)
{
// ...
}
}
通过这种方式你可以检查一下暴徒是否可以移动:
Monster m = new Monster();
if (m is IMovable)
{
m.MoveTo(someX, someY);
}