关于c#:帮助实现生物和物品在计算机角色扮演游戏中的交互方式

Help on implementing how creatures and items interact in a computer role playing game

我正在编写一个简单的角色扮演游戏(为了学习和娱乐),我正努力想出一种让游戏对象相互作用的方法。我想避免两件事。

  • 创建一个巨大的游戏对象,可以是任何东西,可以做任何事情
  • 复杂性——所以我远离像您在这里看到的基于组件的设计
  • 因此,考虑到这些参数,我需要建议一种好的方法,让游戏对象彼此执行操作。

    例如

    • 生物(角色、怪物、NPC)可以对生物或物品(武器、药剂、陷阱、门)执行操作。
    • 项目也可以对生物或项目执行操作。举个例子,当一个角色试图打开一个箱子时,陷阱就会消失。
    • 小精灵

      我想到的是一种PerformAction方法,可以将生物或物品作为参数。这样地

      1
      2
      3
      PerformAction(Creature sourceC, Item sourceI, Creature targetC, Item targetI)
      // this will usually end up with 2 null params since
      // only 1 source and 1 target will be valid

      还是我应该这样做?

      1
      2
      PerformAction(Object source, Object target)
      // cast to correct types and continue

      还是有一种完全不同的方式让我思考这个问题?


      这是一个"双调度"问题。在常规的OO编程中,将虚拟方法调用的操作"分派"到实现所调用的对象实例的类的具体类型。客户机不需要知道实际的实现类型,它只是对抽象类型描述进行方法调用。那是"单次派遣"。

      大多数OO语言只实现单个分派。双调度是指需要调用的操作依赖于两个不同的对象。在没有直接双调度支持的情况下,用OO语言实现双调度的标准机制是"访问者"设计模式。有关如何使用此模式的信息,请参阅链接。


      您可以尝试将命令模式与一些巧妙的接口使用相混合来解决这一问题:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      // everything in the game (creature, item, hero, etc.) derives from this
      public class Entity {}

      // every action that can be performed derives from this
      public abstract class Command
      {
          public abstract void Perform(Entity source, Entity target);
      }

      // these are the capabilities an entity may have. these are how the Commands
      // interact with entities:
      public interface IDamageable
      {
          void TakeDamage(int amount);
      }

      public interface IOpenable
      {
          void Open();
      }

      public interface IMoveable
      {
          void Move(int x, int y);
      }

      然后,派生命令将向下强制转换,以查看它是否可以对目标执行所需的操作:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class FireBallCommand : Command
      {
          public override void Perform(Entity source, Entity target)
          {
              // a fireball hurts the target and blows it back
              var damageTarget = target as IDamageable;
              if (damageTarget != null)
              {
                  damageTarget.TakeDamage(234);
              }

              var moveTarget = target as IMoveable;
              if (moveTarget != null)
              {
                  moveTarget.Move(1, 1);
              }
          }
      }

      注意:

    • 派生实体只需要实现适合它的功能。

    • 基本实体类没有任何功能的代码。它既漂亮又简单。

    • 如果实体不受其影响,命令可以优雅地不做任何事情。


    • 这听起来像是多态性的例子。不要将项目或生物作为论据,而是让它们都从actionTarget或actionSource派生(或实现)。让生物或物品的实现决定从那里走哪条路。

      你很少想仅仅拿着物体就把它打开。即使是一点信息也比没有好。


      我认为你研究的问题太小了;首先,你如何确定性能函数的参数?Performation函数之外的某个函数已经知道(或者一定要知道)它要调用的操作是否需要目标,以及有多少目标,以及它操作的项或字符。关键的是,代码的某些部分必须决定正在执行的操作。你在文章中忽略了这一点,但我认为这绝对是最重要的方面,因为决定所需论点的是行动。一旦知道了这些参数,就知道了要调用的函数或方法的形式。

      假设一个角色打开了一个箱子,陷阱就消失了。您可能已经有了一个代码,它是正在打开的胸部的事件处理程序,并且您可以很容易地传入执行此操作的字符。你大概也已经确定了这个物体是一个被困的箱子。所以你已经掌握了你需要的信息:

      1
      2
      3
      4
      5
      // pseudocode
      function on_opened(Character opener)
      {
        this.triggerTrap(opener)
      }

      如果您有一个单项类,那么triggerTrap的基本实现将是空的,您需要插入一些检查,例如is_chestis_trapped。如果您有一个派生的胸部类,您可能只需要is_trapped。但实际上,这只是你做的那么困难。

      打开箱子的第一步也是一样的:你的输入代码将知道谁在行动(例如当前玩家或当前的人工智能角色),可以确定目标是什么(通过在鼠标下或命令行上找到一个项目),并可以根据输入确定所需的行动。然后它就变成了查找正确对象并用这些参数调用正确方法的一种情况。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      item = get_object_under_cursor()
      if item is not None:
          if currently_held_item is not None:
              player_use_item_on_other_item(currently_held_item, item)
          else
              player.use_item(item)
          return

      character = get_character_under_cursor()
      if character is not None:
          if character.is_friendly_to(player):
              player.talk_to(character)
          else
              player.attack(character)
          return

      保持简单。:)


      在zork模型中,可以对对象执行的每个操作都表示为该对象的方法,例如。

      1
      2
      door.Open()
      monster.Attack()

      一些普通的东西,比如性能,最终会变成一个大泥球…


      这可能不是很多人都会同意的,但我不是一个团队,它对我很有效(在大多数情况下)。

      不要把每一个对象都看作是一个东西的集合,而要把它看作是对东西的引用的集合。基本上,而不是一个庞大的清单

      1
      2
      3
      4
      Object
          - Position
          - Legs
          - [..n]

      您可以这样做(去掉值,只留下关系):

      Table showing relationships between different values

      每当你的玩家(或生物,或[.n])想要打开一个盒子时,只需呼叫

      1
      2
      3
      Player.Open(Something Target); //or
      Creature.Open(Something Target); //or
      [..n].Open(Something Target);

      其中"something"可以是一组规则,或者只是一个标识目标的整数(甚至更好的是,目标本身),如果目标存在并且确实可以打开,则打开它。

      所有这些都可以很容易地通过一系列接口实现,比如说:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      interface IDraggable
      {
            void DragTo(
                  int X,
                  int Y
            );
      }

      interface IDamageable
      {
            void Damage(
                  int A
            );
      }

      通过巧妙地使用这些接口,您甚至可能最终使用委托之类的东西在顶级之间进行抽象化。

      1
      IDamageable

      以及下级

      1
      IBurnable

      希望有帮助:)

      编辑:这很尴尬,但似乎我劫持了@munificent的答案!对不起,@慷慨!不管怎样,如果你想要一个实际的例子而不是解释这个概念是如何工作的,那就看看他的例子。

      编辑2:哦,废话。我只是看到你清楚地说你不想要你链接的文章中包含的任何东西,这和我在这里写的完全一样!如果你愿意,可以忽略这个答案,并为此道歉!


      我也有过类似的情况,虽然我的设备不是角色扮演,但设备有时与其他设备有相似的特性,但也有一些独特的特性。关键是使用接口定义一类操作,如ICanAttack,然后在对象上实现特定的方法。如果需要公共代码在多个对象之间处理此问题,并且没有明确的方法从另一个对象派生一个对象,那么只需使用带有静态方法的实用程序类来实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public interface ICanAttack { void Attack(Character attackee); }
      public class Character { ... }
      public class Warrior : Character, ICanAttack
      {
          public void Attack(Character attackee) { CharacterUtils.Attack(this, attackee); }
      }
      public static class CharacterUtils
      {
          public static void Attack(Character attacker, Character attackee) { ... }
      }

      然后,如果有代码需要确定字符是否可以执行某些操作:

      1
      2
      3
      4
      5
      6
      public void Process(Character myCharacter)
      {
          ...
          ICanAttack attacker = null;
          if ((attacker = (myCharacter as ICanAttack)) != null) attacker.Attack(anotherCharacter);
      }

      这样,您就可以明确地知道任何特定类型的字符具有哪些功能,从而获得良好的代码重用,并且代码是相对自文档化的。其主要缺点是,根据游戏的复杂程度,最终很容易得到实现许多接口的对象。


      在你的演员(生物、物品)上有一个方法在目标上执行动作怎么样?这样一来,每一个物品的行为都会有所不同,你就不会有一个巨大的方法来处理所有的单个物品/生物。

      例子:

      1
      public abstract bool PerformAction(Object target);  //returns if object is a valid target and action was performed