关于基于类的文本冒险游戏设计的质疑。

时间:2010-07-16 18:00:12

标签: c# oop delegates adventure

我在夏天一直在学习C#,现在感觉就像我到目前为止做的一个小项目。我决定采用一种基于文本的冒险游戏。

游戏的基本结构将涉及具有多个扇区(或房间)。进入房间后,将输出描述并采取一些行动等等;能够检查,拾取,使用那个房间里的东西;可能是一个战斗系统等等。一个扇区最多可以连接4个其他扇区。

无论如何,在纸上涂写关于如何为此设计代码的想法,我对我的部分代码的结构感到头疼。

我决定使用一个玩家类,以及一个代表关卡/地牢/区域的“关卡”类。这个级别的课程将由许多相互关联的“部门”组成。在任何给定时间,玩家将出现在关卡中的某个特定区域。

所以这就是混乱:

从逻辑上讲,人们会期待像player.Move(Dir d)这样的方法 这种方法应该改变关卡对象中的“当前扇区”字段。这意味着类 Player 需要了解类 Level 。嗯。 并且 Level 可能必须操纵 Player 对象(例如,玩家进入房间,被某些东西伏击,从库存中丢失一些东西。)所以现在等级还需要保持对 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

5 个答案:

答案 0 :(得分:5)

“游戏”课程如何能够容纳大部分信息,如播放器和当前房间。对于诸如移动玩家之类的操作,游戏类可以根据房间的等级地图将玩家移动到不同的房间。

游戏课将管理游戏各个组成部分之间的所有互动。

将事件用于此类事件会带来事件变得混乱的危险。如果你不小心,你最终会发现事件相互关闭并且溢出你的堆栈,这将导致标志在特殊情况下关闭事件,以及一个不太容易理解的程序。

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方法的事情,你会有许多必须互相了解的课程。这是因为移动既不属于玩家也不属于房间 - 移动会相互影响两个对象。通过对主要对象之间的“交互”进行建模,可以避免许多依赖关系。

答案 1 :(得分:2)

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

此模式具有进一步强制执行单一责任委托人(SRP)的优势。 SRP说,一个班级应该有一个改变的理由。如果Person类负责移动它无疑会有不止一个改变的理由。通过将Move的逻辑分解为自己的类,可以更好地封装。

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

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

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

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

 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类,但我将放弃该代码。

无论如何,我发现使用命令或服务是一种非常强大的方法来处理操作,比如Move。

答案 2 :(得分:1)

对于基于文本的游戏,您几乎肯定会有一个CommandInterpretor(或类似)对象,它会评估用户的类型化命令。通过这种抽象级别,您无需在Player对象上实现所有可能的操作。您的解释器可能会将某些类型的命令推送到您的Player对象(“显示库存”),将一些命令推送到当前占用的Sector对象(“list exits”),将某些命令推送到Level对象(“move player North”),以及一些命令到专业对象(“攻击”可能被推送到CombatManager对象。)

通过这种方式,Player对象变得更像Character,而CommandInterpretor更像是坐在键盘上的实际人类玩家。

答案 3 :(得分:1)

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

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

级别“包含”其中的所有内容。你可以从那里开始。水平不一定会推动一切,但一切都在水平。

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

等级不是从玩家那里夺取物品,等级也没有造成伤害。关卡中的其他对象正在做这些事情。那些其他物品应该是搜索玩家,或者可能是玩家的接近度,然后他们可以直接向玩家做他们想做的事。

级别可以“拥有”玩家并且玩家可以参考其级别。从OO的角度来看,这“有意义”;你站在地球上并且可以影响它,但是当你挖洞时它会把你拖到宇宙周围。

做简单的事情。任何时候变得复杂,弄清楚如何使它变得简单。简单的代码更易于使用,并且更能抵御错误。

答案 4 :(得分:0)

  

首先,这是一个好的方法   这样做?

绝对!

  

其次,如果代表章节是   我没有按照它的方式呈现   没有想过用这样的   “事件”。那么这将是一个好方法   不使用就实现这个   回调?

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