14

整个夏天我一直在学习 C#,现在我想用我迄今为止所做的事情来做一个小项目。我决定开发一种基于文本的冒险游戏。

游戏的基本结构将涉及拥有多个扇区(或房间)。进入房间后,将输出描述以及您可能采取的一些操作;在那个房间里检查、拿起、使用东西的能力;可能是战斗系统等。一个扇区最多可以连接 4 个其他扇区。

无论如何,在纸上写下关于如何为此设计代码的想法,我对部分代码的结构摸不着头脑。

我已经决定了一个玩家类和一个代表关卡/地牢/区域的“关卡”类。该级别将由许多相互关联的“部门”组成。在任何给定时间,玩家都会出现在关卡中的某个特定区域。

所以这里的混乱:

从逻辑上讲,人们会期望player.Move(Dir d)
诸如此类方法之类的方法应该更改关卡对象中的“当前扇区”字段。这意味着Player类需要了解Level类。嗯。并且Level可能必须操纵Player对象(例如,玩家进入房间,被某物伏击,从库存中丢失某些东西。)所以现在Level还需要持有对Player对象的引用?

这感觉不太好;一切都必须引用其他一切。

在这一点上,我记得在我正在使用的书中阅读了有关代表的内容。虽然我知道 C++ 中的函数指针,但在有关委托的章节中提供了具有某种“基于事件”编程观点的示例,对此我没有太多启发。

这给了我设计类的想法,如下所示:

玩家:

class Player
{
    //...

    public delegate void Movement(Dir d);   //enum Dir{NORTH, SOUTH, ...}

    public event Movement PlayerMoved;

    public void Move(Dir d)
    {        
        PlayerMoved(d);

        //Other code...
    }

}

等级:

class Level
{
    private Sector currSector;
    private Player p;
    //etc etc...

    private void OnMove(Dir d)
    {
        switch (d)
        {
            case Dir.NORTH:
                //change currSector
                //other code
                break;

                //other cases
        }
    }

    public Level(Player p)
    {
        p.PlayerMoved += OnMove;  
        currSector = START_SECTOR;
        //other code
    }

    //etc...
}

这是一种可行的方法吗?
如果代表章节没有按原样呈现,我就不会想到使用这样的“事件”。那么在不使用回调的情况下实现这一点的好方法是什么?

我有发布非常详细的帖子的习惯……抱歉 v__v

4

5 回答 5

5

一个“游戏”类会保存大部分信息,比如玩家和当前房间。对于移动玩家等操作,Game 类可以根据房间的关卡地图将玩家移动到不同的房间。

游戏类将管理游戏各个组件之间的所有交互。

将事件用于这样的事情会带来您的事件会被纠缠的危险。如果您不小心,最终会导致事件相互触发并溢出堆栈,这将导致标志在特殊情况下关闭事件,并且程序难以理解。

UDPATE:

为了使代码更易于管理,您可以将主要类之间的一些交互建模为类本身,例如 Fight 类。使用接口使您的主类能够执行某些交互。(请注意,我冒昧地发明了一些您​​在游戏中可能不想要的东西)。

例如:

// Supports existance in a room.
interface IExistInRoom { Room GetCurrentRoom(); }

// Supports moving from one room to another.
interface IMoveable : IExistInRoom { void SetCurrentRoom(Room room); }

// Supports being involved in a fight.
interface IFightable
{
  Int32 HitPoints { get; set; }
  Int32 Skill { get; }
  Int32 Luck { get; }
}

// Example class declarations.
class RoomFeature : IExistInRoom
class Player : IMoveable, IFightable
class Monster : IMoveable, IFightable

// I'd proably choose to have this method in Game, as it alters the
// games state over one turn only.
void Move(IMoveable m, Direction d)
{
  // TODO: Check whether move is valid, if so perform move by
  // setting the player's location.
}

// I'd choose to put a fight in its own class because it might
// last more than one turn, and may contain some complex logic
// and involve player input.
class Fight
{
  public Fight(IFightable[] participants)

  public void Fight()
  {
    // TODO: Logic to perform the fight between the participants.
  }
}

在您的问题中,您确定了这样一个事实,即如果您在 Player 类上粘贴诸如 Move 方法之类的东西,那么您将有许多类必须相互了解。这是因为像移动这样的东西既不属于玩家也不属于房间——移动会相互影响两个对象。通过对主要对象之间的“交互”进行建模,您可以避免许多这些依赖关系。

于 2010-07-16T18:10:05.787 回答
2

听起来像是我经常使用 Command 类或 Service 类的场景。例如,我可能会创建一个 MoveCommand 类来执行关卡和人员之间的操作和协调。

这种模式具有进一步执行单一责任主体 (SRP) 的优势。SRP 说一个类应该只有一个改变的理由。如果 Person 类负责移动,它无疑有不止一个改变的理由。通过将 Move off 的逻辑分解到它自己的类中,可以更好地封装它。

有几种方法可以实现 Command 类,每种方法都更适合不同的场景。命令类可以有一个接受所有必要参数的 Execute 方法:

 public class MoveCommand {
    public void Execute(Player currentPlayer, Level currentLevel) { ... }
 }

 public static void Main() {
     var cmd = new MoveCommand();
     cmd.Execute(player, currentLevel);
}

或者,有时我发现在命令对象上使用属性更直接、更灵活,但它使客户端代码更容易通过忘记设置属性来滥用类 - 但优点是您具有相同的函数签名在所有命令类上执行,因此您可以为该方法创建一个接口并使用抽象命令:

 public class MoveCommand {
    public Player CurrentPlayer { get; set; } 
    public Level CurrentLevel { get; set; }
    public void Execute() { ... }
 }

 public static void Main() {
     var cmd = new MoveCommand();
     cmd.CurrentPlayer = currentPlayer;
     cmd.CurrentLevel = currentLevel;
     cmd.Execute();
}

最后,您可以将参数作为构造函数参数提供给 Command 类,但我将放弃该代码。

无论如何,我发现使用命令或服务是处理操作的一种非常强大的方式,例如移动。

于 2010-07-16T18:22:56.067 回答
1

对于基于文本的游戏,您几乎肯定会有一个 CommandInterpretor(或类似的)对象,它评估用户输入的命令。使用这种抽象级别,您不必在 Player 对象上实现所有可能的操作。您的解释器可能会将一些键入的命令推送到您的 Player 对象(“显示库存”),一些命令推送到当前占用的 Sector 对象(“列表退出”),一些命令推送到 Level 对象(“向北移动玩家”),还有一些对特殊对象的命令(“攻击”可能被推送到 CombatManager 对象)。

这样一来,Player 对象就变得更像 Character,而 CommandInterpretor 更能代表坐在键盘上的实际人类玩家。

于 2010-07-16T18:24:25.237 回答
1

避免在情感或智力上陷入“正确”的做事方式。而是专注于。不要对您已经编写的代码过于重视,因为可能需要更改其中的任何或全部以支持您想做的事情。

IMO 有太多的精力花在模式和酷技术以及所有这些爵士乐上。只需编写简单的代码即可完成您想做的事情。

该级别“包含”其中的所有内容。你可以从那里开始。关卡不一定驱动一切,但一切都在关卡中。

玩家可以移动,但只能在关卡范围内移动。因此,玩家需要查询关卡以查看移动方向是否有效。

关卡不会从玩家那里拿走物品,也不会造成伤害。关卡中的其他对象正在做这些事情。那些其他对象应该正在搜索玩家,或者可能被告知玩家的接近度,然后它们可以直接对玩家做他们想做的事情。

关卡可以“拥有”玩家,玩家可以参考其关卡。从面向对象的角度来看,这“有道理”;你站在地球上,可以影响它,但当你挖洞的时候,它会拖着你绕着宇宙走。

做简单的事情。任何时候事情变得复杂,想办法让它变得简单。简单的代码更容易使用并且更能抵抗错误。

于 2010-07-16T18:57:19.623 回答
0

那么首先,这是一种可行的方法吗?

绝对地!

其次,如果代表章节没有按原样呈现,我不会想到使用这样的“事件”。那么在不使用回调的情况下实现这一点的好方法是什么?

我知道很多其他方法来实现这一点,但没有任何其他好的方法没有某种回调机制。恕我直言,这是创建解耦实现的最自然方式。

于 2010-07-16T18:21:57.630 回答