In my last post, I discussed how to build the “Kolobok” main
character in our casual game for Windows Phone 7 from the Kolobok.qml file from
the Symbian phone version. I also
discussed the method my team used to animate the fire obstacle by rotating
through a list of texture images at a set frame rate.
In this blog, I will finish up the application by showing
how to map the QML MouseArea inputs for the navigation buttons into the input
handlers for the XNA game object. Next I will discuss how to play the sounds
associated with Kolobok’s movement and collision with obstacles.
Input in the QML version of the application is strictly
event driven. Normally we would place button images on the screen in the game
board object and over a MouseArea to catch a user mouse event using an
“anchors.fill” property. This would work for individual buttons but would not
detect two buttons pressed at the same time. For this we need a MouseArea that
would handle Multi-touch events.
QML does not have a predefined element for multi-touch,
however the C QDeclarativeItem provides multi-touch input events. Therefore
we chose to implement a QDeclarativeItem multi-touch object in C and bind it
to QML as a new element. To simplify the implementation we combined the image
and multi-touch elements into one element called MultiTouchItem that has an
image source property and delivers user events as pressed() and released()
signals. These signals are directly connected to the Kolobok characters key handling
methods.
In Windows Phone 7 we will use the event handling methods
and implement the multi-touch events as synchronous tests in the game object’s
Update method. Multi-touch inputs will be passed onto the Kolobok character
game piece’s KeyPressed and KeyReleased methods.
For each game action button in the QML game, we create a
MultiTouchItem object that overrides the QDeclarativeItem::sceneEvent method
and catches the QEvent::TouchBegin and QEvent::TouchEnd events. The object also catches the Mouse Press and
Mouse Release events in case a non-touch screen input device is used. The
screenEvent method is shown below:
bool MultiTouchItem::sceneEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::TouchBegin:
emit pressed();
break;
case QEvent::TouchUpdate:
break;
case QEvent::TouchEnd:
emit released();
break;
case QEvent::GraphicsSceneMousePress:
emit pressed();
break;
case QEvent::GraphicsSceneMouseRelease:
emit released();
break;
default:
return QDeclarativeItem::sceneEvent(event);
}
return true;
}
The events emit the appropriate released() or pressed()
signals that are communicated to the Kolobok Element .
In the XNA Game object Update() method, we call the
buttonEvent() method that will poll for multi-touch input and then call the
input methods in the Kolobok.cs file. Multi-touch input is collected in a
TouchCollection container object that can be read using the touch panels
GetState() method. The buttonEvent method is shown below:
void buttonEvent()
{
// Windows phone controls
TouchCollection state = TouchPanel.GetState();
foreach(TouchLocation
tl in state)
{
// Handle key pressed
if (tl.State == TouchLocationState.Pressed)
{
if (positonInArea(tl.Position,
closeBtnArea))
this.Exit();
else if
(positonInArea(tl.Position, leftBtnArea))
gameBoard.keyPressed(GameBoard.Key.Left);
else if
(positonInArea(tl.Position, rightBtnArea))
gameBoard.keyPressed(GameBoard.Key.Right);
else if
(positonInArea(tl.Position, upBtnArea))
gameBoard.keyPressed(GameBoard.Key.Up);
}
// Handle key released
else if
(tl.State == TouchLocationState.Released)
{
if (positonInArea(tl.Position, leftBtnArea))
gameBoard.keyReleased(GameBoard.Key.Left);
else if
(positonInArea(tl.Position, rightBtnArea))
gameBoard.keyReleased(GameBoard.Key.Right);
else if
(positonInArea(tl.Position, upBtnArea))
gameBoard.keyReleased(GameBoard.Key.Up);
}
else if
(tl.State == TouchLocationState.Moved)
{
TouchLocation previous;
if (!tl.TryGetPreviousLocation(out previous))
return;
if (positonInArea(previous.Position,
leftBtnArea) &&
!positonInArea(tl.Position, leftBtnArea))
gameBoard.keyReleased(GameBoard.Key.Left);
else if
(positonInArea(previous.Position, rightBtnArea) &&
!positonInArea(tl.Position, rightBtnArea))
gameBoard.keyReleased(GameBoard.Key.Right);
else
if (positonInArea(previous.Position, upBtnArea)
&&
!positonInArea(tl.Position, upBtnArea))
gameBoard.keyReleased(GameBoard.Key.Up);
}
}
}
In both the QML and XNA versions of the game, we pass inputs
from the touch device into the Kolobok object’s input methods, and then call its
doMovement() method to update the Kolobok’s position on the screen and respond
to any collisions with other game pieces.
Once we handle input and process game action, the only thing
left to do is play the game sounds and music for each game action.
In the QML game, we create a SoundPlayer object that
contains QAudioPlayer objects from the multimedia APIs in Qt Mobility. The
SoundPlayer contains audio files that play the various sounds depending on the
type of game action. The two main sounds are “Groan” and “Boing” generated when
the Kolobok experiences health decreasing events or collisions with another
game piece.
Sound player APIs are usually very similar and easy to use.
We create the object, set the format, load the sound file, and when we are
ready, call the play or start method. For QML we create a QAudioPlayer and a
QMediaFormat object. We then set the Format object’s media attributes, bind it
to the Player object, and then call play. The code looks something like this:
QAudioFormat m_format;
m_format.setSampleRate(22050);
m_format.setChannelCount(1);
m_format.setSampleSize(16);
m_format.setCodec("audio/pcm");
m_format.setByteOrder(QAudioFormat::LittleEndian);
m_format.setSampleType(QAudioFormat::SignedInt);
m_boingOutput = new QAudioOutput(m_format,this);
m_boingtFile.setFileName(":/sounds/boink.raw");
if (m_boingOutput)
if (collided) {
if(m_boingPlaying) return;
m_boingOutput->start(&m_boingtFile);
m_boingPlaying = true;
}
For XNA we use a SoundEffect object
to accomplish the same thing. The following code provides support for the Boing
sound above:
SoundEffect boingSound = content.Load<SoundEffect>("sound/boink");
if (boingSound != null)
if (collided) boingSound.Play();
Note that the SoundEffect object
doesn’t require us to set the format of the file. The reader figures this out
from the content .Load method. Once we know how to generate the sounds, we can
trigger them from the collision detection code discussed in the previous blog.
This posting completes my blog series
on reengineering a QML casual game to XNA. Next week I will begin the steps
necessary to port a simple twitter application to Silverlight.
