This article will focus on porting the
Forest Tower Defense game to your Nokia N9 phone.

I am well acquainted with Qt, but poorly with QML, fortunately I completely avoided using it in the application (yes, it is possible!). All code in this article will be in C ++, oldfags approve.
Window
The main and only window of the game is
QGLWidgetclass Widget : public QGLWidget, public Platform { Q_OBJECT
What is Platform, and how to design cross-platform games, you can read
here .
')
Graphics
All painting takes place in
QWidget :: paintEvent void Widget::paintEvent(QPaintEvent *) { QPainter painter(this); m_painter = &painter; Application::instance().render(); flushFragments(); }
I want to share an interesting find - the
QPainter :: drawPixmapFragments method (appeared in Qt 4.7). If all images are stored in one
QPixmap , it is enough to create an array of
QPainter :: PixmapFragment objects , each element of which will contain coordinates, dimensions, angle, scale, transparency and position in the original QPixmap. After this, we make one call to QPainter :: drawPixmapFragments - and all fragments will be drawn:
void Widget::flushFragments() { m_painter->drawPixmapFragments(m_fragments.constData(), m_fragments.size(), pixmap); m_fragments.clear(); }
This approach is faster than individual
QPainter :: drawPixmap calls .
Full screen mode
To hide the toolbar and statusbar, simply call the
showFullScreen method
.
int main(int argc, char** argv) { QApplication a(argc, argv); QWidget w; w.showFullscreen(); return a.exec(); }
It would seem obvious, but I found out about it completely by accident - I noticed this method in the documentation and decided to try it. If you search Google for something like “meego harmattan hide toolbar”, you will most likely find how to do this in QML, but not in C ++. Be careful, QWidget also has a
showMaximized method that doesn’t work like showFullScreen.
Sound
QSound on the Nokia N9 does not work, this option disappears.
Phonon works, but is only suitable for playing background music. For short and fast sound effects that can be superimposed on each other, it was necessary to find something else. My choice fell on
QtGameEnabler - a set of classes for developing games with Qt.
I figured it out pretty quickly, here are the basic steps for adding sound effects to the game:
a) copy the qtgameenabler folder to the project root, add a line in the .pro file
include(qtgameenabler/qtgameenableraudio.pri)
b) create objects
AudioMixer ,
AudioOut GE::AudioMixer audioMixer; GE::AudioOut audioOut(&audioMixer);
c) create an
AudioBuffer for each sound
GE::AudioBuffer* appleHit = GE::AudioBuffer::loadOgg("/opt/foresttd-n9/assets/audio/appleHit.ogg"); GE::AudioBuffer* iceCrash = GE::AudioBuffer::loadOgg("/opt/foresttd-n9/assets/audio/iceCrash.ogg"); GE::AudioBuffer* arrowFly = GE::AudioBuffer::loadOgg("/opt/foresttd-n9/assets/audio/arrowFly.ogg");
d) every time we want to play a sound, create an
AudioBufferPlayInstance object and add it to AudioMixer
GE::AudioBufferPlayInstance* playOneTime = new GE::AudioBufferPlayInstance(appleHit); playOneTime->setDestroyWhenFinished(true); audioMixer.addAudioSource(playOneTime);
There are cases when more than 10 sounds are played simultaneously, the result is an unpleasant mess. I did not find a simple way to overcome this, but I came up with a rather elegant way out - I inherited from AudioBufferPlayInstance and created a global counter of current sounds:
class SoundInstance : public GE::AudioBufferPlayInstance { public: SoundInstance(GE::AudioBuffer* buffer) : GE::AudioBufferPlayInstance(buffer) { ++ms_count; } virtual ~SoundInstance() { --ms_count; } static int count() { return ms_count; } private: static int ms_count; }; int SoundInstance::ms_count = 0;
Before playing each sound counter increases, after - decreases. Thus, I can control the number of simultaneously played sounds:
if (SoundInstance::count() < MAX_SOUNDS) { GE::AudioBufferPlayInstance* playOneTime = new SoundInstance(sound); playOneTime->setDestroyWhenFinished(true); audioMixer.addAudioSource(playOneTime); }
Thanks to the QtGameEnabler developers for making AudioBufferPlayInstance destructor virtual, and also for the setDestroyWhenFinished method! And also for the fact that the latest version of the trunk supports the ogg format!
Another very important point: in order for the game to be able to change the volume using the hardware keys, you need to:
a) add to the .pro file
# Classify the application as a game to support volume keys on Harmattan gameclassify.files += qtc_packaging/debian_harmattan/$${TARGET}.conf gameclassify.path = /usr/share/policy/etc/syspart.conf.d INSTALLS += gameclassify
b) The file qtc_packaging / debian_harmattan / your_target_name.conf should be
[classify gaming] /opt/usr/bin/your_target_name
Input processing
Since I did not use multi-touch, it was enough for me to override
QWidget :: mousePressEvent ,
QWidget :: mouseMoveEvent and
QWidget :: mouseReleaseEvent void Widget::mousePressEvent(QMouseEvent *e) { QGLWidget::mousePressEvent(e); float y = height() - e->y(); m_mouseDown = true; Application::instance().touch(e->x(), y); } void Widget::mouseReleaseEvent(QMouseEvent *e) { QGLWidget::mouseReleaseEvent(e); float y = height() - e->y(); m_mouseDown = false; Application::instance().release(e->x(), y); } void Widget::mouseMoveEvent(QMouseEvent *e) { QGLWidget::mouseMoveEvent(e); if (m_mouseDown) { float y = height() - e->y(); Application::instance().drag(e->x(), y); } }
Notice the drain "float y = height () - e-> y ()". This is to translate the “y” values ​​from the QWidget coordinate system to Cartesian.
Game cycle
To implement the simplest game loop, I did the following:
a) redefined
QObject :: timerEvent void Widget::timerEvent(QTimerEvent *) { Application::instance().simulate();
b) at the start of the game
m_timerId = startTimer(0);
c) when the game goes into pause state
killTimer(m_timerId);
Pause
When the user turns off the game, you must switch to the "pause" mode:
a) override
QObject :: eventFilter bool Widget::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { resume();
b) set an event filter
int main(int argc, char** argv) { QApplication a(argc, argv); Widget w; w.show(); a.installEventFilter(&w); return a.exec(); }
When I first sent the game to moderation in the Ovi Store, QA found a bug - the application did not unfold, if you minimize it and try to start it again by clicking on the .desktop icon:
Application icon after suspending the application in background.I was very surprised by this behavior, the game was normally deployed from the screen of running applications (Open Apps Screen), but when I clicked on the icon, nothing happened. The search for a solution to this problem was unsuccessful, all that was found was the
MeeGo Harmattan application lifecycle , but alas, QML again. Stackoverflow was silent, developer.support@nokia.com, too, had to look for a way out. I decided to check if any events come to QWidget :: eventFilter when I click on the icon:
bool Widget::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { resume(); } else { pause(); } } qDebug() << event->type(); return QWidget::eventFilter(object, event); }
It turned out that an event with code 50 arrives. I looked at this event:
QEvent :: SockAct - Socket activated, used to implement QSocketNotifier.WTF? What is a socket ?! After all, my game does not go online. Know, tell me, please. The funniest thing is the only event for which I could catch, which I did:
bool Widget::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { resume(); } else { pause(); } } else if (event->type() == QEvent::SockAct)
Vibro
To add vibro effects you need:
a) add the following lines to the .pro file
CONFIG += mobility MOBILITY += feedback
b) connect the necessary header file
#include <QFeedbackHapticsEffect>
c) create an effect object
QFeedbackHapticsEffect vibro; vibro.setAttackIntensity(0.0); vibro.setAttackTime(40); vibro.setIntensity(0.5); vibro.setDuration(80); vibro.setFadeTime(40); vibro.setFadeIntensity(0.0);
d) at the right time call
vibro.start();
Everything seems to be simple here, but initially I wanted to achieve such a vibro effect as when pressing the buttons of the virtual keyboard in the Nokia N9. If someone knows how to do this - please share.
Resources
I did not want to pack resources into a .rc file, because I have a shared folder for Android / Desktop / MeeGo and it is two levels higher, while QtCreator requires that all resources be inside the project folder (a bit strange requirement, do not find?)
So I went a different way and added a rule to copy resources into the .pro file:
assetsDir.source = ../../assets DEPLOYMENTFOLDERS = assetsDir installPrefix = /opt/$${TARGET} for(deploymentfolder, DEPLOYMENTFOLDERS) { item = item$${deploymentfolder} itemfiles = $${item}.files $$itemfiles = $$eval($${deploymentfolder}.source) itempath = $${item}.path $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target) export($$itemfiles) export($$itempath) INSTALLS += $$item }
Now all files and folders that were in ../../assets will be copied to N9 during installation and will be available at / opt / foresttd-n9 / assets
Icon

Making an icon in the style of MeeGo Harmattan is easy if you use the blank file
from here (Icon Templates)
Result
The application has already appeared in the
Ovi Store , its cost is 2EUR. It should be noted that this is the only tower defense game for the Nokia N9 in the Ovi Store. The QA process was pretty fast - the game was pending for 2 days and another 1 day after I fixed a bug. Free testing of your application by Nokia engineers is a positive thing.
I want to express gratitude to Nokia for the N9, the phone is very cool.
Also, I thank all the habrawans for numerous bug reports, reviews and ideas for improving the free version of the game for Android, on the market you can find an
update with new levels .