设计游戏对象

时间:2008-10-07 19:57:55

标签: c# oop xna

我最近开始使用Microsoft XNA和C#开发一款适合自己娱乐的小游戏。我的问题是关于设计游戏对象和继承游戏对象的对象。我将把游戏对象定义为可以在屏幕上呈现的东西。所以为此,我决定创建一个基类,其中所有其他需要渲染的对象都将继承,称为GameObject。下面的代码是我制作的课程:

class GameObject
{
    private Model model = null;
    private float scale = 1f;
    private Vector3 position = Vector3.Zero;
    private Vector3 rotation = Vector3.Zero;
    private Vector3 velocity = Vector3.Zero;
    private bool alive = false;
    protected ContentManager content;

    #region Constructors
    public GameObject(ContentManager content, string modelResource)
    {
        this.content = content;
        model = content.Load<Model>(modelResource);
    }
    public GameObject(ContentManager content, string modelResource, bool alive)
        : this(content, modelResource)
    {
        this.alive = alive;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale)
        : this(content, modelResource, alive)
    {
        this.scale = scale;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position)
        : this(content, modelResource, alive, scale)
    {
        this.position = position;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation)
        : this(content, modelResource, alive, scale, position)
    {
        this.rotation = rotation;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity)
        : this(content, modelResource, alive, scale, position, rotation)
    {
        this.velocity = velocity;
    }
    #endregion
}

我遗漏了额外的方法,例如旋转,移动和绘制对象。现在如果我想创建另一个对象,比如船,我会创建一个Ship类,它将继承GameObject。示例代码如下:

class Ship : GameObject
{
    private int num_missiles = 20; // the number of missiles this ship can have alive at any given time
    private Missile[] missiles;
    private float max_missile_distance = 3000f; // max distance a missile can be from the ship before it dies

    #region Constructors
    public Ship(ContentManager content, string modelResource)
        : base(content, modelResource)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource , bool alive)
        : base(content, modelResource, alive)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale)
        : base(content, modelResource, alive, scale)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position)
        : base(content, modelResource, alive, scale, position)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation)
        : base(content, modelResource, alive, scale, position, rotation)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity)
        : base(content, modelResource, alive, scale, position, rotation, velocity)
    {
        InitShip();
    }
    #endregion
}

同样,我省略了任何额外的特定于船只的方法,例如发射导弹。你认为这种设计是好还是应该以某种方式改进或完全改变?看起来子类的构造函数很乱,但也许这是唯一的方法。我从来没有做过这样的事情,我想知道我是否偏离轨道。


感谢大家留下了答案。他们都非常有帮助。似乎普遍认为改变它以使用MVC模式是最好的。我将进一步研究如何做到这一点。我也将删除大多数构造函数,并且只有一个构造函数,因为modelResource之后的所有参数都不是创建对象所必需的,以后可以通过方法调用来更改它们。

11 个答案:

答案 0 :(得分:7)

就个人而言,我发现你有一点点输出的构造函数,但这是你的选择,没有任何根本错误:)

关于从共同基础派生游戏对象的一般策略,这是一种非常常见的做事方式。标准,均匀。我倾向于开始使用更类似于MVC的东西,使用非常轻量级的“模型”游戏对象,只包含数据。

我在XNA中看到的其他常见方法包括/您可能需要考虑的事项:

  • 实现IRenderable接口,而不是在基类或派生类中执行任何呈现代码。例如,您可能想要从未渲染的游戏对象 - 航点或某些东西。
  • 您可以将您的基类抽象化,您不太可能想要实例化GameObject。

再次,小点。你的设计很好。

答案 1 :(得分:5)

构造函数的数量不仅是一个潜在的问题(特别是必须为每个派生类重新定义它们) - 但构造函数的参数数量可能会成为一个问题。即使您将可选参数设置为可空,也很难在以后读取和维护代码。

如果我写:

new Ship(content, "resource", true, null, null, null, null);

第二个NULL做什么?

如果参数列表超出四个或五个参数,它会使代码更具可读性(但更详细)以保存参数:

GameObjectParams params(content, "resource");
params.IsAlive = true;
new Ship(params);

有很多方法可以做到。

答案 2 :(得分:5)

由于您在询问与设计相关的问题,我会尝试给出一些提示,而不是您发布的大量代码,因为其他人已经开始这样做了。

游戏非常适合Model-View-Controller pattern。你明确地谈论了显示部分,但想一想:你在那里有矢量等,这通常是你建模的一部分。把游戏世界和那里的物体想象成模型。他们有一个位置,他们可能有速度和方向。这是MVC的模型部分。

我注意到你想让船开火。例如,您可能希望机械师在控制器类中进行发射,这需要键盘输入。此控制器类将向模型部分发送消息。一个声音很容易想到你可能想要的是在模型上有一些fireAction()方法。在您的基础模型上,这可能是一个可覆盖(虚拟,抽象)的方法。在船上它可能会给游戏世界添加一个“子弹”。

看到我如何写这么多关于你的模型的行为,这里有另一件事帮助了我:使用strategy pattern来使类的行为可以交换。如果您认为AI并希望行为在将来改变某些行为,那么策略模式也会很棒。你现在可能不知道,但是在一个月内你可能会想要弹道导弹作为默认导弹,如果你使用了策略模板,你可以在以后轻松改变它。

所以最终你可能想要真正做出类似于显示类的东西。它会有像你这样命名的原语:rotate,translate等。并且您希望从中派生以添加更多功能或更改功能。 但是考虑一下,一旦你拥有的不仅仅是一艘船,一种特殊的船,另一种特殊的船,你会遇到派生地狱,你会复制很多代码即可。再次,使用策略模式。并记得保持简单。 Displayble类需要具备什么?

  • 意味着知道它相对于屏幕的位置。它可以,因为它的in-world对象的模型和类似于gameworld的模型!
  • 必须知道要为其模型及其尺寸显示的位图。
  • 必须知道是否有任何东西阻止它绘制,如果绘图引擎没有处理它(即另一个遮挡它的对象)。

答案 3 :(得分:4)

这种设计不会分离对象的UI部分和对象的行为部分,即“模型”和“视图”。这不一定是件坏事,但如果你继续这个设计,你可能会发现以下重构很难:

  • 通过更改所有艺术资产重新设置游戏皮肤
  • 更改许多不同游戏内对象的行为,例如确定对象是否存活的规则
  • 更改为其他精灵引擎。

但是如果你对这些权衡利弊并且这种设计对你有意义,那么我看不出任何特别错误。

答案 4 :(得分:3)

我对你在矢量中旋转的存储方法很感兴趣。这是如何运作的? 如果你存储X,Y,Z轴的角度(所谓的欧拉角),你应该重新考虑这个想法,如果你想创建一个3D游戏,你将在你第一次渲染邪恶之后不久见到{{3 }}

我个人不喜欢那里的构造函数,因为你可以提供1个构造函数并将所有不需要的params设置为null。通过这种方式,您的界面保持清洁。

答案 5 :(得分:2)

我还强烈建议使用合成而不是继承来研究基于组件的设计。这个想法的要点是根据对象的底层属性对对象进行建模,并允许属性为您完成所有工作。

在您的情况下,GameObject可能具有能够移动,可渲染或具有某种状态的属性。这些单独的函数(或行为)可以建模为对象,并在GameObject中组合以构建您想要的功能。

老实说,对于你正在开展的一个小项目,我会专注于让游戏发挥功能,而不是担心耦合和抽象等细节。

答案 6 :(得分:1)

根据构造函数的调用频率,您可以考虑将所有额外参数设置为可空,并在没有值时将null传递给它们。 Messier创建对象,构造函数更少

答案 7 :(得分:1)

我首先在XNA中创建游戏时走了这条路,但下次我会做更多的模型/视图类型的方法。 Model对象将是您在Update循环中处理的对象,您的Views将在Draw循环中使用。

当我想使用我的精灵对象来处理3D和2D对象时,我没有将模型与视图分离。真的很快就真的很乱。

答案 8 :(得分:1)

这里的人们谈论将模型与视图分离是正确的。要将它放在更多面向游戏开发者的术语中,您应该为GameObject和RenderObject提供单独的类。回想一下你的主要游戏循环:

// main loop
while (true) {
    ProcessInput();  // handle input events
    UpdateGameWorld();  // update game objects
    RenderFrame();  // each render object draws itself
}

您希望更新游戏世界并将当前帧渲染为尽可能独立的过程。

答案 9 :(得分:1)

或者你可以创建一个struct或helper类来保存所有数据,并在struct中包含许多构造函数,这样你只需要在1个对象中有吨构造函数,而不是在每个继承自GameObject的类中

答案 10 :(得分:1)

第一场比赛?开始简单。您的基础GameObject(在您的情况下更适合称为DrawableGameObject [注意XNA中的DrawableGameComponent类])应该只包含属于所有GameObjects的信息。你的活着和速度变量并不真正属于。正如其他人所提到的,你可能不希望混合你的绘图和更新逻辑。

Velocity应该在Mobile类中,它应该处理更新逻辑以更改GameObject的位置。您可能还需要考虑跟踪加速度,并在玩家按下按钮时稳定增加,并在玩家释放按钮后稳步减少(而不是使用恒定加速度)......但这更像是游戏设计决定比班级组织决定。

基于上下文,“活着”的含义可能非常不同。在一个简单的太空射击游戏中,可能会破坏一些不活的东西(可能会被一些空间碎片所取代?)。如果它是重生的球员,那么你将要确保球员与船只分开。船没有重生,船被摧毁,玩家得到一艘新船(将旧船回收而不是删除它并创建一艘新船将使用更少的周期 - 你可以简单地将船改为无人船-land或将其从游戏场景中完全拉出并在重新生成之后将其取回。)

另一件你应该警惕的事情......不要让你的球员在你的垂直轴上直接向上或向下看,否则你会遇到,就像彼得帕克提到的那样,万向锁问题(你不想要的)如果你正在进行游戏开发以获得乐趣,那就太乱了!)更好的是......从2D开始(或者至少将运动限制为仅2维)!当你刚接触游戏开发时,这会容易得多。

然而,我建议在进行任何编程之前遵循XNA Creators Club Getting Started Guides。您还应该考虑寻找其他一些更通用的游戏开发指南,以获得一些替代建议。