1

我正在连接到图形 LCD 的 Arduino 上编写游戏(太空入侵者),并且我有一个精灵类。此类具有诸如 Player/Alien、位图对象、位置 (x,y) 和如何移动函数等属性。

我希望每个实例都有一个导弹,我认为这可以通过继承和多态来完成,尽管我不确定如何——我的流线型代码如下,并且为了更好地了解形状,我包含了一个字形图像。我希望 Missile 从 sprite 类派生 location(x,y) ,但它会有自己的位图和移动方法,比如(?)

Class Missile: public Sprite{
  Missile();   // create shape here
  void Move(); // has its own method of moving, but starts from Sprite(x,y)  
};  

[不管怎么做,我想在我的 C++ 练习中使用继承和多态]

Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);
unsigned char spaceShip[5]    PROGMEM = {0x3c, 0x1e, 0x1f, 0x1e, 0x3c};
unsigned char spaceAlien[5]   PROGMEM = {0x1e, 0x0f, 0x1f, 0x0f, 0x1e};
unsigned char spaceMissile[5] PROGMEM = {0x00, 0x00, 0x1f, 0x00, 0x00}; 

enum TYPES {ALIEN = 0, PLAYER = 1 };
class Sprite
{
  public:
    Sprite(TYPES Type);
    void Move();
    void Render()      { display.drawBitmap(x,y, spacePtr, 5, 6, BLACK); }
  private:
    unsigned char *spacePtr;
    unsigned int x, y;
    TYPES Type;
};

Sprite::Sprite(TYPES theType)
{
  Type   = theType;
  switch( Type )
  {
      case( PLAYER ):
        spacePtr = &spaceShip[0];
        x = xPlayer(); // get x from xfunction
        y = yPlayer(); // get y from yfunction
        break;      
      case( ALIEN ):
        spacePtr = &spaceAlien[0];
        x = random(0, 82);
        y = random(10, 20);
        break;
      default: 
        break;
   }
}

位图在这里

4

1 回答 1

2

在涉及导弹之前,您应该意识到您当前的 sprite 实现实际上可以分为(至少)三个类:玩家、外星人和前两者派生的 Sprite。

继承的意义在于它代表了一种“是一种”的关系。即:玩家是精灵,外星人是精灵。在这种情况下,精灵是一个可以移动、渲染、具有位置和位图的类。正如您在此处展示的那样,将 Alien 与 Player 区分开来的是它的初始位置、位图数据的位置以及大概的移动方式。

类精灵
{
  上市:
    虚拟~Sprite();
    Sprite(无符号字符 * 常量空间指针,无符号整数 xInit,无符号整数 yInit)
        : 空间点(空间点)
        , x(xInit)
        , y(yInit)
    {}
    虚拟无效移动()= 0;
    void Render(Display &display) const { display.drawBitmap(x,y, spacePtr, 5, 6, BLACK); }
    unsigned int X() const {return x;}
    unsigned int Y() const {return y;}
  受保护:
    无效 X(int newX) { x = newX; }
    无效 Y(int newY) { y = newY; }
  私人的:
    无符号字符 * 常量空间指针;
    无符号整数 x, y;
};

类外星人:公共精灵
{
上市:
   外星人()
     : Sprite(spaceAlien, random(0, 82), random(10, 20))
   {}
   虚拟无效移动();
};

类播放器:公共精灵
{
上市:
   播放器()
     :精灵(太空船,xPlayer(),yPlayer())
   {}
   虚拟无效移动();
};

一旦我们将玩家和外星人的特殊属性与 Sprite 的一般属性区分开来,它应该更清楚导弹与 sprite 的关系:它是一个。

导弹类:公共雪碧
{
上市:
   导弹(Sprite const &launchPoint)
     :精灵(太空导弹,launchPoint.X(),launchPoint.Y())
   {}
   虚拟无效移动();
};

假设是,一旦导弹来自指定的 Sprite,它就与它没有任何关系。请注意,导弹和外星人/玩家之间没有依赖关系。

其他需要注意的是,Sprite 的 x 和 y 属性只能由子类通过受保护的 setter 函数修改,让 Sprite 类进行范围检查和/或根据需要以不同的格式存储值 - 这是工作中的封装原理。此外,我已经删除了对可能是全局显示对象的引用——而是在需要时将它传递给 now const Render 函数。这有很多原因,尤其是全局变量的普遍邪恶和它们引入的隐藏依赖项。通过将函数设为 const,调用者可以更容易地假设一旦函数返回,该对象将不会在下一次调用之前触摸显示器。这样做的开销可能非常低,因此您在 Arduino 上执行此操作的事实不应该阻止您,

之所以出现多态性,是因为调用 Render 和 Move 方法的代码不需要知道它正在处理的对象的实际类型是什么——它只需要知道精灵。

void MoveAndRender(Sprite **begin, Sprite **end, Display &display)
{
   for(; 开始 != 结束; ++开始)
   {
      (*开始)->移动();
      (*开始)->渲染(显示);
   }
}

当然,有无数种方法可以解决这个问题,最难的一点是首先定义问题。这只是演示继承如何适合您的场景。

继承只是许多 OO 关系中的一种,并且经常被过度使用或误用,从而导致一些真正可怕的代码。熟悉组合(“has a”)、聚合(“shares a”)和关联(“knows a”?)。通常,继承和组合的混合比单独的继承更有效(参见桥模式、代理模式和朋友。)

编辑删除了 Type 属性,因为实际的对象类型应该是表征其行为所需的全部 - 这真的很重要。添加了 MoveAndRender 示例。

于 2013-10-04T23:53:15.990 回答