代码之家  ›  专栏  ›  技术社区  ›  bugfixr

游戏对夫妇或不对夫妇的架构?

  •  10
  • bugfixr  · 技术社区  · 16 年前

    我正在构建一个由手机组成的简单游戏——游戏内角色(mobs)。每个暴徒都可以执行某些功能。为了给暴徒提供这个功能,我创建了一个行为。

    例如,假设一个mob需要在游戏场周围移动,我会给它movebehavior-这会添加到mob类的内部行为列表中:

    // Defined in the Mob class
    List<Behavior> behaviors;
    
    // Later on, add behavior...
    movingMob.addBehavior(new MovingBehavior());
    

    我的问题是。大多数行为都会操纵黑帮的某些东西。在movebehavior示例中,它将改变mob在世界上的x,y位置。但是,每个行为都需要特定的信息,例如“movementRate”--应该将movementRate存储在哪里?

    它应该存储在mob类中吗?其他的暴徒可能会试图通过减缓/加速暴徒的速度来与之互动,而且在暴徒层面上更容易接近……但并不是所有的暴徒都有移动率,所以会造成混乱。

    还是应该存储在moveBehavior类中?这会将其隐藏起来,使其他暴徒更难与之互动-但它不会将非移动暴徒与额外的和未使用的属性(例如,不移动的塔不需要使用移动速率)混淆起来。

    8 回复  |  直到 16 年前
        1
  •  4
  •   sean riley    16 年前

    这是典型的“行为组合”问题。权衡的是,行为越独立,他们之间的互动就越困难。

    从游戏编程的角度来看,最简单的解决方案是决定一组“核心”或“引擎”数据,并将其放在主对象中,然后让行为能够访问和修改该数据-可能通过功能接口。

    如果您想要特定于行为的数据,这很好,但是为了避免变量名称中的冲突,您可能需要使用于访问它的接口包含行为名称。像:

    obj.getBehaviorValue("movement", "speed")
    
    obj.setBehaviorValue("movement", "speed", 4)
    

    这样,两个行为都可以定义自己的变量,称为速度而不是碰撞。这种类型的交叉行为getter和setter允许在需要时进行通信。

    为此,我建议您考虑使用Lua或Python之类的脚本语言。

        2
  •  2
  •   JMarsch    16 年前

    您可以从WPF(附加属性)中借用模式。WPF人员需要一种在运行时将属性附加到控件的方法。(例如,如果将控件放在网格中,则该控件最好有一个行属性——它们对附加属性进行了伪操作。

    它的工作方式如下:(注意,这可能与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
  •   Stack Overflow is garbage    16 年前

    如果它与行为相关,并且只在该行为的上下文中有意义,那么它应该作为它的一部分存储。

    移动速率只对可以移动的东西有意义。这意味着它应该作为对象的一部分存储,表示它的移动能力,这似乎是您的移动行为。

    如果这使得访问变得太困难,听起来更像是您的设计问题。那么问题不是“我应该作弊,将一些变量放在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类本身之外的助手功能)

        4
  •  2
  •   Mikeon    16 年前

    看看组件系统/实体系统设计:

    http://www.devmaster.net/articles/oo-game-design/

    到目前为止我所见过的最好的。

    聪明的人说这是玩大型游戏的唯一方法,但这需要改变你对OOP的看法。

        5
  •  0
  •   Spoike Otávio Décio    16 年前

    你想做什么?

    存储移动速率数据的最简单方法是什么?

    如果只在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
        }
    }
    

    之后,如果您确实需要执行多种类型的行为,但它们共享一个公共接口,那么在此之前创建该接口,并让行为实现该接口。

        6
  •  0
  •   chaos    16 年前

    编辑:下面的答案只有当你没有为每一个暴徒展示一个新的移动行为,但只有一个单一的移动行为才真正有意义。

    我想说黑帮(呃,我讨厌游戏NPC这个词,但这不是你的错)应该在什么时候 addBehavior() 被召唤,得到一个 BehaviorState 从addBehavior()返回并保留的对象,并将其键控到添加的行为。然后提供接口 MovingBehavior 以便于检索 行为状态 对象从 movingMob 它可以存储任何需要存储的东西。

        7
  •  0
  •   Jason Miesionczek    16 年前

    如果我正在设计这样的东西,我会尝试使用接口来定义暴徒的行为:

    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);
    }
    
        8
  •  0
  •   Samuel Carrijo    16 年前

    imho,移动速度与移动行为相关,而不是与暴徒本身相关,正如你所说,它不一定移动。所以变量应该和行为联系在一起,移动率的改变是对他的行为的改变,而不是对暴徒本身的改变。

    您还可以创建一个基本mob类,并派生一个movingmob类。但我想,这并不真正适用,一旦很明显你可以任意组合不同的行为…

    --编辑——

    首先,显然在同一个mob中不会有两次相同的行为类型(例如,没有一个mob具有相同类型的两个movementbehaviors),因此在这种情况下,集合是更好的选择,因为它可以避免重复

    你可以在每一个黑帮中找到一个方法

        public Behavior GetBehavior(Type type)
        {
            foreach (var behavior in behaviorHashSet)
            {
                if ( behavior.GetType() == type)
                    return behavior;
    
            }
            return null;
        }
    

    一旦你有了暴徒,你就可以做任何你想做的事情。此外,还可以更改gethashcode()和equals()方法以确保没有重复项,或者使getbehavior方法更快(常量时间)