使用 Qt 开发嵌入式 Linux

作者:Natalie Watson

2001 年 1 月,我开始在 Trolltech 位于澳大利亚布里斯班的嵌入式开发部门实习。我的目标是双重的:学习 C++ 和学习如何开发嵌入式应用程序。我在 Trolltech 工作了五个星期,这段时间足够我实现这两个目标(当然,真正掌握 C++ 需要一生)。应该注意的是,在加入 Trolltech 之前,我的编程经验仅限于大学一年的 Java 学习,而且我从未使用过 C++ 或 Qt。在五个星期结束时,我已经开发了两个完整的 2D 游戏,这两个游戏现在都包含在 Qt Palmtop Environment (QPE) 中。

对于那些不了解的人来说,Qt/Embedded 是 Trolltech 的 Qt 的嵌入式版本,Qt 是一个跨平台的 C++ GUI 应用程序框架,支持 Windows、UNIX、Mac 和嵌入式 Linux。Qt 和 Qt/Embedded 通过优雅的双重许可程序提供给开发人员。对于开发免费软件的程序员,Qt 是免费提供的(在 GPL 和 QPL 下;Qt/Embedded 仅在 GPL 下提供)。对于开发商业应用程序的程序员,该框架在商业许可下提供。商业和开源版本均可直接从 Trolltech 获取,网址为 http://www.trolltech.com/

入门指南

我相信任何具备基本编程技能的人都可以学习使用 Qt,并快速开发出实用、外观专业的应用程序。我发现 API 非常直观,这大大缩短了学习时间。虽然 C++ 在语言特性方面可能令人眼花缭乱,但 Qt 使用的是一个适度的子集,因此您不必成为 C++ 专家也能使用它。

本文解释了用于开发一个简单游戏的技术。但在尝试这样做之前,您应该熟悉以下四个领域:Qt 教程、基本编程、面向对象编程和 Qt 参考文档。

Qt 教程的前几课将引导您完成开发一个简单的应用程序,例如“hello world”程序。它们涵盖了几个具体的技能,包括创建源文件和头文件,以及如何编译和运行 Qt 程序。

Qt 是一个用于 C++ 的应用程序框架,但我同时学习了框架和基本编程。如果您不懂 C++,我建议您使用一本好的教科书作为参考,特别是学习指针。

我在大学学习过 Java,发现面向对象编程技术是学习 Qt 的极佳基础。Qt 的设计目的是使面向对象编程更容易,因此熟悉多个类、对象和其他组件编程技术会有所帮助。

要使用 Qt,您需要确保已安装必要的软件并熟悉 Qt 参考文档。C++ 编译器是必要的,调试器也非常有用,这两者都包含在所有 Linux 发行版中。文本编辑器,例如 viEmacs,也是创建源文件所必需的。最重要的是,您需要安装用于 X11 和嵌入式 Linux 的 Qt。

我用来创建 Qt/Embedded 应用程序的工具随大多数 Linux 发行版一起提供,包括 GCC 和 GDB。我还使用了 TMake,这是 Trolltech 提供的一个简单工具,用于创建和维护 makefile;Qt 文档(可在 http://doc.trolltech.com/ 获取);以及 Qt 教科书,Kalle Dalheimer 的 Programming with Qt。您应该查看 Qt 文档中的功能,包括关于 Qt 如何使创建简单应用程序变得非常容易的解释。它的 API 包含几个旨在使常见任务更容易和更快的类。

特别是,对于创建 2D 游戏,您应该查看三个主要功能以入门:QMainWindow,它提供了一个典型的应用程序窗口,带有工具栏或菜单栏和状态栏;QCanvas,一个 2D 图形区域,可以在其上放置 QCanvasItems(图形对象);以及 QCanvasView,它提供了画布的视图。查看 Qt 文档是一个很好的起点,因为它会让您了解 Qt 提供的丰富功能。此外,许多您想做的事情都已经完成。

本文的下一部分重点介绍了 Qt 的功能,这些功能使创建简单游戏变得容易,使用了我编写的 Snake 游戏中的示例代码(见图 1)。这里解释的原理可以应用于创建许多其他 2D 游戏。

Using Qt to Develop for Embedded Linux

MainWindow

您首先需要的是一个主类。这应该位于名为 main.cpp 的文件中。它将仅包含一个 main 方法,用于创建程序的实例。Snake 中的 main 方法如下所示。

int main(int argc, char **argv)
{
   QPEApplication app(argc,argv);
   SnakeGame* m = new SnakeGame;
      // creates an instance of Snake
   m->resize(m->sizeHint());
   qApp->setMainWidget(m);
   m->show();
   return app.exec();
}

如果您已经编写过 Qt 程序,您会发现其中一些代码很熟悉。这是启动程序的标准方法。

Canvas 和 CanvasView

现在您需要一个类来设置程序。它的构造函数将创建一个程序的实例,如上面出现的主方法中所用。它将继承自 QMainWindow,并将创建菜单或工具栏,并可能创建一个状态栏。它还需要创建一个 QCanvas 和一个 QCanvasView。所有这些都显示在列表 1 中。

列表 1. 创建 QCanvas 和 QCanvasView

此类还将包含程序中要使用的一些其他方法。在 Snake 中,此类包含显示标题屏幕、开始新游戏、更新分数、更改级别、清除屏幕、结束游戏和处理键盘交互(按键事件)的方法。这些方法将特定于您的程序,因此我没有在此处包含代码。

精灵

现在您有了一个 QCanvas,您可以开始在其上放置 QCanvasItems。QCanvasItems 有许多类型,要创建一个图形项目,我们将使用 QCanvasSprite。有关其他类型的 QCanvasItems 的信息,请参阅 Qt 文档。在 Snake 游戏中,目标鼠标是一个从图像创建的 QCanvasSprite。鼠标在其自己的类中定义,并继承自 QCanvasSprite。以下代码显示了名为 Target 的类中鼠标的构造函数

Target::Target(QCanvas* canvas)
   : QCanvasSprite(0, canvas)
       //inherits from QCanvasSprite
{
  mouse = new
QCanvasPixmapArray
  setSequence(mouse);
  newTarget();
}

此代码首先将图像加载到名为“mouse”的像素图数组中。如果您有多个图像并希望为精灵制作动画,则可以使用 readPixmaps() 函数将所有图像加载到数组中。然后,您可以使用 QCanvasSprite 中的 setSequence() 函数告诉您的精灵使用刚刚加载到数组中的图像。此构造函数还调用一个函数 newTarget()。这只是生成可以放置新目标的随机整数 x 和 y 值。然后,它调用 QCanvasSprite 中的 move (int x, int y) 函数来设置其位置,并调用 show() 使其可见。墙壁也以相同的方式创建。

键盘事件

现在您可以使您的程序具有交互性。Qt 类 keyEvent 收集有关按键事件的信息。事件处理程序 keyPressEventkeyReleaseEvent 接收此信息。要使用这些,您需要在程序中将它们实现为函数。keyPressEvent 的典型实现是使用 switch 语句在用户按下某些键时调用函数。

在游戏中,当按下箭头键之一时,蛇会改变方向。这是通过一个 switch 语句实现的,该语句调用一个 move() 函数,并将按下的方向作为其参数。以下是 keyPressEvent() 函数的实现。它非常简单,因为它调用了 Snake 类中的一个函数,该函数记住最后按下的键并调用另一个函数 moveSnake() 来确定蛇应该朝哪个方向移动。要实现这一点,请将以下行添加到 interface.cpp 文件中

void SnakeGame::keyPressEvent(QKeyEvent* event)
{
       int newkey = event->key();
       snake->go(newkey)
}

添加到 snake.cpp 文件中

void Snake::moveSnake()
{
   switch (last)
   {
      case Key_Up: move(up); break;
      case Key_Left: move(left);  break;
      case Key_Right: move(right); break;
      case Key_Down: move(down);  break;
    }
    detectCrash();
}
如果用户按下向上键,程序将调用 move(up)。move 函数执行,蛇朝按下的键的方向移动:在本例中为向上。move() 函数中的代码将特定于创建的特定游戏。

蛇的实际移动相当复杂,因为蛇的身体是由小图像集合组成的。要移动,将末端图像放在前面,然后移动头部和尾部。在其他情况下,例如按下键发射子弹,可以使用 Qt 中的 setVelocity() 函数设置子弹将以恒定速度移动。

碰撞

在这个阶段,您有一个 QMainWindow,其中包含一个 QCanvasView,该视图具有一个画布,其中包含项目,其中一些项目可能会移动。下一步是通过检测项目之间的碰撞来使您的程序更有趣。您可以使用 collidesWith()collisions() 获取有关 QCanvasItems 之间碰撞的信息。collidesWith() 用于测试一个项目是否会与另一个特定项目发生碰撞,而 collisions() 返回与特定项目发生碰撞的所有项目的列表。在 Snake 游戏中,使用了 collisions(),因为蛇可能会与自身、墙壁或目标发生碰撞。列表 2 显示了游戏中如何处理碰撞。

列表 2. 处理碰撞

蛇头碰撞的 QCanvasItems 列表被分配给一个名为“l”的 QCanvasItemList。collisions() 的 false 参数指定以非精确模式测试碰撞。这将返回靠近头部的项目,但它的工作速度比精确模式快得多。如果该项目是一个有用的候选项,则使用 collidesWith() 对其进行测试。

“for”循环用于迭代列表 l,将每个项目分配给一个名为“item”的变量。画布上的每个 QCanvasItem 都可以有一个指定的 rtti() 值,这是一个整数,允许您识别项目。目标的 rtti() 值为 1,500,此信息用于 if 语句中以测试列表中的项目是否为目标。如果是目标,则函数返回;否则,它继续执行下一个 if 语句以测试项目是否为墙壁。请注意,此代码段仅用于说明目的。在实际的 Snake 游戏中,for 循环中还有其他 if 语句来处理其他情况。“do something”被替换为代码。

信号和槽

开发任何程序时,另一个重要的考虑因素是设计。Qt 通过其信号和槽机制为组件编程(也称为面向对象编程)提供帮助。这是一个很棒的功能,可以节省大量时间和精力,因为它允许程序中不同对象之间轻松通信。它允许一个对象发出 signal(),激活另一个对象中的 slot()。槽可以是普通的成员函数,参数也可以传递给它们。

我发现这个功能在开发 Snake 时特别有用。信号和槽的使用示例发生在游戏结束时。当蛇撞到墙壁时,蛇对象发出 dead() 信号。然后,蛇的身体或屏幕边缘连接到界面类中的 gameOver() 槽,以结束游戏并开始新游戏。此代码在 snake.cpp 中

//check if snake hit itself
for (uint i = 3; i < snakelist.count(); i++) {
  if (head->collidesWith(snakelist.at(i)) ) {
      emit dead();      // signal to end game
      autoMoveTimer->stop();
      return;
  }
}

文件 interface.cpp 中 newGame() 函数中的代码为

// connect the signal to the slot
connect(snake, SIGNAL(dead()), this, SLOT(gameOver()) );
结论

如果应用程序框架的测试标准是快速构建专业、强大且无错误代码的能力,那么 Qt/Embedded 完全合格。作为一名编程经验相当有限的实习生,我能够创建一个 2D 电脑游戏,其质量足以包含在 Qt Palmtop Environment 的应用程序套件中。我只用了不到五个星期的实际开发时间就完成了。Qt/Embedded 的标语是“Small Enough for the Job”。我认为这非常准确。它功能强大,具有丰富的功能集,使编程变得非常容易,并且它占用的主内存和闪存空间惊人地小。如果我职业生涯中遇到的其他工具能有它一半好,我就会成为一个快乐的开发人员。

Using Qt to Develop for Embedded Linux

Natalie Watson (nwatson04@optusnet.com.au) 目前是格里菲斯大学的学生,她正在攻读信息技术学士学位。她的编程经验包括 Java 和 C++。

加载 Disqus 评论