Sub-Attaq▲
The purpose of the game is to destroy all submarines to win the current level. The boat can be controlled using left and right keys. To fire a bomb you can press the up and down keys.
The main() Function▲
int
main(int
argc, char
*
argv[])
{
QApplication app(argc, argv);
Q_INIT_RESOURCE(subattaq);
MainWindow w;
w.show();
return
app.exec();
}
The MainWindow instance is created and shown.
The MainWindow Class▲
QMenu *
file =
menuBar()-&
gt;addMenu(tr("&File"
));
QAction *
newAction =
file-&
gt;addAction(tr("New Game"
));
newAction-&
gt;setShortcut(QKeySequence(Qt::
CTRL |
Qt::
Key_N));
QAction *
quitAction =
file-&
gt;addAction(tr("Quit"
));
quitAction-&
gt;setShortcut(QKeySequence(Qt::
CTRL |
Qt::
Key_Q));
if
(QApplication::
arguments().contains("-fullscreen"
)) {
scene =
new
GraphicsScene(0
, 0
, 750
, 400
, GraphicsScene::
Small, this
);
setWindowState(Qt::
WindowFullScreen);
}
else
{
scene =
new
GraphicsScene(0
, 0
, 880
, 630
, GraphicsScene::
Big, this
);
layout()-&
gt;setSizeConstraint(QLayout::
SetFixedSize);
}
view =
new
QGraphicsView(scene, this
);
view-&
gt;setAlignment(Qt::
AlignLeft |
Qt::
AlignTop);
scene-&
gt;setupScene(newAction, quitAction);
setCentralWidget(view);
MainWindow extends QMainWindow and contains the GraphicsScene instance. It creates and sets up the menu bar as well.
The GraphicsScene Class▲
There are several state machines in the application. The GraphicsScene state machine handles states related to events outside the actual game scene like the letter animation in the beginning.
The GraphicsScene Constructor▲
PixmapItem *
backgroundItem =
new
PixmapItem(QStringLiteral("background"
), mode);
backgroundItem-&
gt;setZValue(1
);
backgroundItem-&
gt;setPos(0
,0
);
addItem(backgroundItem);
PixmapItem *
surfaceItem =
new
PixmapItem(QStringLiteral("surface"
), mode);
surfaceItem-&
gt;setZValue(3
);
surfaceItem-&
gt;setPos(0
, sealLevel() -
surfaceItem-&
gt;boundingRect().height() /
2
);
addItem(surfaceItem);
//The item that displays score and level
progressItem =
new
ProgressItem(backgroundItem);
textInformationItem =
new
TextInformationItem(backgroundItem);
textInformationItem-&
gt;setMessage(QString("Select new game from the menu or press Ctrl+N to start!<br/>Press left or right to move the ship and up to drop bombs."
), false
);
textInformationItem-&
gt;setPos(backgroundItem-&
gt;boundingRect().center().x() -
textInformationItem-&
gt;boundingRect().size().width() /
2
,
backgroundItem-&
gt;boundingRect().height() *
3
/
4
);
//We create the boat
addItem(boat);
boat-&
gt;setPos(this
-&
gt;width()/
2
, sealLevel() -
boat-&
gt;size().height());
boat-&
gt;hide();
The GraphicsScene class contains the background images and the score and level information texts.
The setupScene Method▲
QStateMachine *
machine =
new
QStateMachine(this
);
//This state is when the player is playing
PlayState *
gameState =
new
PlayState(this
, machine);
//Final state
QFinalState *
finalState =
new
QFinalState(machine);
//Animation when the player enters the game
QAnimationState *
lettersMovingState =
new
QAnimationState(machine);
lettersMovingState-&
gt;setAnimation(lettersGroupMoving);
//Animation when the welcome screen disappears
QAnimationState *
lettersFadingState =
new
QAnimationState(machine);
lettersFadingState-&
gt;setAnimation(lettersGroupFading);
//if it is a new game then we fade out the welcome screen and start playing
lettersMovingState-&
gt;addTransition(newAction, &
amp;QAction::
triggered, lettersFadingState);
lettersFadingState-&
gt;addTransition(lettersFadingState, &
amp;QAnimationState::
animationFinished, gameState);
//New Game is triggered then player starts playing
gameState-&
gt;addTransition(newAction, &
amp;QAction::
triggered, gameState);
//Wanna quit, then connect to CTRL+Q
gameState-&
gt;addTransition(quitAction, &
amp;QAction::
triggered, finalState);
lettersMovingState-&
gt;addTransition(quitAction, &
amp;QAction::
triggered, finalState);
//Welcome screen is the initial state
machine-&
gt;setInitialState(lettersMovingState);
machine-&
gt;start();
//We reach the final state, then we quit
connect(machine, &
amp;QStateMachine::
finished, qApp, &
amp;QApplication::
quit);
The four state machine states are created with sequential transitions from one to the next. The gameState also has a transition that is triggered by newAction, the new game menu item, or its shortcut key at any point in the application. The gameState state is an instance of the PlayState class.
The PlayState Class▲
The PlayState class is a QState derived class that handles the state when the game is in progress.
machine =
new
QStateMachine;
//This state is active when the player is playing
LevelState *
levelState =
new
LevelState(scene, this
, machine);
//This state is active when the player is actually playing but the game is not paused
QState *
playingState =
new
QState(levelState);
levelState-&
gt;setInitialState(playingState);
//This state is active when the game is paused
PauseState *
pauseState =
new
PauseState(scene, levelState);
//We have one view, it receives the key press events
QKeyEventTransition *
pressPplay =
new
QKeyEventTransition(scene-&
gt;views().at(0
), QEvent::
KeyPress, Qt::
Key_P);
pressPplay-&
gt;setTargetState(pauseState);
QKeyEventTransition *
pressPpause =
new
QKeyEventTransition(scene-&
gt;views().at(0
), QEvent::
KeyPress, Qt::
Key_P);
pressPpause-&
gt;setTargetState(playingState);
//Pause "P" is triggered, when the player pauses the game
playingState-&
gt;addTransition(pressPplay);
//To get back to playing when the game has been paused
pauseState-&
gt;addTransition(pressPpause);
//This state is active when player has lost
LostState *
lostState =
new
LostState(scene, this
, machine);
//This state is active when player has won
WinState *
winState =
new
WinState(scene, this
, machine);
//If boat has been destroyed then the game is finished
levelState-&
gt;addTransition(scene-&
gt;boat, &
amp;Boat::
boatExecutionFinished,lostState);
//This transition checks if we have won or not
WinTransition *
winTransition =
new
WinTransition(scene, this
, winState);
//If boat has been destroyed then the game is finished
levelState-&
gt;addTransition(winTransition);
//This state is for an animation when the score changes
UpdateScoreState *
scoreState =
new
UpdateScoreState(levelState);
//This transition updates the score when a submarine is destroyed
UpdateScoreTransition *
scoreTransition =
new
UpdateScoreTransition(scene, this
, levelState);
scoreTransition-&
gt;setTargetState(scoreState);
//If the boat has been destroyed then the game is finished
playingState-&
gt;addTransition(scoreTransition);
//We go back to play state
scoreState-&
gt;addTransition(playingState);
//We start playing!!!
machine-&
gt;setInitialState(levelState);
//Final state
QFinalState *
finalState =
new
QFinalState(machine);
//This transition is triggered when the player presses space after completing a level
CustomSpaceTransition *
spaceTransition =
new
CustomSpaceTransition(scene-&
gt;views().at(0
), this
, QEvent::
KeyPress, Qt::
Key_Space);
spaceTransition-&
gt;setTargetState(levelState);
winState-&
gt;addTransition(spaceTransition);
//We lost so we should reach the final state
lostState-&
gt;addTransition(lostState, &
amp;QState::
finished, finalState);
scene-&
gt;textInformationItem-&
gt;hide();
machine-&
gt;start();
The PlayState state machine handles higher level game logic like pausing the game and updating the score.
The playingState state is a QState instance that is active while the user is actively playing the game. The pauseState is set up with transitions to and from playingState, which are triggered by pressing the p key. The lostState is created with a transition to it, which is triggered when the boat is destroyed. The winState is also created here with a transition to and from the levelState.
The LevelState Class▲
void
LevelState::
initializeLevel()
{
//we re-init the boat
scene-&
gt;boat-&
gt;setPos(scene-&
gt;width()/
2
, scene-&
gt;sealLevel() -
scene-&
gt;boat-&
gt;size().height());
scene-&
gt;boat-&
gt;setCurrentSpeed(0
);
scene-&
gt;boat-&
gt;setCurrentDirection(Boat::
None);
scene-&
gt;boat-&
gt;setBombsLaunched(0
);
scene-&
gt;boat-&
gt;show();
scene-&
gt;setFocusItem(scene-&
gt;boat, Qt::
OtherFocusReason);
scene-&
gt;boat-&
gt;run();
scene-&
gt;progressItem-&
gt;setScore(game-&
gt;score);
scene-&
gt;progressItem-&
gt;setLevel(game-&
gt;currentLevel +
1
);
const
GraphicsScene::
LevelDescription currentLevelDescription =
scene-&
gt;levelsData.value(game-&
gt;currentLevel);
for
(const
QPair&
lt;int
,int
&
gt; &
amp;subContent : currentLevelDescription.submarines) {
GraphicsScene::
SubmarineDescription submarineDesc =
scene-&
gt;submarinesData.at(subContent.first);
for
(int
j =
0
; j &
lt; subContent.second; ++
j ) {
SubMarine *
sub =
new
SubMarine(submarineDesc.type, submarineDesc.name, submarineDesc.points);
scene-&
gt;addItem(sub);
int
random =
QRandomGenerator::
global()-&
gt;bounded(15
) +
1
;
qreal x =
random ==
13
||
random ==
5
? 0
: scene-&
gt;width() -
sub-&
gt;size().width();
qreal y =
scene-&
gt;height() -
(QRandomGenerator::
global()-&
gt;bounded(150
) +
1
) -
sub-&
gt;size().height();
sub-&
gt;setPos(x,y);
sub-&
gt;setCurrentDirection(x ==
0
? SubMarine::
Right : SubMarine::
Left);
sub-&
gt;setCurrentSpeed(QRandomGenerator::
global()-&
gt;bounded(3
) +
1
);
}
}
}
The components of the scene are initialized based on what level the player has reached.