游戏架构 - 夫妻双方是否结合?

时间:2009-06-25 16:50:35

标签: c# architecture

我正在构建一个由手机组成的简单游戏 - 游戏角色(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)。

8 个答案:

答案 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)

恕我直言,移动速度与movingBehavior相关,而不是与Mob本身相关,正如你所说,它并不一定会移动。所以变量应该与行为相关联,movingRate的变化是对他行为的改变,而不是暴徒自己。

您还可以创建一个基本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);
}