📜 ⬆️ ⬇️

Porting games to Nokia N9

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 QGLWidget

class 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(); // game logic update(); // repaint } 

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(); // start timers, resume music } else { pause(); // kill timers, pause music } } return QWidget::eventFilter(object, event); } 

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) // pure magic { activateWindow(); resume(); } return QWidget::eventFilter(object, event); } 


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 .

Source: https://habr.com/ru/post/138097/


All Articles