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

时间:2010-02-01 19:57:06

标签: c# class-design

我正在编写一个简单的角色扮演游戏(学习和娱乐),而我正试图想出一种让游戏对象互相交流的方法。我试图避免两件事。

  1. 创建一个可以做任何事情并做任何事情的巨大游戏对象
  2. 复杂性 - 所以我远离基于组件的设计,例如see here
  3. 因此,考虑到这些参数,我需要一些关于游戏对象相互执行操作的好方法的建议。

    例如

    • 生物(角色,怪物,NPC)可以对生物或物品(武器,药水,陷阱,门)进行操作
    • 项目也可以对Creatures或Items执行操作。一个例子就是当一个角色试图打开胸部时陷阱就会消失

    我提出的是一种PerformAction方法,可以将生物或物品作为参数。喜欢这个

    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
    

    或者我应该这样做吗?

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

    或者我应该考虑采用一种完全不同的方式吗?

8 个答案:

答案 0 :(得分:4)

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

大多数OO语言除了单一调度外没有实现任何其他功能。双分派是指需要调用的操作取决于两个不同的对象。在没有直接双重调度支持的情况下,在OO语言中实现双重调度的标准机制是“Visitor”设计模式。请参阅链接以了解如何使用此模式。

答案 1 :(得分:3)

这听起来像多态性的情况。而不是将Item或Creature作为参数,使它们都从ActionTarget或ActionSource派生(或实现)。让生物或物品的实施决定从那里开始。

你很少想通过拿对象让它如此开放。即使是一点信息也比没有好。

答案 2 :(得分:3)

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

// 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);
}

然后派生命令向下转换以查看它是否可以执行目标所需的内容:

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);
        }
    }
}

请注意:

  1. 派生实体只需实现适合它的功能。

  2. 基本实体类没有任何功能的代码。这很简单。

  3. 如果实体不受其影响,命令可以优雅地执行任何操作。

答案 3 :(得分:2)

我认为你正在研究问题的一小部分;你怎么在第一时间确定PerformAction函数的参数? PerformAction函数之外的东西已经知道(或以某种方式必须找出)它想要调用的动作是否需要目标,以及它操作了多少个目标和哪个项目或角色。至关重要的是,代码的某些部分必须决定正在进行的操作。你已经从帖子中省略了它,但我认为这是绝对最重要的方面,因为它是决定所需参数的动作。一旦你知道了这些参数,就会知道要调用的函数或方法的形式。

说一个角色打开了一个胸部,一个陷阱熄灭了。你可能已经拥有了代码,这个代码是打开胸部的事件处理程序,你可以很容易地传入执行它的角色。你也可能已经确定对象是一个被困的胸部。所以你已经掌握了所需的信息:

// pseudocode
function on_opened(Character opener)
{
  this.triggerTrap(opener)
}

如果您有一个Item类,triggerTrap的基本实现将为空,您需要插入某种检查,例如。 is_chestis_trapped。如果你有派生的胸部课程,你可能只需要is_trapped。但实际上,它只会像你做到的那样困难。

首先打开胸部也是如此:您的输入代码将知道谁在行动(例如,当前玩家或当前AI角色),可以确定目标是什么(通过在鼠标下找到项目) ,或在命令行上),并可以根据输入确定所需的操作。然后它只是成为查找正确对象并使用这些参数调用正确方法的情况。

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

保持简单。 :)

答案 4 :(得分:1)

在Zork模型中,对一个对象可以做的每个动作都表示为该对象的一个​​方法,例如

door.Open()
monster.Attack()
像PerformAction这样通用的东西最终将成为泥巴......

答案 5 :(得分:0)

如何在Actors(生物,物品)上使用对目标执行操作的方法。这样每个项目可以采取不同的行动,你将不会有一个大的方法来处理所有单个项目/生物。

示例:

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

答案 6 :(得分:0)

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

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) { ... }
}

然后,如果你有代码需要确定角色是否可以做某事:

public void Process(Character myCharacter)
{
    ...
    ICanAttack attacker = null;
    if ((attacker = (myCharacter as ICanAttack)) != null) attacker.Attack(anotherCharacter);
}

这样,您明确知道任何特定类型的字符具有哪些功能,您可以获得良好的代码重用,并且代码相对自我记录。这样做的主要缺点是很容易找到实现大量接口的对象,具体取决于游戏的复杂程度。

答案 7 :(得分:0)

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

不要将每个Object视为 stuff 的集合,而应将其视为引用 stuff 的集合。基本上,而不是一个巨大的许多列表

Object
    - Position
    - Legs
    - [..n]

你会有这样的事情(剥离值,只留下关系):

Table showing relationships between different values

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

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

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

所有这些都可以(非常)轻松地通过一系列接口来实现,例如:

interface IDraggable
{
      void DragTo(
            int X,
            int Y
      );
}

interface IDamageable
{
      void Damage(
            int A
      );
}

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

IDamageable

和子级别

IBurnable

希望它有所帮助:)

编辑:这很令人尴尬,但似乎我劫持了@ munificent的答案!对不起,我很抱歉!无论如何,看看他的例子,如果你想要一个实际例子,而不是解释这个概念是如何运作的。

编辑2:哦,废话。我刚看到你明确表示你不想要你链接的文章中包含的任何东西,这显然与我在这里写的一样!如果你愿意,请忽略这个答案并抱歉!