Cocos2d.X.Example.Beginners.Guide.Second.812

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 270 [warning: Documents this large are best viewed by clicking the View PDF Link!]

www.it-ebooks.info
Cocos2d-x by Example
Beginner's Guide
Second Edition
Unleash your inner creavity and learn how to build
great cross-plaorm 2D games with the popular
Cocos2d-x framework
Roger Engelbert
BIRMINGHAM - MUMBAI
www.it-ebooks.info
Cocos2d-x by Example Beginner's Guide
Second Edition
Copyright © 2015 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system,
or transmied in any form or by any means, without the prior wrien permission of the
publisher, except in the case of brief quotaons embedded in crical arcles or reviews.
Every eort has been made in the preparaon of this book to ensure the accuracy of the
informaon presented. However, the informaon contained in this book is sold without
warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers
and distributors will be held liable for any damages caused or alleged to be caused directly or
indirectly by this book.
Packt Publishing has endeavored to provide trademark informaon about all of the
companies and products menoned in this book by the appropriate use of capitals.
However, Packt Publishing cannot guarantee the accuracy of this informaon.
First published: April 2013
Second edion: March 2015
Producon reference: 1240315
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham B3 2PB, UK.
ISBN 978-1-78528-885-2
www.packtpub.com
Cover image by Roger Engelbert (rengelbert@gmail.com)
www.it-ebooks.info
Credits
Author
Roger Engelbert
Reviewers
Saeed Afshari
Joni Mikkola
Michael Price
Vamsi Krishna Veligatla
Chatchai Mark Wangwiwaana
Commissioning Editor
Akram Hussain
Acquision Editor
Sam Wood
Content Development Editor
Rika Singh
Technical Editor
Mrunal M. Chavan
Copy Editors
Sonia Michelle Cheema
Aar Saldanha
Adithi Shey
Project Coordinator
Judie Jose
Proofreaders
Simran Bhogal
Maria Gould
Paul Hindle
Indexer
Priya Sane
Producon Coordinator
Nitesh Thakur
Cover Work
Nitesh Thakur
www.it-ebooks.info
About the Author
Roger Engelbert is a game developer, with over 10 years of experience developing online
games. He grew up surrounded by video games and thinks of classic, 8-bit, arcade tles as
a form of art. He is the author behind the blog Done With Computers, which is chock-full of
tutorials on game development and design. Roger sincerely hopes to soon live in a world
where people of all ages will be given the knowledge and opportunity to experience the joy
of building their own games.
I would like to thank the people from Packt Publishing that helped
me through the process of wring this book. And above all, my family,
who—once again!—had to see a bleary eyed and slightly surly version of
myself as I worked on this book long into the night.
www.it-ebooks.info
About the Reviewers
Saeed Afshari is an independent game developer and a researcher in the area of
human-computer interacon. He has published more than 20 tles on the iOS App Store
and Google Play under the brand Neat Games, and is focused on developing mobile games
for iOS and Android.
He is a member of the Games, Interacon and Novel Interface Technologies (IGNITE)
research collecve at the University of Luxembourg, working on natural and novel user
interfaces, and understanding how people interact with games and how games should
interact with people. His current research projects deal with interacon with mobile games,
virtual-reality head-mounted displays, and behavior change through serious gaming. He is
the game developer for the European Commission project, LiveCity, which deals with long
distance mulplayer gaming on multouch tables.
With over 15 years of experience in game development and interacve media, he has
built skills in plaorms, including OpenGL, DirectX, Unity, Cocos2d-x, and also interacon
design for natural user interfaces and virtual reality. He has invented an algorithm to track
permanent magnets for use in mobile games, in parcular in the form of a magnec joysck.
He is willing to consult and collaborate on creang games and interacve soware targeted
for educaon, entertainment, and social networks.
For more informaon about Saeed and Neat Games, you can contact pr@neat-games.com.
www.it-ebooks.info
Joni Mikkola is currently working on his next mobile game in northern Finland. He keeps
his game developing stamina up by training regularly at the gym and eang healthy. Among
developing games, he oen reads books, plays piano, or bakes buns to keep ideas owing
and mind focused. He constantly keeps challenging the status quo, which in turn helps in
learning new ways to create things.
He has developed games for over 4 years professionally mostly for mobile plaorms.
He targets casual games and focuses on creang simplisc designs. With one game released,
he is currently working on his next game, which will be released in early 2015 for Android
and iOS plaorms. Somemes he chills by parcipang in Ludum Dare game contests.
Michael Price has been working in the game industry since 2009. He studied game
development at Algonquin College in Oawa and has always had an interest in technology
and programming. He's been playing video games for as long as he can remember and now
uses that passion to build fun and excing games. He has extensive knowledge of building
games for the Web and mobile. He has his own website (http://www.michaelrjprice.
com) that shows all his past and current projects.
I would like to thank my parents for their support, and my girlfriend for
always being there for me.
www.it-ebooks.info
Vamsi Krishna Veligatla is the Director of engineering at Tiny Mogul games, which is
the gaming arm of Hike Messenger Pvt Ltd. He was the lead developer on some of the
most iconic games such as Shiva: The Time Bender and Dadi vs Jellies developed at Tiny
Mogul Games.
He has a master's degree in computer science from the Internaonal Instute of Informaon
Technology, Hyderabad. Previously, he worked at Nvidia Graphics Pvt Ltd, AMD (ATI),
and University of Groningen, Netherlands.
I would like to thank my family for their love and support.
Chatchai Mark Wangwiwaana is a game researcher, designer, and programmer.
Currently, he is pursuing a PhD in game-based learning and using video games to improve
health. As a freelancer in the gaming industry, Chatchai has been developing games
for entertaining and educaon. Kraven Manor was one of the award-winning games
at Intel University Showcase, 2014. More informaon about his works is available at
www.chatchaiwang.com.
www.it-ebooks.info
www.PacktPub.com
Support les, eBooks, discount offers, and more
For support les and downloads related to your book, please visit www.PacktPub.com.
Did you know that Packt oers eBook versions of every book published, with PDF and ePub
les available? You can upgrade to the eBook version at www.PacktPub.com and as a print
book customer, you are entled to a discount on the eBook copy. Get in touch with us at
service@packtpub.com for more details.
At www.PacktPub.com, you can also read a collecon of free technical arcles, sign up
for a range of free newsleers and receive exclusive discounts and oers on Packt books
and eBooks.
TM
https://www2.packtpub.com/books/subscription/packtlib
Do you need instant soluons to your IT quesons? PacktLib is Packt's online digital book
library. Here, you can search, access, and read Packt's enre library of books.
Why subscribe?
Fully searchable across every book published by Packt
Copy and paste, print, and bookmark content
On demand and accessible via a web browser
Free access for Packt account holders
If you have an account with Packt at www.PacktPub.com, you can use this to access
PacktLib today and view 9 enrely free books. Simply use your login credenals for
immediate access.
www.it-ebooks.info
[ i ]
Table of Contents
Preface vii
Chapter 1: Installing Cocos2d-x 1
Downloading and installing Cocos2d-x 1
Time for acon – downloading, downloading, downloading 2
Time for acon – installing Cocos2d-x 3
Hello-x World-x 4
Time for acon – creang an applicaon 4
Time for acon – deploying to Android 6
The folder structure 8
The iOS linkage classes 9
The AppDelegate class 9
The HelloWorldScene class 10
Who begets whom? 10
Finding more reference material 10
Time for acon – running the test samples 11
The other tools 12
Summary 13
Chapter 2: You Plus C++ Plus Cocos2d-x 15
Cocos2d-x – an introducon 15
Containers 16
The Director and cache classes 17
The other stu 17
Do you know C++? 18
The class interface 18
Time for acon – creang the interface 18
The class implementaon 20
Time for acon – creang the implementaon 20
Instanang objects and managing memory 22
www.it-ebooks.info
Table of Contents
[ ii ]
Using stac methods 22
The C++ and Cocos2d-x style 23
What you get with Ref 25
Summary 25
Chapter 3: Your First Game – Air Hockey 27
Game conguraons 28
Time for acon – creang your game project 28
Time for acon – laying down the rules 30
Supporng rena displays 30
Time for acon – adding the image les 30
Time for acon – adding rena support 31
Adding sound eects 32
Time for acon – adding the sound les 32
Extending Sprite 34
Time for acon – adding GameSprite.cpp 34
Time for acon – implemenng GameSprite 35
The actual game scene 36
Time for acon – coding the GameLayer interface 37
Time for acon – implemenng init() 38
Time for acon – handling multouches 41
Time for acon – adding our main loop 43
Time for acon – updang scores 47
Time for acon – running the game in Android 48
Summary 49
Chapter 4: Fun with Sprites – Sky Defense 51
The game – sky defense 51
The game sengs 52
The start project 52
Adding screen support for a universal app 52
Adding background music 53
Inializing the game 54
Using sprite sheets in Cocos2d-x 54
Batch drawing sprites 55
Time for acon – creang SpriteBatchNode 56
Bitmap fonts in Cocos2d-x 58
Time for acon – creang bitmap font labels 59
Time for acon – adding the nal screen sprites 60
Time for acon – creang our object pools 63
Acons in a nutshell 64
Time for acon – creang acons with Cocos2d-x 64
www.it-ebooks.info
Table of Contents
[ iii ]
Animang a sprite in Cocos2d-x 66
Time for acon – creang animaons 66
Time to make our game ck! 68
Time for acon – handling touches 68
Time for acon – starng and restarng the game 71
Time for acon – updang the game 73
Time for acon – retrieving objects from the pool 74
Play the game! 76
Time for acon – running the game in Android 77
Summary 79
Chapter 5: On the Line – Rocket Through 81
The game – Rocket Through 81
The game sengs 81
Play rst, work later 82
The start project 83
Screen sengs 83
So what are parcles? 84
Time for acon – creang parcle systems 84
Creang the grid 86
Drawing primives in Cocos2d-x 87
Time for acon – let's do some drawing! 87
The rocket sprite 89
Time for acon – updang our rocket sprite 90
Time for acon – handling touches 92
The game loop 96
Time for acon – adding the main loop 96
Kill and reset 99
Time for acon – adding our resets and kills 99
Time for acon – running the game in Android 101
Summary 102
Chapter 6: Quick and Easy Sprite – Victorian Rush Hour 103
The game – Victorian Rush Hour 103
The game sengs 104
Rapid prototyping with Cocos2d-x 104
Time for acon – creang placeholder sprites 105
The Player object 106
The Block object 106
The terrain object 106
Time for acon – coding the player 107
Time for acon – coding the Block object 110
www.it-ebooks.info
Table of Contents
[ iv ]
Planning the Terrain class 111
Building the terrain object 113
Time for acon – inializing our Terrain class 113
Time for acon – inializing our Blocks object 115
Time for acon – moving and reseng 119
Plaorm collision logic 121
Time for acon – adding collision detecon 122
Adding the controls 124
Time for acon – handling touches 124
Time for acon – coding the main loop 126
Summary 127
Chapter 7: Adding the Looks – Victorian Rush Hour 129
Victorian Rush Hour – the game 130
New sprites 130
Animaons 131
Texturing our buildings with sprites 131
Time for acon – texturing the buildings 132
Containers within containers 135
Creang a parallax eect 136
Time for acon – creang a parallax eect 137
Adding a menu to our game 138
Time for acon – creang Menu and MenuItem 139
Adding a tutorial to our game 141
Time for acon – adding a tutorial 142
Time for acon – running the game in Android 144
Summary 145
Chapter 8: Geng Physical – Box2D 147
Building a Box2D project with Cocos2d-x 147
Time for acon – using debug draw in your Box2D project 148
So what is a physics engine? 151
Meeng Box2D 151
Meeng the world 152
Running the simulaon 153
No Ref objects in Box2D 153
Meeng the bodies 154
Our game – MiniPool 155
Game sengs 156
Sprite plus b2Body equal to b2Sprite 156
Creang the pool table 157
Creang edges 158
www.it-ebooks.info
Table of Contents
[ v ]
Creang the ball objects 159
Creang collision lters 161
Creang the cue 161
Creang a contact listener 163
The game controls 165
Time for acon – adding the touch events 166
The main loop 170
Time for acon – adding the main loop 170
Adding a mer to our game 172
Time for acon – creang a mer 172
Time for acon – running the game in Android 173
Summary 175
Chapter 9: On the Level – Eskimo 177
The game – Eskimo 178
The game sengs 179
Organizing the game 180
Using scenes in Cocos2d-x 181
Time for acon – creang a scene transion 181
Time for acon – creang transions with a singleton Layer class 183
Loading external data from a .plist le 184
Time for acon – creang a .plist le 185
Loading the level data 186
Time for acon – retrieving data from the .plist le 187
Saving game data 189
Time for acon – storing the completed levels 189
Using events in your game 190
Time for acon – using the event dispatcher 191
Using the accelerometer 192
Time for acon – reading accelerometer data 192
Reusing b2Bodies 194
Time for acon – changing a b2Body xture 194
Time for acon – running the game in Android 195
Summary 196
Chapter 10: Introducing Lua! 197
So what is Lua like? 197
Debugging in Lua – the knights who say nil 199
The game – Stone Age 200
Time for acon – creang or imporng a project 201
Time for acon – seng up our screen resoluon 203
Time for acon – creang a menu scene 204
www.it-ebooks.info
Table of Contents
[ vi ]
Time for acon – creang our game scene 207
Time for acon – building the gems 209
Time for acon – changing the grid with GridController 211
Time for acon – swapping the gems and looking for matches 216
Time for acon – collapsing the grid and repeang 220
Time for acon – animang matches and collapses 226
Time for acon – publishing the game with the Cocos IDE 229
Summary 230
Appendix A: Vector Calculaons with Cocos2d-x 231
What are vectors? 231
The vector methods 232
Using ccp helper methods 233
Rotang the rocket around a point 233
Using the dot product of vectors 234
Moving from pixel-based speed to angular-based speed 236
Appendix B: Pop Quiz Answers 239
Index 241
www.it-ebooks.info
Preface
Cocos2d-x combines the benets of using one the most popular and test-proven 2D game
frameworks out there with the power and portability of C++. So, you get the best deal
possible. Not only is the framework built to be easy to use and quick to implement, it also
allows your code to target more than one system.
The book will show you how to use the framework to quickly implement your ideas, and let
Cocos2d-x help you with the translaon of all that OpenGL gobbledygook, leaving you with
the fun part: making sprites jump around and hit each other!
There are seven examples of games in this book with two of them being physics-based, using
Box2D and one using the Lua bindings and the new Cocos Code IDE. With each example,
you'll learn more about the framework and the magical lines that can quickly add parcle
eects, animaons, sounds, UI elements, and all sorts of wonderful things to your games.
Not only this, but you will also learn how to target both iOS and Android devices, and
mulple screen sizes.
What this book covers
Chapter 1, Installing Cocos2d-x, guides you through the download and installaon of the
Cocos2d-x framework. It also examines the ins and outs of a basic Cocos2d-x applicaon
and deployment to both iOS and Android devices.
Chapter 2, You Plus C++ Plus Cocos2d-x, explains the main elements in the framework.
It also covers the dierences in syntax when developing in C++, and the dierences in
memory management when developing with Cocos2d-x.
Chapter 3, Your First Game – Air Hockey, kick-starts our game development tutorials by
using Cocos2d-x to build an air hockey game. You will learn how to load the images for
your sprites, display text, manage touches, and add sounds to your game.
www.it-ebooks.info
Preface
[ viii ]
Chapter 4, Fun with Sprites – Sky Defense, demonstrates the power of acons in
Cocos2d-x, and shows how an enre game could be built with them. It also introduces
the concept of sprite sheets and the steps to build a universal applicaon targeng
dierent screen resoluons.
Chapter 5, On the Line – Rocket Through, adds two new elements to our game development
toolbox: how to draw primives, such as lines, curves, and circles, and how to use parcle
systems to improve the look of our game with special eects.
Chapter 6, Quick and Easy Sprite – Victorian Rush Hour, shows how you can use Cocos2d-x to
quickly implement game ideas for further tesng and development by rapidly building game
prototypes with placeholder sprites. In the game example used for this chapter, you'll also
learn how to build a side-scrolling plaorm game.
Chapter 7, Adding the Looks – Victorian Rush Hour, connues with the project from
the previous chapter adding the nal touches to the game including a menu and a
playable tutorial.
Chapter 8, Geng Physical – Box2D, introduces the popular Box2D API for a physics
simulaon, guiding you through the process of using Box2D in the development of a pool
game. You learn how to create bodies and manage the way they interact with each other.
Chapter 9, On the Level – Eskimo, teaches you how to load external data for game levels,
how to store game-related data locally as well as structure your games with mulple scenes.
We use a second Box2D game to illustrate these topics, plus a couple of new concepts, such
as using the event dispatcher to structure your games beer.
Chapter 10, Introducing Lua!, will guide you in the development of a mulplaorm
match-three game using Lua and the new Cocos Code IDE. You will see how similar the
calls are between the C++ version and its Lua bindings and how easy it is to develop a
game in Lua.
Appendix A, Vector Calculaons with Cocos2d-x, covers some of the math concepts used in
Chapter 5, On the Line – Rocket Through, in a lile more detail.
Appendix B, Pop Quiz Answers, provides answers to the pop quiz available in some chapters.
What you need for this book
In order to run the games developed in this book, you will need Xcode for iOS devices,
and Eclipse for Android, as well as the Cocos Code IDE for the Lua game. Although the
tutorials describe the development process using Xcode in each chapter of the book,
you will see how to import the code in Eclipse and develop and deploy from there.
www.it-ebooks.info
Preface
[ ix ]
Who this book is for
You have a passion for games. You may have used Cocos2d already (the Objecve-C version
of the framework) and are eager to learn its C++ port. Or, you know a lile bit of some other
C-based language, such as Java, PHP, or Objecve-C and you want to learn how to develop
2D games in C++. You may also be a C++ developer already and want to know what all the
hoopla about Cocos2d-x is. If you t any of these scenarios, welcome aboard!
Sections
In this book, you will nd several headings that appear frequently (Time for acon,
What just happened?, Pop quiz, and Have a go hero).
To give clear instrucons on how to complete a procedure or task, we use these secons
as follows:
Time for action – heading
1. Acon 1
2. Acon 2
3. Acon 3
Instrucons oen need some extra explanaon to ensure they make sense, so they are
followed with these secons:
What just happened?
This secon explains the working of the tasks or instrucons that you have just completed.
You will also nd some other learning aids in the book, for example:
Pop quiz – heading
These are short mulple-choice quesons intended to help you test your own
understanding.
Have a go hero – heading
These are praccal challenges that give you ideas to experiment with what you have learned.
www.it-ebooks.info
Preface
[ x ]
Conventions
You will also nd a number of text styles that disnguish between dierent kinds of
informaon. Here are some examples of these styles and an explanaon of their meaning.
Code words in text, database table names, folder names, lenames, le extensions,
pathnames, dummy URLs, user input, and Twier handles are shown as follows: "For
background music volume, you must use setBackgroundMusicVolume."
A block of code is set as follows:
CCScene* GameLayer::scene()
{
// 'scene' is an autorelease object
CCScene *scene = CCScene::create();
GameLayer *layer = GameLayer::create();
scene->addChild(layer);
return scene;
}
When we wish to draw your aenon to a parcular part of a code block, the relevant lines
or items are set in bold:
//add score display
_player1ScoreLabel = CCLabelTTF::create("0", "Arial", 60);
_player1ScoreLabel->setRotation(90);
this->addChild(_player1ScoreLabel);
Any command-line input or output is wrien as follows:
sudo ./install-templates-xcode.sh -u
New terms and important words are shown in bold. Words that you see on the screen,
in menus or dialog boxes for example, appear in the text like this: "In the dialog box select
cocos2d-x under the iOS menu and choose the cocos2dx template."
www.it-ebooks.info
Preface
[ xi ]
Warnings or important notes appear in a box like this.
Tips and tricks appear like this.
Reader feedback
Feedback from our readers is always welcome. Let us know what you think about this
book—what you liked or may have disliked. Reader feedback is important for us to develop
tles that you really get the most out of.
To send us general feedback, simply send an e-mail to feedback@packtpub.com, and
menon the book tle through the subject of your message.
If there is a topic that you have experse in and you are interested in either wring or
contribung to a book, see our author guide on www.packtpub.com/authors.
Customer support
Now that you are the proud owner of a Packt book, we have a number of things to help you
to get the most from your purchase.
Downloading the example code
You can download the example code les for all Packt books you have purchased from
your account at http://www.packtpub.com. If you purchased this book elsewhere, you
can visit http://www.packtpub.com/support and register to have the les e-mailed
directly to you.
Downloading the color images of this book
We also provide you with a PDF le that has color images of the screenshots/diagrams used
in this book. The color images will help you beer understand the changes in the output.
You can download this le from https://www.packtpub.com/sites/default/files/
downloads/8852OS_GraphicsBundle.pdf.
www.it-ebooks.info
Preface
[ xii ]
Errata
Although we have taken every care to ensure the accuracy of our content, mistakes do happen.
If you nd a mistake in one of our books—maybe a mistake in the text or the code—we
would be grateful if you could report this to us. By doing so, you can save other readers from
frustraon and help us improve subsequent versions of this book. If you nd any errata, please
report them by vising http://www.packtpub.com/submit-errata, selecng your book,
clicking on the Errata Submission Form link, and entering the details of your errata. Once your
errata are veried, your submission will be accepted and the errata will be uploaded to our
website or added to any list of exisng errata under the Errata secon of that tle.
To view the previously submied errata, go to https://www.packtpub.com/books/
content/support and enter the name of the book in the search eld. The required
informaon will appear under the Errata secon.
Piracy
Piracy of copyrighted material on the Internet is an ongoing problem across all media.
At Packt, we take the protecon of our copyright and licenses very seriously. If you come
across any illegal copies of our works in any form on the Internet, please provide us with the
locaon address or website name immediately so that we can pursue a remedy.
Please contact us at copyright@packtpub.com with a link to the suspected
pirated material.
We appreciate your help in protecng our authors and our ability to bring you
valuable content.
Questions
If you have a problem with any aspect of this book, you can contact us at
questions@packtpub.com, and we will do our best to address the problem.
www.it-ebooks.info
[ 1 ]
1
Installing Cocos2d-x
In this chapter, we'll get things up and running on your machine so you can
get the most out of the examples in this book. This will include information on
downloading the framework and creating a project as well as an overview of
the basic structure of a Cocos2d-x application.
I will also point you to some extra tools you should consider getting to help you
with the development process, such as tools to build sprite sheets, particle effects,
and bitmap fonts. Although these tools are optional, and you can still learn how
to use sprite sheets, particles, and bitmap fonts just by following the examples
given in this book, you might consider these tools for your own projects.
Things you will learn in this rst chapter are as follows:
How to download Cocos2d-x
How to run your rst mulplaorm applicaon
What the basic project looks like and how to nd your way around it
How to use the test project as a major reference source
Downloading and installing Cocos2d-x
All the examples in this book were developed in a Mac using Xcode and/or Eclipse.
The example in the nal chapter uses Cocos2d-x own IDE for scripng. Although you
can use Cocos2d-x to develop your games for other plaorms using dierent systems,
the examples were built in a Mac and deployed to both iOS and Android.
www.it-ebooks.info
Installing Cocos2d-x
[ 2 ]
Xcode is free and can be downloaded from the Mac App store (https://developer.
apple.com/xcode/index.php), but in order to test your code on an iOS device and publish
your games, you will need a developer account with Apple, which will cost you $99 a year. You
can nd more informaon on their website: https://developer.apple.com/.
For Android deployment, I recommend that you get the Eclipse and ADT bundle from
Google, which you can nd at http://developer.android.com/sdk/installing/
installing-adt.html. You will be able to test your games in an Android device for free.
So, assuming you have an Internet connecon, let's begin!
Time for action – downloading, downloading, downloading
We start by downloading the necessary SDKs, NDKs, and general bits and bobs:
1. Go to http://www.cocos2d-x.org/download and download the latest stable
version of Cocos2d-x. For this book, I'll be using version Cocos2d-x-3.4.
2. Uncompress the les somewhere in your machine you can remember later.
I recommend that you add all the les we're going to download now to the
same folder.
3. Go ahead and download the Code IDE as well. We'll be using this in the last chapter
in this book.
4. Then, go to http://developer.android.com/sdk/installing/
installing-adt.html and download the Eclipse ADT plugin (if you don't have
Eclipse or the Android SDK installed, download them at https://eclipse.org/
downloads/ and http://developer.android.com/sdk/installing/
index.html?pkg=tools, respecvely).
If you have any problems installing the ADT plugin, you will find complete
instructions at http://developer.android.com/sdk/
installing/installing-adt.html.
5. Now, for Apache Ant, go to http://ant.apache.org/bindownload.cgi and
look for the links to the compressed les, and download the .zip one.
6. Lastly, go to https://developer.android.com/tools/sdk/ndk/index.
html and download the latest version of the NDK for your target system. Follow
the installaon instrucons on this same page on how to extract the les as some
systems will not allow these to be self-extractable. A word of warning: you must use
a version of the NDK above r8e with Cocos2d-x 3.x.
www.it-ebooks.info
Chapter 1
[ 3 ]
What just happened?
You have successfully downloaded everything you'll need to set up Cocos2d-x in your
machine and start development. If you are using a Mac, you will probably need to change
your security sengs in System Preferences to allow Eclipse to run. Also, go ahead and open
the Android SDK Manager inside Eclipse by going to the Window-Android SDK Manager
menu, and install the packages for at least Version 2.3.3 and then anything above that you
might wish to target.
Also, make sure you have Python installed in your machine. Inside Terminal or command
prompt, just type the word python and hit enter. If you don't have it installed, visit
https://www.python.org/ and follow the instrucons there.
So by the end of this step you should have Cocos2d-x, CocosIDE, the Android SDK the NDK,
and Apache Ant all extracted inside a folder.
Now let's install Cocos2d-x.
Time for action – installing Cocos2d-x
Open Terminal or command prompt and navigate to the Cocos2d-x extracted folder:
1. You can do this by typing cd (that is, cd and a space) and then dragging the folder to
the Terminal window and hing Enter. In my machine this looks like:
cd /Applications/Dev/cocos2d-x-3.4
2. Next, type python setup.py.
3. Hit Enter. You will be prompted for the paths to the NDK, SDK and Apache ANT
root. You must drag each one of the folders to the Terminal window, making sure to
delete any extra spaces at the end of the path and hit Enter. So for the NDK I get:
/Applications/Dev/android-ndk-r10c
4. Next, it's the path for the SDK. Once again, I drag the folder that I have stored inside
Eclipse's folder:
/Applications/eclipse/sdk
5. Next, it's the path to ANT. If you have it properly installed on your machine, the
path will be something like usr/local/bin and the setup script will nd it for you.
Otherwise, you can use the one you downloaded and extracted. Just point to the
bin folder inside it:
/Applications/Dev/apache-ant-1.9.4/bin
www.it-ebooks.info
Installing Cocos2d-x
[ 4 ]
6. The last step is to add these paths to your system. Follow the last instrucon in the
window where it says: Please execute command: "source /Users/YOUR_USER_
NAME/.bash_prole" to make added system variables take eect. You can copy
the command inside the quotes, paste it, and hit Enter.
What just happened?
You now have Cocos2d-x installed on your machine and you're ready to go. Time to create
our rst project!
Hello-x World-x
Let's create that old chestnut in computer programming: the hello world example.
Time for action – creating an application
Open Terminal again and follow these easy steps:
1. You should have the path to the Cocos2d-x console already added to your system.
You can test this by using the cocos command inside Terminal. In order to create a
new project called HelloWorld, using C++ as its primary language and save it on
your desktop, you need to run the following command, replacing YOUR_BUNDLE_
INDETIFIER with a package name of your choice, and replacing PATH_TO_YOUR_
PROJECT with the path to wherever you wish to save your project:
cocos new HelloWorld -p YOUR_BUNDLE_IDENTIFIER -l cpp -d PATH_TO_
YOUR_PROJECT
2. As an example, in my machine this is the line I typed:
cocos new HelloWorld -p com.rengelbert.HelloWorld -l cpp -d /
Users/rengelbert/Desktop/HelloWorld
And hit Enter. If you choose not to give a directory parameter (-d), the Cocos
console will save the project inside the Cocos2d-x folder.
3. Now you can go to your desktop, or wherever you chose to save your project, and
navigate to the folder proj.ios_mac inside the HelloWorld project. Inside that
folder you'll nd the Xcode project le. Once you open the project inside Xcode,
you can click the Run buon and you're done.
www.it-ebooks.info
Chapter 1
[ 5 ]
When you run a cocos2d-x applicaon in Xcode, it is quite common for the
program to post some warnings regarding your code, or most likely, the
frameworks. These will mostly reference deprecated methods or statements
that do not precisely follow more recent and stricter rules of the current SDK.
But that's okay. These warnings, though certainly annoying, can be ignored.
What just happened?
You created your rst Cocos2d-x applicaon. The parameters used on the command line are:
-p for package or bundle idener
-l for language, and here, you have the opon cpp, lua, or JavaScript
Now let's run this app in Android.
Downloading the example code
You can download the example code les from your account at
http://www.packtpub.com for all the Packt Publishing books you
have purchased. If you purchased this book elsewhere, you can visit
http://www.packtpub.com/support and register to have the les
e-mailed directly to you.
www.it-ebooks.info
Installing Cocos2d-x
[ 6 ]
Time for action – deploying to Android
We're going to open the project inside Eclipse:
1. Open Eclipse.
2. We need to x the path to the NDK; this step may be oponal in your system, and in
any case, it must be done only once. Inside Eclipse, go to Eclipse-Preferences, then
inside the C/C++ opon select Build-Environment.
3. You need to add the NDK path and it must be called NDK_ROOT. In order to do this,
you must click Add…, and use NDK_ROOT as the name and then click inside the
Value eld to make sure the mouse cursor is acve inside it, and then drag the NDK
folder you downloaded inside the eld. On my machine the result looked like this:
4. Click Apply. It might be good to restart Eclipse. (If you do not see the C/C++ opon in
Preferences, it means you do not have the CDT plugins installed. Look for complete
instrucons at http://www.eclipse.org/cdt/ on how to install them.)
5. Now we're ready to bring our project inside Eclipse. Select File | Import….
6. In the dialog box, select the Android opon, and then select the Exisng Android
Code Into Workspace opon and click Next:
www.it-ebooks.info
Chapter 1
[ 7 ]
7. Click on the Browse buon and navigate to the HelloWorld project, and select the
proj.android folder inside it and hit Next.
8. You should see the project compiling. The enre framework library will be compiled
and so will the classes used in the base template.
9. Sadly, with Version 3.4 of the framework, we have an extra step here. It was gone in
Version 3.3, but now it's back. You must import the project's referenced Cocos2d-x
library into Eclipse's package explorer. Repeat step 8, but instead of selecng the
proj.android folder, select cocos2d/cocos/platform/android/java,
and hit Next.
10. This will select a library called libcocos2dx; click on Finish.
11. Once that's done, it might be good to run a build just in case your project failed
to generate the correct resource les. So, navigate to Project | Build All.
12. Now, connect your Android device and make sure Eclipse has recognized it.
You might need to turn on Development opons in your device, or restart your
device while connected to your computer and with Eclipse running.
13. Right-click on your project folder and select Run As | Android Applicaon.
What just happened?
You ran your rst Cocos2d-x applicaon in Android. Don't bother with the simulator for
your Android builds; it's a waste of me. If you don't have a device handy, consider
invesng in one.
Alternavely, you could open your project's root folder inside Terminal
(or command prompt) and use the Cocos2d-x console compile command:
cocos compile -p android
The people behind Cocos2d-x have announced they will get rid of the build Python script in the
future versions of the framework, so it's good to be prepared and know how to go without it.
While working with Eclipse, you might soon be faced with the dreaded java.lang.
NullPointerException error. This might be related to conicts in the ADT, CDT or NDK!
When you're faced with this error you have no opon other than reinstall
whatever Eclipse points to as the culprit. This might happen aer an update, or
if for some reason you have installed another framework that uses a path to the
NDK or ADT. If the error is ed to a parcular project or library, just remove all
projects from the package explorer in Eclipse and reimport them.
Now let's go over the sample applicaon and its les.
www.it-ebooks.info
Installing Cocos2d-x
[ 8 ]
The folder structure
First you have the Classes folder; this will contain the classes for your applicaon,
and are wrien enrely in C++. Below that is the Resources folder, where you nd the
images, fonts, and any kind of media used by the applicaon.
The ios folder has the necessary underlying connecon between your app and iOS.
For other plaorms, you will have their necessary linkage les in separate folders targeng
their respecve plaorm.
It is important to maintain this le structure. So your classes will go into the Classes folder
and all your images, sound les, fonts, level data should be placed in the Resources folder.
Now let's go over the main classes of the basic applicaon.
www.it-ebooks.info
Chapter 1
[ 9 ]
The iOS linkage classes
AppController and RootViewController are responsible to seng up OpenGL in
iOS as well as telling the underlying operang system that your applicaon is about to say
Hello... To the World.
These classes are wrien with a mix of Objecve-C and C++, as all the nice brackets and
the .mm extension show. You will change very lile, if anything, on these classes; and again
that will reect in changes to the way iOS handles your applicaon. So other targets would
require the same instrucons or none at all, depending on the target.
In AppController for instance, I could add support for multouch. And in
RootViewController, I could limit the screen orientaons supported by my applicaon,
for instance.
The AppDelegate class
This class marks the rst me your C++ app will talk to the underlying OS. It aempts to map
the main events mobile devices we want to dispatch and listen to. From here on, all your
applicaon will be wrien in C++ (unless you need something else for a specic target) and
from this point on, you can add condional code for dierent targets.
In AppDelegate, you should set up the Director object (it is the Cocos2d-x all powerful
singleton manager object), to run your applicaon just the way you want. You can:
Get rid of the applicaon status informaon
Change the frame rate of your applicaon
Tell Director where your high denion images are, and where your
standard denion images are, as well as which to use
You can change the overall scale of your applicaon, so it will best t
dierent screens
The AppDelegate class is also the best place to start any preloading process
And most importantly, it is here you tell the Director object what Scene to
begin your applicaon with
Here too, you will handle what happens to your applicaon if the OS decides to
kill it, push it aside, or hang it upside down to dry. All you need to do is place your
logic inside the correct event handler: applicationDidEnterBackground or
applicationWillEnterForeground.
www.it-ebooks.info
Installing Cocos2d-x
[ 10 ]
The HelloWorldScene class
When you run the applicaon, you get a screen with the words Hello World and a bunch
of numbers in one corner; those are the display stascs you decided you wanted around in
the AppDelegate class.
The actual screen is created by the oddly named HelloWorldScene class. It is a Layer
class that creates its own scene (don't worry if you don't know what a Layer or a Scene
class is; you will know soon).
When it inializes, HelloWorldScene puts a buon on the screen that you can press to
exit the applicaon. The buon is actually a Menu item part of a Menu object that only has
one buon, with two image states for the buon, and one call back event when the said
buon is pressed.
The Menu object automacally handles touch events targeng its members, so you don't
get to see any of that code oang about. Then, there is also the necessary Label object to
show the Hello World message and the background image.
Who begets whom?
If have you never worked with either Cocos2d or Cocos2d-x before, the way the inial
scene() method is instanated may lead to dizziness. To recap, in AppDelegate you have:
auto scene = HelloWorld::createScene();
director->runWithScene(scene);
Director needs a Scene object to run, which you can think of as being your applicaon,
basically. Scene needs something to show, and in this case, a Layer object will do. Scene is
then said to contain a Layer object.
Here a Scene object is created through a stac method scene inside a Layer derived
class. So the layer creates the scene, and the scene immediately adds the layer to itself.
Huh? Relax. This incestuous-like instanaon will most likely happen only once, and you get
nothing to do with it when it happens. So you can easily ignore all these funny goings-on and
look the other way. I promise instanaons will be a much easier task aer this rst one.
Finding more reference material
Follow these steps to access one of the best sources for reference material on Cocos2d-x:
its Test project.
www.it-ebooks.info
Chapter 1
[ 11 ]
Time for action – running the test samples
You can open the test project just like you would any other Xcode/Eclipse project:
1. In Eclipse, you can import the test project from inside the Cocos2d-x folder you
downloaded. You'll nd it in tests/cpp-tests/proj.android.
2. You can follow the same steps as before to build this project.
3. In Xcode, you must open the tests project le that you'll nd in the Cocos2d-x
framework folder inside the build folder: build/cocos2d_tests.xcodeproj.
4. Once the project is opened in Xcode, you must select the correct target next to the
Run buon as follows:
5. In order to actually review the code inside the tests, you may navigate to tests/
cpp-tests/Classes for the C++ tests or tests/lua-tests/src for the Lua
tests. Beer yet, if you have a program such as TextWrangler or an equivalent,
you can open these enre directories inside a Disk Browser window and have all
that informaon ready for referencing right at your desktop.
www.it-ebooks.info
Installing Cocos2d-x
[ 12 ]
What just happened?
With the test samples, you can visualize most features in Cocos2d-x, what they do, as well as
see some of the ways you can inialize and customize them.
I will refer to the code found in the tests quite oen. As usual with programming, there is
always a dierent way to accomplish the same task, so somemes, aer showing you one
way, I'll refer to a dierent one that you can nd (and by then easily understand) inside the
Test classes.
The other tools
Now comes the part where you may need to spend a bit more money to get some extremely
helpful tools (and do some extra bit of learning). In this book's examples, I use four of them:
A tool to help build sprite sheets: I'll use TexturePacker (http://www.
codeandweb.com/texturepacker). There are other alternaves, such as
Zwoptex (http://zwopple.com/zwoptex/), and they usually oer some
features for free. Cocos2d-x now oers a free program called CocosStudio, which is
somewhat similar to SpriteBuilder (previously CocosBuilder), and it oers ways to
build sprite sheets, bitmap fonts, as well as a number of other goodies. At the me
of wring this, the Windows version is somewhat superior to the Mac version, but
they are free!
A tool to help build parcle eects: I'll use Parcle Designer
(http://www.71squared.com/en/particledesigner). Depending on
your operang system, you may nd free tools online for this. Cocos2d-x comes
bundled with some common parcle eects that you can customize. But doing
it blindly is a process I do not recommend. CocosStudio also allows you to create
your own parcle eects, but you may nd its interface a bit daunng. It certainly
requires its own book of tutorials!
A tool to help build bitmap fonts: I'll use Glyph Designer
(http://www.71squared.com/en/glyphdesigner). But there are others:
bmGlyph (which is not as expensive) and FontBuilder (which is free). It is not
extremely hard to build a bitmap font—not nearly as hard as building a parcle
eect from scratch—but doing it once is enough to convince you to get one of
these tools fast. Once again, you might give CocosStudio a go.
A tool to produce sound eects: No contest—sfxr for Windows or its Mac
port cfxr. Both are free (http://www.drpetter.se/project_sfxr.html
and http://thirdcog.eu/apps/cfxr, respecvely).
www.it-ebooks.info
Chapter 1
[ 13 ]
Summary
You just learned how to install Cocos2d-x and create a basic applicaon. You also learned
enough of the structure of a basic Cocos2d-x applicaon to start building your rst game and
you know how to deploy to both iOS and Android.
Keep the Test classes by your side as you go over the examples in this book and you will be
a Cocos2d-x pro in no me!
But rst, let's go over a few things regarding the framework and its nave language.
www.it-ebooks.info
www.it-ebooks.info
[ 15 ]
2
You Plus C++ Plus Cocos2d-x
This chapter will be aimed at two types of developers: the original Cocos2d
developer who is scared of C++ but won't admit it to his friends and the C++
coder who never even heard of Cocos2d and finds Objective-C funny looking.
I'll go over the main syntax differences Objective-C developers should
pay attention to and the few code style changes involved in developing
with Cocos2d-x that C++ developers should be aware of. But first, a quick
introduction to Cocos2d-x and what it is all about.
You will learn the following topics:
What Cocos2d-x is and what it can do for you
How to create classes in C++
How to memory manage your objects in Cocos2d-x and C++
What you get out of Ref
Cocos2d-x – an introduction
So what is a 2D framework? If I had to dene it in as few words as possible, I'd say
rectangles in a loop.
At the heart of Cocos2d-x, you nd the Sprite class and what that class does, in simple
terms, is keep a reference to two very important rectangles. One is the image (or texture)
rectangle, also called the source rectangle, and the other is the desnaon rectangle. If you
want an image to appear in the center of the screen, you will use Sprite. You will pass it
the informaon of what and where that image source is and where on the screen you want
it to appear.
www.it-ebooks.info
You Plus C++ Plus Cocos2d-x
[ 16 ]
There is not much that needs to be done to the rst rectangle, the source one; but there is a
lot that can be changed in the desnaon rectangle, including its posion on the screen, its
size, opacity, rotaon, and so on.
Cocos2d-x will then take care of all the OpenGL drawing necessary to display your image
where you want it and how you want it, and it will do so inside a render loop. Your code
will most likely tap into that same loop to update its own logic.
Prey much any 2D game you can think of can be built with Cocos2d-x with a few sprites
and a loop.
In Version 3.x of the framework, there was a mild separaon between
Cocos2d-x and its counterpart Cocos2d. It dropped the prex CC in favor
of namespaces, embraced C++11 features, and became that much nicer
to work with because of it.
Containers
Also important in Cocos2d-x is the noon of containers (or nodes). These are all the
objects that can have sprites inside them (or other nodes.) This is extremely useful at mes
because by changing aspects of the container, you automacally change aspects of its
children. Move the container and all its children will move with it. Rotate the container and
well, you get the picture!
The containers are: Scene, Layer, and Sprite. They all inherit from a base container
class called node. Each container will have its peculiaries, but basically you will arrange
them as follows:
Scene: This will contain one or more Node, usually Layer types. It is common to
break applicaons into mulple scenes; for instance, one for the main menu, one
for sengs, and one for the actual game. Technically, each scene will behave as a
separate enty in your applicaon, almost as subapplicaons themselves, and you
can run a series of transion eects when changing between scenes.
Layer: This will most likely contain Sprite. There are a number of specialized
Layer objects aimed at saving you, the developer, some me in creang things such
as menus for instance (Menu), or a colored background (LayerColor). You can have
more than one Layer per scene, but good planning makes this usually unnecessary.
www.it-ebooks.info
Chapter 2
[ 17 ]
Sprite: This will contain your images and be added as children to Layer derived
containers. To my mind, this is the most important class in all of Cocos2d-x, so much
so, that aer your applicaon inializes, when both a Scene and a Layer object are
created, you could build your enre game only with sprites and never use another
container class in Cocos2d-x.
Node: This super class to all containers blurs the line between itself and
Layer, and even Sprite at mes. It has its own set of specialized subclasses
(besides the ones menoned earlier), such as MotionStreak, ParallaxNode,
and SpriteBatchNode, to name a few. It can, with a few adjustments, behave
just as Layer. But most of the me you will use it to create your own specialized
nodes or as a general reference in polymorphism.
The Director and cache classes
Aer containers comes the all-knowing Director and all-encompassing cache objects.
The Director object manages scenes and knows all about your applicaon. You will make
calls to it to get to that informaon and to change some of the things such as screen size,
frame rate, scale factor, and so forth.
The caches are collector objects. The most important ones are TextureCache,
SpriteFrameCache, and AnimationCache. These are responsible for storing key
informaon regarding those two important rectangles I menoned about earlier. But every
type of data that is used repeatedly in Cocos2d-x will be kept in some sort of cache list.
Both Director and all cache objects are singletons. These are special sort of classes that
are instanated only once; and this one instance can be accessed by any other object.
The other stuff
Aer the basic containers, the caches and the Director object, comes the remaining
90 percent of the framework. Among all this, you will nd:
Acons: Animaons will be handled through these and what a treat they are!
Parcles: Parcles systems for your delight.
Specialized nodes: For things such as menus, progress bars, special eects,
parallax eect, le maps, and much, much more.
The macros, structures, and helper methods: Hundreds of me-saving, magical bits
of logic. You don't need to know them all, but chances are that you will be coding
something that can be easily replaced by a macro or a helper method and feel
incredibly silly when you nd out about it later.
www.it-ebooks.info
You Plus C++ Plus Cocos2d-x
[ 18 ]
Do you know C++?
Don't worry, the C part is easy. The rst plus goes by really fast, but that second plus, oh boy!
Remember, it is C. And if you have coded in Objecve-C with the original Cocos2d, you know
good old C already even if you saw it in between brackets most of the me.
But C++ also has classes, just like Objecve-C, and these classes are declared in the interface
les just like in Objecve-C. So let's go over the creaon of a C++ class.
The class interface
This will be done in a .h le. We'll use a text editor to create this le since I don't want any
code hinng and autocompleon features geng in the way of you learning the basics of
C++ syntax. So for now at least, open up your favorite text editor. Let's create a class interface!
Time for action – creating the interface
The interface, or header le, is just a text le with the .h extension.
1. Create a new text le and save it as HelloWorld.h. Then, enter the following
lines at the top:
#ifndef __HELLOWORLD_H__
#define __HELLOWORLD_H__
#include "cocos2d.h"
2. Next, add the namespace declaraon:
using namespace cocos2d;
3. Then, declare your class name and the name of any inherited classes:
class HelloWorld : public cocos2d::Layer {
4. Next, we add the properes and methods:
protected:
int _score;
public:
HelloWorld();
virtual ~HelloWorld();
virtual bool init();
static cocos2d::Scene* scene();
CREATE_FUNC(HelloWorld);
void update(float dt);
inline int addTwoIntegers (int one, int two) {
www.it-ebooks.info
Chapter 2
[ 19 ]
return one + two;
}
};
5. We nish by closing the #ifndef statement:
#endif // __HELLOWORLD_H__
What just happened?
You created a header le in C++. Let's go over the important bits of informaon:
In C++ you include, you do not import. The import statement in Objecve-C checks
whether something needs to be included; include does not. But we accomplish
the same thing through that clever use of denions at the top. There are other
ways to run the same check (with #pragma once, for instance) but this one is
added to any new C++ les you create in Xcode.
You can make your life easier by declaring the namespaces you'll use in the class.
These are similar to packages in some languages. You may have noced that all
the uses of cocos2d:: in the code are not necessary because of the namespace
declaraon. But I wanted to show you the bit you can get rid of by adding a
namespace declaraon.
So next you give your class a name and you may choose to inherit from some other
class. In C++ you can have as many super classes as you want. And you must declare
whether your super class is public or not.
You declare your public, protected and private methods and members
between the curly braces. HelloWorld is the constructor and ~HelloWorld is the
destructor (it will do what dealloc does in Objecve-C).
The virtual keyword is related to overrides. When you mark a method as
virtual, you are telling the compiler not to set in stone the owner of the method,
but to keep it in memory as execuon will reveal the obvious owner. Otherwise, the
compiler may erroneously decide that a method belongs to the super and not its
inhering class.
Also, it's good pracce to make all your destructors virtual. You only need use
the keyword once in the super class to mark potenal overrides, but it is common
pracce to repeat the virtual keyword in all subclasses so developers know which
methods are overrides (C++11 adds a tag override, which makes this disncon
even clearer, and you will see examples of it in this book's code). In this case, init
comes from Layer and HelloWorld wants to override it.
virtual bool init();
Oh yes, in C++ you must declare overrides in your interfaces. No excepons!
www.it-ebooks.info
You Plus C++ Plus Cocos2d-x
[ 20 ]
The inline method is something new to you, probably. These methods are added to the code
by the compiler wherever they are called for. So every me I make a call to addTwoIntegers,
the compiler will replace it with the lines for the method declared in the interface. So the
inline method works just as statements inside a method; they do not require their own bit
of memory in the stack. But if you have a two-line inline method called 50 mes in your
program, it means that the compiler will add a hundred lines to your code.
The class implementation
This will be done in a .cpp le. So let's go back to our text editor and create the
implementaon for our HelloWorld class.
Time for action – creating the implementation
The implementaon is a text le with the .cpp extension:
1. Create a new text le and save it as HelloWorld.cpp. At the top, let's start by
including our header le:
#include "HelloWorld.h"
2. Next, we implement our constructor and destructor:
HelloWorld::HelloWorld () {
//constructor
}
HelloWorld::~HelloWorld () {
//destructor
}
3. Then comes our stac method:
Scene* HelloWorld::scene() {
auto scene = Scene::create();
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;
}
www.it-ebooks.info
Chapter 2
[ 21 ]
4. And then come our two remaining public methods:
bool HelloWorld::init() {
// call to super
if ( !Layer::init() )
{
return false;
}
//create main loop
this->scheduleUpdate();
return true;
}
void HelloWorld::update (float dt) {
//the main loop
}
What just happened?
We created the implementaon for our HelloWorld class. Here are the most important
bits to take noce of:
The HelloWorld:: scope resoluon is not oponal here. Every single method
declared in your interface belongs to the new class that needs the correct scope
resoluon in the implementaon le.
You also need the scope resoluon when calling the super class like
Layer::init(). There is no built-in super keyword in the standard C++ library.
You use this instead of self. The -> notaon is used when you're trying to access
an object's properes or methods through a pointer to the object (a pointer is the
informaon of where you nd the actual object in memory). The . (dot) notaon
is used to access an object's methods and properes through its actual instance
(the blob of memory that comprises the actual object).
We create an update loop, which takes a oat for its delta me value simply
by calling scheduleUpdate. You will see more opons related to this later in
this book.
You can use the auto keyword as the type of an object if it's obvious enough to
the compiler which type an object is.
The inline methods, of course, are not implemented in the class since they exist
only in the interface.
www.it-ebooks.info
You Plus C++ Plus Cocos2d-x
[ 22 ]
And that's enough of syntax for now. C++ is one of the most extensive languages out there
and I do not wish to leave you with the impression that I have covered all of it. But it is a
language made by developers for developers. Trust me, you will feel right at home working
with it.
The informaon listed previously will become clearer once we move on to building the
games. But now, onwards to the big scary monster: memory management.
Instantiating objects and managing memory
There is no Automac Reference Counng (ARC) in Cocos2d-x, so Objecve-C developers
who have forgoen memory management might have a problem here. However, the rule
regarding memory management is very simple with C++: if you use new, you must delete.
C++11 makes this even easier by introducing special pointers that are memory-managed
(these are std::unique_ptr and std::shared_ptr).
Cocos2d-x, however, will add a few other opons and commands to help with memory
management, similar to the ones we have in Objecve-C (without ARC). This is because
Cocos2d-x, unlike C++ and very much like Objecve-C, has a root class. The framework is
more than just a C++ port of Cocos2d. It also ports certain noons of Objecve-C to C++ in
order to recreate its memory-management system.
Cocos2d-x has a Ref class that is the root of every major object in the framework.
It allows the framework to have autorelease pools and retain counts, as well other
Objecve-C equivalents.
When instanang Cocos2d-x objects, you have basically two opons:
Using stac methods
The C++ and Cocos2d-x style
Using static methods
Using stac methods is the recommended way. The three-stage instanaon process of
Objecve-C, with alloc, init, and autorelease/retain, is recreated here. So, for
instance, a Player class, which extends Sprite, might have the following methods:
Player::Player () {
this->setPosition ( Vec2(0,0) );
}
Player* Player::create () {
auto player = new Player();
www.it-ebooks.info
Chapter 2
[ 23 ]
if (player && player->initWithSpriteFrameName("player.png")) {
player->autorelease();
return player;
}
CC_SAFE_DELETE(player);
return nullptr;
}
For instanaon, you call the stac create method. It will create a new Player object
as an empty husk version of Player. No major inializaon should happen inside the
constructor, just in case you may have to delete the object due to some failure in the
instanaon process. Cocos2d-x has a series of macros for object deleon and release,
like the CC_SAFE_DELETE macro used previously.
You then inialize the super through one of its available methods. In Cocos2d-x, these init
methods return a boolean value for success. You may now begin lling the Player object
with some data.
If successful, then inialize your object with its proper data if not done in the previous step,
and return it as an autorelease object.
So in your code the object would be instanated as follows:
auto player = Player::create();
this->addChild(player);//this will retain the object
Even if the player variable were a member of the class (say, m_player), you wouldn't
have to retain it to keep it in scope. By adding the object to some Cocos2d-x list or cache,
the object is automacally retained. So you may connue to address that memory through
its pointer:
m_player = Player::create();
this->addChild(m_player);//this will retain the object
//m_player still references the memory address
//but does not need to be released or deleted by you
The C++ and Cocos2d-x style
In this opon, you would instanate the previous Player object as follows:
auto player = new Player();
player->initWithSpriteFrameName("player.png");
this->addChild(player);
player->autorelease();
www.it-ebooks.info
You Plus C++ Plus Cocos2d-x
[ 24 ]
Player could do without a stac method in this case and the player pointer will not
access the same memory in future as it's set to be autoreleased (so it would not sck around
for long). In this case, however, the memory would not leak. It would sll be retained by a
Cocos2d-x list (the addChild command takes care of that). You can sll access that memory
by going over the children list added to this.
If you needed the pointer to be a member property you could use retain() instead of
autorelease():
m_player = new Player();
m_player->initWithSpriteFrameName("player.png");
this->addChild(m_player);
m_player->retain();
Then someme later, you would have to release it; otherwise, it will leak:
m_player->release();
Hardcore C++ developers may choose to forget all about the autorelease pool and simply
use new and delete:
Player * player = new Player();
player->initWithSpriteFrameName("player.png");
this->addChild(player);
delete player;//This will crash!
This will not work. You have to use autorelease, retain, or leave the previous code
without the delete command and hope there won't be any leak.
C++ developers must keep in mind that Ref is managed by the framework. This means
that objects are being internally added to caches and the autorelease pool even though
you may not want this to happen. When you create that Player sprite, for instance, the
player.png le you used will be added to the texture cache, or the sprite frame cache.
When you add the sprite to a layer, the sprite will be added to a list of all children of
that layer, and this list will be managed by the framework. My advice is, relax and let the
framework work for you.
Non-C++ developers should keep in mind that any class not derived from Ref should
be managed the usual way, that is, if you are creang a new object you must delete it at
some point:
MyObject* object = new MyObject();
delete object;
www.it-ebooks.info
Chapter 2
[ 25 ]
What you get with Ref
With Ref you get managed objects. This means that Ref derived objects will have a
reference count property, which will be used to determine whether an object should be
deleted from memory or not. The reference count is updated every me an object is added
or removed from a Cocos2d-x collecon object.
For instance, Cocos2d-x comes with a Vector collecon object that extends the
funconality of the C++ standard library vector (std::vector) by increasing and decreasing
the reference count when objects are added and removed from it. For that reason, it can
only store Ref derived objects.
Once again, every Ref derived class can be managed the way things used to be managed in
Objecve-C before ARC- with retain counts and autorelease pools.
C++, however, comes packed with its own wonderful dynamic list classes, similar to the
ones you would nd in Java and C#. But for Ref derived objects, you would probably be
best served by Cocos2d-x managed lists, or else remember to retain and release each object
when applicable. If you create a class which does not extend Ref and you need to store
instances of this class in a list container, then choose the standard library ones.
In the examples that follow in this book I will code primarily from within the framework,
so you will get to see plenty of examples of cocos2d::Vector being used, for instance,
but I will also use a std::vector instance or two in some of the games.
Summary
Hopefully, non-C++ developers have now learned that there is nothing to be feared from the
language, and hardcore C++ developers have not scoed too much at the noon of a root
class and its retains and autoreleases.
All the stu that root classes have brought to languages such as Java and Objecve-C will
forever be a moot point. The creepy, underlying operaons that go on behind your back
with root objects cannot be shut down or controlled. They are not oponal, and this forceful
nature of root objects has bothered C++ developers ever since noons such as garbage
collectors rst surfaced.
Having said that, memory management of Ref objects is extremely helpful and I hope even
the most distrusul developers will soon learn to be thankful for it.
Furthermore, Cocos2d-x is awesome. So let's create a game already!
www.it-ebooks.info
www.it-ebooks.info
[ 27 ]
3
Your First Game – Air Hockey
We are going to build an Air Hockey game to introduce you to all the main
aspects of building a project with Cocos2d-x. These include setting up the
project's configuration, loading images, loading sounds, building a game for
more than one screen resolution, and managing touch events.
Oh, and you will need to call a friend. This is a two player game. Go on,
I'll wait here.
By the end of this chapter, you will know:
How to build an iPad-only game
How to enable multouch
How to support both rena and non-rena displays
How to load images and sounds
How to play sound eects
How to create sprites
How to extend the Cocos2d-x Sprite class
How to create labels and update them
Without further ado...let's begin.
www.it-ebooks.info
Your First Game – Air Hockey
[ 28 ]
Game congurations
The game will have the following characteriscs:
It must support multouch since it's a two player game
It must be played on large screens since it's a two player game
It must support rena displays because we want to cash in on that
It must be played only in portrait mode because I built the art in portrait
So let's create our project!
Time for action – creating your game project
I'll build the game rst in Xcode and then show how to take the project to Eclipse, but
the folder structure remains the same, so you can work with any IDE you wish and the
instrucons here will be the same:
1. Open the terminal and create a new Cocos2d-x project called AirHockey that uses
C++ as its main language. I saved mine on the desktop, so the command I had to
enter looks like this:
cocos new AirHockey -p com.rengelbert.AirHockey -l cpp -d /Users/
rengelbert/Desktop/AirHockey
2. Once the project is created, navigate to its proj.ios_mac folder and double-click
on the AirHockey.xcodeproj le. (For Eclipse, you can follow the same steps we
did when we created the HelloWorld project to import the project.)
3. Select the top item in Project Navigator and making sure the iOS target is selected,
edit the informaon by navigang to General | Deployment info, seng the target
device to iPad and Device Orientaon to Portrait and Upside Down.
www.it-ebooks.info
Chapter 3
[ 29 ]
4. Save your project changes.
What just happened?
You created a Cocos2d-x project targeng iPads, and you are ready to set it up with the rest
of the conguraons I described earlier.
So let's do that now.
www.it-ebooks.info
Your First Game – Air Hockey
[ 30 ]
Time for action – laying down the rules
We'll update the RootViewController.mm le.
1. Go to RootViewController.mm inside the ios folder and look for the
shouldAutorotateToInterfaceOrientation method. Change the line
inside the method to read:
return UIInterfaceOrientationIsPortrait( interfaceOrientation );
2. And a few lines below in the supportedInterfaceOrientations method,
change the line inside the condional to:
return UIInterfaceOrientationMaskPortrait;
What just happened?
We just told RootViewController we want our applicaon to play in any of the two
supported portrait modes.
Supporting retina displays
Now let's add the images to our project.
Time for action – adding the image les
First, we download the resources for this project, and then we add them in Xcode.
1. Go to this book's Support page (www.packtpub.com/support) and download
the 4198_03_RESOURCES.zip le. Inside it, you should nd three folders called
hd, sd, and fonts.
2. Go to your Project folder, the actual folder in your system. Drag the three folders
to the Resources folder inside your project.
3. Go back to Xcode. Select the Resources folder in your project navigaon panel.
Then go to File | Add Files to AirHockey.
4. In the File window, navigate to the Resources folder and select the sd, hd,
and fonts folders.
5. This is very important: make sure Create folder references for any added folders is
selected. Also make sure you selected AirHockey as the target. It wouldn't hurt to
make sure Copy items to desnaon... is also selected.
6. Click on Add.
www.it-ebooks.info
Chapter 3
[ 31 ]
What just happened?
You added the necessary image les for your Air Hockey game. These come in two versions:
one for rena displays (high denion) and one for non-rena displays (standard denion).
It is very important that references are added to the actual folders, only this way Xcode will
be able to have two les with the same name inside the project and sll keep them apart;
one in each folder. We also added the font we'll be using in the game.
Now let's tell Cocos2d-x where to look for the correct les.
Time for action – adding retina support
This me we'll work with the class AppDelegate.cpp:
1. Go to AppDelegate.cpp (you'll nd it in the Classes folder). Inside the
applicationDidFinishLaunching method, and below the director-
>setAnimationInterval(1.0 / 60) line, add the following lines:
auto screenSize = glview->getFrameSize();
auto designSize = Size(768, 1024);
glview->setDesignResolutionSize(designSize.width, designSize.
height, ResolutionPolicy::EXACT_FIT);
std::vector<std::string> searchPaths;
if (screenSize.width > 768) {
searchPaths.push_back("hd");
director->setContentScaleFactor(2);
} else {
searchPaths.push_back("sd");
director->setContentScaleFactor(1);
}
auto fileUtils = FileUtils::getInstance();
fileUtils->setSearchPaths(searchPaths);
2. Save the le.
What just happened?
An enre book could be wrien about this topic, although in this rst example, we have a very
simple implementaon on how to support mulple screen sizes since we are only targeng
iPads. Here we are saying: "Hey AppDelegate, I designed this game for a 768 x 1024 screen."
www.it-ebooks.info
Your First Game – Air Hockey
[ 32 ]
All the values for posioning and font size were chosen for that screen size. If the screen is
larger, make sure you grab the les from the hd folder and change the scale by which you
will mulply all my posioning and font sizes. If the screen has the same size I designed
the game for, use the les in the sd folder and set the scale to 1. (Android adds even more
complexity to this, but we'll tackle that in later in the book.)
FileUtils will look for every le you load for your game rst inside Resources | sd
(or hd). If it doesn't nd them there, it will try to nd them in Resources. This is a good
thing because les shared by both versions may be added only once to the project, inside
Resources. That is what we'll do now with the sound les.
Adding sound effects
This game has two les for sound eects. You will nd them in the same .zip le you
downloaded previously.
Time for action – adding the sound les
Assuming you have the sound les from the downloaded resources, let's add them to
the project.
1. Drag both the .wav les to the Resources folder inside your Project folder.
2. Then go to Xcode, select the Resources folder in the le navigaon panel and
select File | Add Files to AirHockey.
3. Make sure the AirHockey target is selected.
4. Go to AppDelegate.cpp again. At the top, add this include statement:
#include "SimpleAudioEngine.h"
5. Then below the USING_NS_CC macro (for using namespace cocos2d), add:
using namespace CocosDenshion;
6. Then just below the lines you added in the previous secon, inside
applicationDidFinishLaunching, add the following lines:
auto audioEngine = SimpleAudioEngine::getInstance();
audioEngine->preloadEffect( fileUtils->fullPathForFilename("hit.
wav").c_str() );
audioEngine->preloadEffect( fileUtils->fullPathForFilename("score.
wav").c_str() );
audioEngine->setBackgroundMusicVolume(0.5f);
audioEngine->setEffectsVolume(0.5f);
www.it-ebooks.info
Chapter 3
[ 33 ]
What just happened?
With the preloadEffect method from CocosDenshion, you manage to preload the les
as well as instanate and inialize SimpleAudioEngine. This step will always take a toll on
your applicaon's processing power, so it's best to do it early on.
By now, the folder structure for your game should look like this:
www.it-ebooks.info
Your First Game – Air Hockey
[ 34 ]
Extending Sprite
No, there is nothing wrong with Sprite. I just picked a game where we need a bit more
informaon from some of its sprites. In this case, we want to store where a sprite is and
where it will be once the current iteraon of the game is completed. We will also need a
helper method to get the sprite's radius.
So let's create our GameSprite class.
Time for action – adding GameSprite.cpp
From here on, we'll create any new classes inside Xcode, but you could do it just as easily
in Eclipse if you remember to update the Make le. I'll show you how to do that later in
this chapter.
1. In Xcode, select the Classes folder and then go to File | New | File and navigate
to iOS | Source select C++ File.
2. Call it GameSprite and make sure the Also create a header le opon is selected.
3. Select the new GameSprite.h interface le and replace the code there with this:
#ifndef __GAMESPRITE_H__
#define __GAMESPRITE_H__
#include "cocos2d.h"
using namespace cocos2d;
class GameSprite : public Sprite {
public:
CC_SYNTHESIZE(Vec2, _nextPosition, NextPosition);
CC_SYNTHESIZE(Vec2, _vector, Vector);
CC_SYNTHESIZE(Touch*, _touch, Touch);
GameSprite();
virtual ~GameSprite();
static GameSprite* gameSpriteWithFile(const char*
pszFileName);
virtual void setPosition(const Vec2& pos) override;
float radius();
};
#endif // __GAMESPRITE_H__
www.it-ebooks.info
Chapter 3
[ 35 ]
What just happened?
In the interface, we declare the class to be a subclass of the public Sprite class.
Then we add three synthesized properes. In Cocos2d-x, these are macros to create geers
and seers. You declare the type, the protected variable name, and the words that will
be appended to the get and set methods. So in the rst CC_SYNTHESIZE method, the
getNextPosition and setNextPosition method will be created to deal with the Point
value inside the _nextPosition protected variable.
We also add the constructor and destructor for our class, and the ubiquitous stac method
for instanaon. This receives as a parameter, the image lename used by the sprite. We
nish o by overriding setPosition from Sprite and adding the declaraon for our
helper method radius.
The next step then is to implement our new class.
Time for action – implementing GameSprite
With the header out of the way, all we need to do is implement our methods.
1. Select the GameSprite.cpp le and let's start on the instanaon logic of
the class:
#include "GameSprite.h"
GameSprite::GameSprite(void){
_vector = Vec2(0,0);
}
GameSprite::~GameSprite(void){
}
GameSprite* GameSprite::gameSpriteWithFile(const char *
pszFileName) {
auto sprite = new GameSprite();
if (sprite && sprite->initWithFile(pszFileName)) {
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return sprite = nullptr;
}
www.it-ebooks.info
Your First Game – Air Hockey
[ 36 ]
2. Next we need to override the Node method setPosition. We need to make sure
that whenever we change the posion of the sprite, the new value is also used by
_nextPosition:
void GameSprite::setPosition(const Point& pos) {
Sprite::setPosition(pos);
if (!_nextPosition.equals(pos)) {
_nextPosition = pos;
}
}
3. And nally, we implement our new method to retrieve the radius of our sprite,
which we determine to be half its texture's width:
float GameSprite::radius() {
return getTexture()->getContentSize().width * 0.5f;
}
What just happened?
Things only begin happening in the stac method. We create a new GameSprite class, then
we call initWithFile on it. This is a GameSprite method inherited from its super class;
it returns a Boolean value for whether that operaon succeeded. The stac method ends by
returning an autorelease version of the GameSprite object.
The setPosition override makes sure _nextPosition receives the posion informaon
whenever the sprite is placed somewhere. And the helper radius method returns half of
the sprite's texture width.
Have a go hero
Change the radius method to an inline method in the interface and remove it from the
implementaon le.
The actual game scene
Finally, we'll get to see all our work and have some fun with it. But rst, let's delete the
HelloWorldScene class (both header and implementaon les). You'll get a few errors in
the project so let's x these.
References to the class must be changed at two lines in AppDelegate.cpp. Go ahead and
change the references to a GameLayer class.
We'll create that class next.
www.it-ebooks.info
Chapter 3
[ 37 ]
Time for action – coding the GameLayer interface
GameLayer is the main container in our game.
1. Follow the steps to add a new le to your Classes folder. This is a C++ le
called GameLayer.
2. Select your GameLayer.h. Just below the rst define preprocessor
command, add:
#define GOAL_WIDTH 400
3. We dene the width of the goals in pixels.
4. Next, add the declaraons for our sprites and our score text labels:
#include "cocos2d.h"
#include "GameSprite.h"
using namespace cocos2d;
class GameLayer : public Layer
{
GameSprite* _player1;
GameSprite* _player2;
GameSprite* _ball;
Vector<GameSprite*> _players;
Label* _player1ScoreLabel;
Label* _player2ScoreLabel;
We have the GameSprite objects for two players (the weird looking things called
mallets), and the ball (called a puck). We'll store the two players in a Cocos2d-x
Vector. We also have two text labels to display the score for each player.
5. Declare a variable to store the screen size. We'll use this a lot for posioning:
Size _screenSize;
6. Add variables to store the score informaon and a method to update these scores
on screen:
int _player1Score;
int _player2Score;
void playerScore (int player);
www.it-ebooks.info
Your First Game – Air Hockey
[ 38 ]
7. Finally, let's add our methods:
public:
GameLayer();
virtual ~GameLayer();
virtual bool init();
static Scene* scene();
CREATE_FUNC(GameLayer);
void onTouchesBegan(const std::vector<Touch*> &touches,
Event* event);
void onTouchesMoved(const std::vector<Touch*> &touches,
Event* event);
void onTouchesEnded(const std::vector<Touch*> &touches,
Event* event);
void update (float dt);
};
#endif // __GAMELAYER_H__
There are constructor and destructor methods, then the Layer init methods, and nally
the event handlers for the touch events and our loop method called update. These touch
event handlers will be added to our class to handle when users' touches begin, when they
move across the screen, and when they end.
What just happened?
GameLayer is our game. It contains references to all the sprites we need to control and
update, as well as all game data.
In the class implementaon, all the logic starts inside the init method.
Time for action – implementing init()
Inside init(), we'll build the game screen, bringing in all the sprites and labels we'll need
for the game:
1. So right aer the if statement where we call the super Layer::init method,
we add:
_players = Vector<GameSprite*>(2);
_player1Score = 0;
_player2Score = 0;
_screenSize = Director::getInstance()->getWinSize();
www.it-ebooks.info
Chapter 3
[ 39 ]
2. We create the vector where we'll store both players, inialize the score values,
and grab the screen size from the singleton, all-knowing Director. We'll use the
screen size to posion all sprites relavely. Next we will create our rst sprite. It is
created with an image lename, which FileUtils will take care of loading from
the correct folder:
auto court = Sprite::create("court.png");
court->setPosition(Vec2(_screenSize.width * 0.5, _screenSize.
height * 0.5));
this->addChild(court);
3. Get into the habit of posioning sprites with relave values, and not absolute ones,
so we can support more screen sizes. And say hello to the Vec2 type denion used
to create points; you'll be seeing it a lot in Cocos2d-x.
4. We nish by adding the sprite as a child to our GameLayer (the court sprite does
not need to be a GameSprite).
5. Next we will use our spanking new GameSprite class, carefully posioning the
objects on screen:
_player1 = GameSprite::gameSpriteWithFile("mallet.png");
_player1->setPosition(Vec2(_screenSize.width * 0.5,
_player1->radius() * 2));
_players.pushBack(_player1);
this->addChild(_player1);
_player2 = GameSprite::gameSpriteWithFile("mallet.png");
_player2->setPosition(Vec2(_screenSize.width * 0.5, _screenSize.
height - _player1->radius() * 2));
_players.pushBack(_player2);
this->addChild(_player2);
_ball = GameSprite::gameSpriteWithFile("puck.png");
_ball->setPosition(Vec2(_screenSize.width * 0.5, _screenSize.
height * 0.5 - 2 * _ball->radius()));
this->addChild(_ball);
6. We will create TTF labels with the Label class createWithTTF stac method,
passing as parameters the inial string value (0), and the path to the font le.
We will then posion and rotate the labels:
_player1ScoreLabel = Label::createWithTTF("0",
"fonts/Arial.ttf", 60);
_player1ScoreLabel->setPosition(Vec2(_screenSize.width - 60,
_screenSize.height * 0.5 - 80));
_player1ScoreLabel->setRotation(90);
this->addChild(_player1ScoreLabel);
www.it-ebooks.info
Your First Game – Air Hockey
[ 40 ]
_player2ScoreLabel = Label::createWithTTF("0",
"fonts/Arial.ttf", 60);
_player2ScoreLabel->setPosition(Vec2(_screenSize.width - 60,
_screenSize.height * 0.5 + 80));
_player2ScoreLabel->setRotation(90);
this->addChild(_player2ScoreLabel);
7. Then we turn GameLayer into a multouch event listener and tell the Director
event dispatcher that GameLayer wishes to listen to those events. And we nish by
scheduling the game's main loop as follows:
auto listener = EventListenerTouchAllAtOnce::create();
listener->onTouchesBegan =
CC_CALLBACK_2(GameLayer::onTouchesBegan, this);
listener->onTouchesMoved =
CC_CALLBACK_2(GameLayer::onTouchesMoved, this);
listener->onTouchesEnded =
CC_CALLBACK_2(GameLayer::onTouchesEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,
this);
//create main loop
this->scheduleUpdate();
return true;
What just happened?
You created the game screen for Air Hockey, with your own sprites and labels. The game
screen, once all elements are added, should look like this:
And now we're ready to handle the player's screen touches.
www.it-ebooks.info
Chapter 3
[ 41 ]
Time for action – handling multitouches
There are three methods we need to implement in this game to handle touches.
Each method receives, as one of its parameters, a vector of Touch objects:
1. So add our onTouchesBegan method:
void GameLayer::onTouchesBegan(const std::vector<Touch*> &touches,
Event* event)
{
for( auto touch : touches) {
if(touch != nullptr) {
auto tap = touch->getLocation();
for (auto player : _players) {
if (player->boundingBox().containsPoint(tap)) {
player->setTouch(touch);
}
}
}
}
}
Each GameSprite, if you recall, has a _touch property.
So we iterate through the touches, grab their locaon on screen, loop through
the players in the vector, and determine if the touch lands on one of the players.
If so, we store the touch inside the player's _touch property (from the
GameSprite class).
A similar process is repeated for onTouchesMoved and onTouchesEnded, so you
can copy and paste the code and just replace what goes on inside the _players
array for loop.
2. In TouchesMoved, when we loop through the players, we do this:
for (auto player : _players) {
if (player->getTouch() != nullptr && player->getTouch() ==
touch) {
Point nextPosition = tap;
if (nextPosition.x < player->radius())
nextPosition.x = player->radius();
if (nextPosition.x > _screenSize.width - player->radius())
nextPosition.x = _screenSize.width - player->radius();
if (nextPosition.y < player->radius())
www.it-ebooks.info
Your First Game – Air Hockey
[ 42 ]
nextPosition.y = player->radius();
if (nextPosition.y > _screenSize.height - player->radius())
nextPosition.y = _screenSize.height - player->radius();
//keep player inside its court
if (player->getPositionY() < _screenSize.height* 0.5f) {
if (nextPosition.y > _screenSize.height* 0.5 -
player->radius()) {
nextPosition.y = _screenSize.height* 0.5 -
player->radius();
}
} else {
if (nextPosition.y < _screenSize.height* 0.5 +
player->radius()) {
nextPosition.y = _screenSize.height* 0.5 +
player->radius();
}
}
player->setNextPosition(nextPosition);
player->setVector(Vec2(tap.x - player->getPositionX(),
tap.y - player->getPositionY()));
}
}
We check to see if the _touch property stored inside the player is the being moved
now. If so, we update the player's posion with the touch's current posion, but
we check to see if the new posion is valid: a player cannot move outside the
screen and cannot enter its opponent's court. We also update the player's vector of
movement; we'll need this when we collide the player with the puck. The vector is
based on the player's displacement.
3. In onTouchesEnded, we add this:
for (auto player : _players) {
if (player->getTouch() != nullptr && player->getTouch() ==
touch) {
//if touch ending belongs to this player, clear it
player->setTouch(nullptr);
player->setVector(Vec2(0,0));
}
}
We clear the _touch property stored inside the player if this touch is the one just ending.
The player also stops moving, so its vector is set to 0. Noce that we don't need the locaon
of the touch anymore; so in TouchesEnded you can skip that bit of logic.
www.it-ebooks.info
Chapter 3
[ 43 ]
What just happened?
When you implement logic for multouch this is prey much what you will have to do:
store the individual touches inside either an array or individual sprites, so you can keep
tracking these touches.
Now, for the heart and soul of the game—the main loop.
Time for action – adding our main loop
This is the heart of our game—the update method:
1. We will update the puck's velocity with a lile fricon applied to its vector
(0.98f). We will store what its next posion will be at the end of the iteraon,
if no collision occurred:
void GameLayer::update (float dt) {
auto ballNextPosition = _ball->getNextPosition();
auto ballVector = _ball->getVector();
ballVector *= 0.98f;
ballNextPosition.x += ballVector.x;
ballNextPosition.y += ballVector.y;
2. Next comes the collision. We will check collisions with each player sprite
and the ball:
float squared_radii = pow(_player1->radius() +
_ball->radius(), 2);
for (auto player : _players) {
auto playerNextPosition = player->getNextPosition();
auto playerVector = player->getVector();
float diffx = ballNextPosition.x - player->getPositionX();
float diffy = ballNextPosition.y - player->getPositionY();
float distance1 = pow(diffx, 2) + pow(diffy, 2);
float distance2 = pow(_ball->getPositionX() -
playerNextPosition.x, 2) + pow(_ball->getPositionY() -
playerNextPosition.y, 2);
www.it-ebooks.info
Your First Game – Air Hockey
[ 44 ]
Collisions are checked through the distance between ball and players.
Two condions will ag a collision, as illustrated in the following diagram:
3. If the distance between ball and player equals the sum of the radii of both sprites,
or is less than the sum of the radii of both sprites, we have a collision:
if (distance1 <= squared_radii ||
distance2 <= squared_radii) {
4. We use the squared radii values so we don't need to use costly square root
calculaons to get the values for distance. So all values in the previous condional
statement are squared, including the distances.
5. These condions are checked both with the player's current posion and its
next posion, so there is less risk of the ball moving "through" the player sprite
between iteraons.
6. If there is a collision, we grab the magnitudes of both the ball's vector and
the player's vector, and make the force with which the ball will be pushed
away. We update the ball's next posion in that case, and play a nice sound
eect through the SimpleAudioEngine singleton (don't forget to include
the SimpleAudioEngine.h header le and declare we're using the
CocosDenshion namespace):
float mag_ball = pow(ballVector.x, 2) + pow(ballVector.y,
2);
float mag_player = pow(playerVector.x, 2) + pow
(playerVector.y, 2);
www.it-ebooks.info
Chapter 3
[ 45 ]
float force = sqrt(mag_ball + mag_player);
float angle = atan2(diffy, diffx);
ballVector.x = force * cos(angle);
ballVector.y = (force * sin(angle));
ballNextPosition.x = playerNextPosition.x + (player-
>radius() + _ball->radius() + force) * cos(angle);
ballNextPosition.y = playerNextPosition.y + (player-
>radius() + _ball->radius() + force) * sin(angle);
SimpleAudioEngine::getInstance()-
>playEffect("hit.wav");
}
}
7. Next, we will check the collision between the ball and screen sides. If so, we will
move the ball back to the court and play our sound eect here as well:
if (ballNextPosition.x < _ball->radius()) {
ballNextPosition.x = _ball->radius();
ballVector.x *= -0.8f;
SimpleAudioEngine::getInstance()->playEffect("hit.wav");
}
if (ballNextPosition.x > _screenSize.width - _ball->radius()) {
ballNextPosition.x = _screenSize.width - _ball->radius();
ballVector.x *= -0.8f;
SimpleAudioEngine::getInstance()->playEffect("hit.wav");
}
8. At the top and boom sides of the court, we check to see whether the ball has
not moved through one of the goals through our previously dened GOAL_WIDTH
property as follows:
if (ballNextPosition.y > _screenSize.height - _ball->radius()) {
if (_ball->getPosition().x < _screenSize.width * 0.5f -
GOAL_WIDTH * 0.5f || _ball->getPosition().x >
_screenSize.width * 0.5f + GOAL_WIDTH * 0.5f) {
ballNextPosition.y = _screenSize.height - _ball-
>radius();
ballVector.y *= -0.8f;
SimpleAudioEngine::getInstance()-
>playEffect("hit.wav");
}
}
www.it-ebooks.info
Your First Game – Air Hockey
[ 46 ]
if (ballNextPosition.y < _ball->radius() ) {
if (_ball->getPosition().x < _screenSize.width * 0.5f -
GOAL_WIDTH * 0.5f || _ball->getPosition().x >
_screenSize.width * 0.5f + GOAL_WIDTH * 0.5f) {
ballNextPosition.y = _ball->radius();
ballVector.y *= -0.8f;
SimpleAudioEngine::getInstance()-
>playEffect("hit.wav");
}
}
9. We nally update the ball informaon, and if the ball has passed through the goal
posts (drum roll):
_ball->setVector(ballVector);
_ball->setNextPosition(ballNextPosition);
//check for goals!
if (ballNextPosition.y < -_ball->radius() * 2) {
this->playerScore(2);
}
if (ballNextPosition.y > _screenSize.height + _ball->radius() * 2)
{
this->playerScore(1);
}
10. We call our helper method to score a point and we nish the update with the
placement of all the elements, now that we know where the nextPosition
value is for each one of the elements in the game:
_player1->setPosition(_player1->getNextPosition());
_player2->setPosition(_player2->getNextPosition());
_ball->setPosition(_ball->getNextPosition());
What just happened?
We have just built the game's main loop. Whenever your gameplay depends on precise
collision detecon, you will undoubtedly apply a similar logic of posion now, posion next,
collision checks, and adjustments to posion next, if a collision has occurred. And we nish
the game with our helper method.
All that's le to do now is update the scores.
www.it-ebooks.info
Chapter 3
[ 47 ]
Time for action – updating scores
Time to type the last method in the game.
1. We start by playing a nice eect for a goal and stopping our ball:
void GameLayer::playerScore (int player) {
SimpleAudioEngine::getInstance()->playEffect("score.wav");
_ball->setVector(Vec2(0,0));
2. Then we update the score for the scoring player, updang the score label in the
process. And the ball moves to the court of the player against whom a point was
just scored:
char score_buffer[10];
if (player == 1) {
_player1Score++;
_player1ScoreLabel->setString(std::to_string(_player1Score));
_ball->setNextPosition(Vec2(_screenSize.width * 0.5,
_screenSize.height * 0.5 + 2 * _ball->radius()));
} else {
_player2Score++;
_player2ScoreLabel->setString(std::to_string(_player2Score));
_ball->setNextPosition(Vec2(_screenSize.width * 0.5,
_screenSize.height * 0.5 - 2 * _ball->radius()));
}
The players are moved to their original posion and their _touch properes
are cleared:
_player1->setPosition(Vec2(_screenSize.width * 0.5,
_player1->radius() * 2));
_player2->setPosition(Vec2(_screenSize.width * 0.5,
_screenSize.height - _player1->radius() * 2));
_player1->setTouch(nullptr);
_player2->setTouch(nullptr);
}
www.it-ebooks.info
Your First Game – Air Hockey
[ 48 ]
What just happened?
Well, guess what! You just nished your rst game in Cocos2d-x. We charged forward at
a brisk pace for our rst game, but we managed to touch on almost every area of game
development with Cocos2d-x in the process.
If you click Run now, you should be able to play the game. In the source code for this chapter,
you should also nd the complete version of the game if you run into any problems.
Time to take this to Android!
Time for action – running the game in Android
Time to deploy the game to Android.
1. Follow the instrucons from the HelloWorld example to import the game
into Eclipse.
2. Navigate to the proj.android folder and open the AndroidManifest.xml le in a
text editor. Then, go to the jni folder and open the Android.mk le in a text editor.
3. In the AndroidManifest.xml le, edit the following line in the activity tag:
android:screenOrientation="portrait"
4. And it's possible to target only tablets by adding these lines in the supports-
screens tag:
<supports-screens android:anyDensity="true"
android:smallScreens="false"
android:normalScreens="false"
android:largeScreens="true"
android:xlargeScreens="true"/>
5. Although if you want to target only tablets, you might also wish to target the later
versions of SDK, like this:
<uses-sdk android:minSdkVersion="11"/>
6. Next, let's edit the make le, so open the Android.mk le and edit the lines in
LOCAL_SRC_FILES to read:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/GameSprite.cpp \
../../Classes/GameLayer.cpp
7. Save it and run your applicaon (don't forget to connect an Android device,
in this case, a tablet if you used the sengs as explained here).
www.it-ebooks.info
Chapter 3
[ 49 ]
What just happened?
And that's it! You can edit these les inside Eclipse as well.
When you build a Cocos2d-x project in the command line, you see a message saying that the
hellocpp target is being renamed. But I think this is sll a bug in the build script and usually
correcng that in the make le and the folder structure creates a much bigger headache. So
for now, sck to the strangely named hellocpp in Android.mk.
Have a go hero
Make any changes to the code. For instance, add an extra label and then publish again from
Eclipse. You may nd that working with the project in this IDE is faster than Xcode.
Sadly, sooner or later, Eclipse will throw one of its infamous tantrums. A common problem
that occurs if you have many projects open in your navigator is for one or many of the
projects to report an error like Cannot nd the class le for java.lang.Object or The type
java.lang.Object cannot be resolved. Get into the habit of cleaning your project and building
it as soon as you open Eclipse and keeping only acve projects opened, but even that might
fail you. The soluon? Restart Eclipse, or beer yet, delete the project from the navigator
(but not from the disk!) and reimport it. Yeah, I know. Welcome to Eclipse!
Summary
You now know how to add sprites and labels, and how to add support for two screen
resoluons as well as support for multouch. There are quite a few ways to create sprites
other than by passing it an image lename, and I'll show examples of these in the games
to come.
LabelTTF won't be used as much in this book. Generally, they are good for large chunks of
text and text that is not updated too frequently; we'll use bitmap fonts from now on.
So, let's move on to the next game and animaons. I promise I won't make you type as
much. You should get your friend to do it for you!
www.it-ebooks.info
www.it-ebooks.info
[ 51 ]
Fun with Sprites – Sky Defense
Time to build our second game! This time, you will get acquainted with the
power of actions in Cocos2d-x. I'll show you how an entire game could be built
just by running the various action commands contained in Cocos2d-x to make
your sprites move, rotate, scale, fade, blink, and so on. And you can also use
actions to animate your sprites using multiple images, like in a movie. So let's
get started.
In this chapter, you will learn:
How to opmize the development of your game with sprite sheets
How to use bitmap fonts in your game
How easy it is to implement and run acons
How to scale, rotate, swing, move, and fade out a sprite
How to load mulple .png les and use them to animate a sprite
How to create a universal game with Cocos2d-x
The game – sky defense
Meet our stressed-out city of...your name of choice here. It's a beauful day when
suddenly the sky begins to fall. There are meteors rushing toward the city and it is your
job to keep it safe.
4
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 52 ]
The player in this game can tap the screen to start growing a bomb. When the bomb is big
enough to be acvated, the player taps the screen again to detonate it. Any nearby meteor
will explode into a million pieces. The bigger the bomb, the bigger the detonaon, and the
more meteors can be taken out by it. But the bigger the bomb, the longer it takes to grow it.
But it's not just bad news coming down. There are also health packs dropping from the sky
and if you allow them to reach the ground, you'll recover some of your energy.
The game settings
This is a universal game. It is designed for the iPad rena screen and it will be scaled down to
t all the other screens. The game will be played in landscape mode, and it will not need to
support multouch.
The start project
Go ahead and download the le 4198_04_START_PROJECT.zip from this book's support
page (www.packtpub.com/support). When you uncompress the le, you will nd the
basic project already set up and ready for you to work on.
The steps involved in creang this project are similar to the ones I showed you in our
previous game. The command line I used was:
cocos new SkyDefense -p com.rengelbert.SkyDefense -l cpp -d /Users/
rengelbert/Desktop/SkyDefense
In Xcode you must set the Devices eld in Deployment Info to Universal, and the Device
Family eld is set to Universal. And in RootViewController.mm, the supported interface
orientaon is set to Landscape.
The game we are going to build requires only one class, GameLayer.cpp, and you will nd
that the interface for this class already contains all the informaon it needs.
Also, some of the more trivial or old-news logic is already in place in the implementaon le
as well. But I'll go over this as we work on the game.
Adding screen support for a universal app
In the previous game, we targeted iPad size screens only. Now things get a bit more
complicated as we add support for smaller screens in our universal game, as well as some
of the most common Android screen sizes.
www.it-ebooks.info
Chapter 4
[ 53 ]
So open AppDelegate.cpp. Inside the applicationDidFinishLaunching method,
we now have the following code:
auto screenSize = glview->getFrameSize();
auto designSize = Size(2048, 1536);
glview->setDesignResolutionSize(designSize.width, designSize.height,
ResolutionPolicy::EXACT_FIT);
std::vector<std::string> searchPaths;
if (screenSize.height > 768) {
searchPaths.push_back("ipadhd");
director->setContentScaleFactor(1536/designSize.height);
} else if (screenSize.height > 320) {
searchPaths.push_back("ipad");
director->setContentScaleFactor(768/designSize.height);
} else {
searchPaths.push_back("iphone");
director->setContentScaleFactor(380/designSize.height);
}
auto fileUtils = FileUtils::getInstance();
fileUtils->setSearchPaths(searchPaths);
Once again, we tell our GLView object (our OpenGL view) that we designed the game for
a certain screen size (the iPad rena screen) and once again, we want our game screen to
resize to match the screen on the device (ResolutionPolicy::EXACT_FIT).
Then we determine where to load our images from, based on the device's screen size.
We have art for iPad rena, then for regular iPad which is shared by iPhone rena,
and for the regular iPhone.
We end by seng the scale factor based on the designed target.
Adding background music
Sll inside AppDelegate.cpp, we load the sound les we'll use in the game, including a
background.mp3 (courtesy of Kevin MacLeod from incompetech.com), which we load
through the command:
auto audioEngine = SimpleAudioEngine::getInstance();
audioEngine->preloadBackgroundMusic(fileUtils->fullPathForFilename("backg
round.mp3").c_str());
We end by seng the eects' volume down a tad:
//lower playback volume for effects
audioEngine->setEffectsVolume(0.4f);
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 54 ]
For background music volume, you must use setBackgroundMusicVolume. If you create
some sort of volume control in your game, these are the calls you would make to adjust the
volume based on the user's preference.
Initializing the game
Now back to GameLayer.cpp. If you take a look inside our init method, you will see that
the game inializes by calling three methods: createGameScreen, createPools, and
createActions.
We'll create all our screen elements inside the rst method, and then create object pools
so we don't instanate any sprite inside the main loop; and we'll create all the main acons
used in our game inside the createActions method.
And as soon as the game inializes, we start playing the background music, with its should
loop parameter set to true:
SimpleAudioEngine::getInstance()-
>playBackgroundMusic("background.mp3", true);
We once again store the screen size for future reference, and we'll use a _running Boolean
for game states.
If you run the game now, you should only see the background image:
Using sprite sheets in Cocos2d-x
A sprite sheet is a way to group mulple images together in one image le. In order to
texture a sprite with one of these images, you must have the informaon of where in the
sprite sheet that parcular image is found (its rectangle).
Sprite sheets are oen organized in two les: the image one and a data le that describes
where in the image you can nd the individual textures.
www.it-ebooks.info
Chapter 4
[ 55 ]
I used TexturePacker to create these les for the game. You can nd them inside the
ipad, ipadhd, and iphone folders inside Resources. There is a sprite_sheet.png le
for the image and a sprite_sheet.plist le that describes the individual frames inside
the image.
This is what the sprite_sheet.png le looks like:
Batch drawing sprites
In Cocos2d-x, sprite sheets can be used in conjuncon with a specialized node, called
SpriteBatchNode. This node can be used whenever you wish to use mulple sprites that
share the same source image inside the same node. So you could have mulple instances of
a Sprite class that uses a bullet.png texture for instance. And if the source image is a
sprite sheet, you can have mulple instances of sprites displaying as many dierent textures
as you could pack inside your sprite sheet.
With SpriteBatchNode, you can substanally reduce the number of calls during the
rendering stage of your game, which will help when targeng less powerful systems,
though not noceably in more modern devices.
Let me show you how to create a SpriteBatchNode.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 56 ]
Time for action – creating SpriteBatchNode
Let's begin implemenng the createGameScreen method in GameLayer.cpp. Just below
the lines that add the bg sprite, we instanate our batch node:
void GameLayer::createGameScreen() {
//add bg
auto bg = Sprite::create("bg.png");
...
SpriteFrameCache::getInstance()->
addSpriteFramesWithFile("sprite_sheet.plist");
_gameBatchNode = SpriteBatchNode::create("sprite_sheet.png");
this->addChild(_gameBatchNode);
In order to create the batch node from a sprite sheet, we rst load all the frame informaon
described by the sprite_sheet.plist le into SpriteFrameCache. And then we create
the batch node with the sprite_sheet.png le, which is the source texture shared by all
sprites added to this batch node. (The background image is not part of the sprite sheet, so
it's added separately before we add _gameBatchNode to GameLayer.)
Now we can start pung stu inside _gameBatchNode.
1. First, the city:
for (int i = 0; i < 2; i++) {
auto sprite = Sprite::createWithSpriteFrameName
("city_dark.png");
sprite->setAnchorPoint(Vec2(0.5,0));
sprite->setPosition(_screenSize.width * (0.25f + i *
0.5f),0));
_gameBatchNode->addChild(sprite, kMiddleground);
sprite = Sprite::createWithSpriteFrameName
("city_light.png");
sprite->setAnchorPoint(Vec2(0.5,0));
sprite->setPosition(Vec2(_screenSize.width * (0.25f + i *
0.5f),
_screenSize.height * 0.1f));
_gameBatchNode->addChild(sprite, kBackground);
}
www.it-ebooks.info
Chapter 4
[ 57 ]
2. Then the trees:
//add trees
for (int i = 0; i < 3; i++) {
auto sprite = Sprite::createWithSpriteFrameName("trees.png");
sprite->setAnchorPoint(Vec2(0.5f, 0.0f));
sprite->setPosition(Vec2(_screenSize.width * (0.2f + i *
0.3f),0));
_gameBatchNode->addChild(sprite, kForeground);
}
Noce that here we create sprites by passing it a sprite frame name. The IDs
for these frame names were loaded into SpriteFrameCache through our
sprite_sheet.plist le.
3. The screen so far is made up of two instances of city_dark.png ling at
the boom of the screen, and two instances of city_light.png also ling.
One needs to appear on top of the other and for that we use the enumerated
values declared in GameLayer.h:
enum {
kBackground,
kMiddleground,
kForeground
};
4. We use the addChild( Node, zOrder) method to layer our sprites on top of
each other, using dierent values for their z order.
So for example, when we later add three sprites showing the trees.png sprite
frame, we add them on top of all previous sprites using the highest value for z that
we nd in the enumerated list, which is kForeground.
Why go through the trouble of ling the images and not using one large
image instead, or combining some of them with the background image?
Because I wanted to include the greatest number of images possible inside
the one sprite sheet, and have that sprite sheet to be as small as possible, to
illustrate all the clever ways you can use and opmize sprite sheets. This is
not necessary in this parcular game.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 58 ]
What just happened?
We began creang the inial screen for our game. We are using a SpriteBatchNode
to contain all the sprites that use images from our sprite sheet. So SpriteBatchNode
behaves as any node does—as a container. And we can layer individual sprites inside the
batch node by manipulang their z order.
Bitmap fonts in Cocos2d-x
The Cocos2d-x Label class has a stac create method that uses bitmap images for
the characters.
The bitmap image we are using here was created with the program GlyphDesigner,
and in essence, it works just as a sprite sheet does. As a maer of fact, Label extends
SpriteBatchNode, so it behaves just like a batch node.
You have images for all individual characters you'll need packed inside a PNG le
(font.png), and then a data le (font.fnt) describing where each character is.
The following screenshot shows how the font sprite sheet looks like for our game:
The dierence between Label and a regular SpriteBatchNode class is that the data le
also feeds the Label object informaon on how to write with this font. In other words,
how to space out the characters and lines correctly.
The Label objects we are using in the game are instanated with the name of the data le
and their inial string value:
_scoreDisplay = Label::createWithBMFont("font.fnt", "0");
And the value for the label is changed through the setString method:
_scoreDisplay->setString("1000");
www.it-ebooks.info
Chapter 4
[ 59 ]
Just as with every other image in the game, we also have dierent versions
of font.fnt and font.png in our Resources folders, one for each
screen denion. FileUtils will once again do the heavy liing of
nding the correct le for the correct screen.
So now let's create the labels for our game.
Time for action – creating bitmap font labels
Creang a bitmap font is somewhat similar to creang a batch node.
1. Connuing with our createGameScreen method, add the following lines to the
score label:
_scoreDisplay = Label::createWithBMFont("font.fnt", "0");
_scoreDisplay->setAnchorPoint(Vec2(1,0.5));
_scoreDisplay->setPosition(Vec2
(_screenSize.width * 0.8f, _screenSize.height * 0.94f));
this->addChild(_scoreDisplay);
And then add a label to display the energy level, and set its horizontal alignment
to Right:
_energyDisplay = Label::createWithBMFont("font.fnt", "100%",
TextHAlignment::RIGHT);
_energyDisplay->setPosition(Vec2
(_screenSize.width * 0.3f, _screenSize.height * 0.94f));
this->addChild(_energyDisplay);
2. Add the following line for an icon that appears next to the _energyDisplay label:
auto icon = Sprite::createWithSpriteFrameName
("health_icon.png");
icon->setPosition( Vec2(_screenSize.
width * 0.15f, _screenSize.height * 0.94f) );
_gameBatchNode->addChild(icon, kBackground);
What just happened?
We just created our rst bitmap font object in Cocos2d-x. Now let's nish creang our
game's sprites.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 60 ]
Time for action – adding the nal screen sprites
The last sprites we need to create are the clouds, the bomb and shockwave, and our game
state messages.
1. Back to the createGameScreen method, add the clouds to the screen:
for (int i = 0; i < 4; i++) {
float cloud_y = i % 2 == 0 ? _screenSize.height * 0.4f : _
screenSize.height * 0.5f;
auto cloud = Sprite::createWithSpriteFrameName("cloud.png");
cloud->setPosition(Vec2 (_screenSize.width * 0.1f + i * _
screenSize.width * 0.3f, cloud_y));
_gameBatchNode->addChild(cloud, kBackground);
_clouds.pushBack(cloud);
}
2. Create the _bomb sprite; players will grow when tapping the screen:
_bomb = Sprite::createWithSpriteFrameName("bomb.png");
_bomb->getTexture()->generateMipmap();
_bomb->setVisible(false);
auto size = _bomb->getContentSize();
//add sparkle inside bomb sprite
auto sparkle = Sprite::createWithSpriteFrameName("sparkle.png");
sparkle->setPosition(Vec2(size.width * 0.72f, size.height *
0.72f));
_bomb->addChild(sparkle, kMiddleground, kSpriteSparkle);
//add halo inside bomb sprite
auto halo = Sprite::createWithSpriteFrameName
("halo.png");
halo->setPosition(Vec2(size.width * 0.4f, size.height *
0.4f));
_bomb->addChild(halo, kMiddleground, kSpriteHalo);
_gameBatchNode->addChild(_bomb, kForeground);
3. Then create the _shockwave sprite that appears aer the _bomb goes o:
_shockWave = Sprite::createWithSpriteFrameName
("shockwave.png");
_shockWave->getTexture()->generateMipmap();
_shockWave->setVisible(false);
_gameBatchNode->addChild(_shockWave);
www.it-ebooks.info
Chapter 4
[ 61 ]
4. Finally, add the two messages that appear on the screen, one for our intro state
and one for our gameover state:
_introMessage = Sprite::createWithSpriteFrameName
("logo.png");
_introMessage->setPosition(Vec2
(_screenSize.width * 0.5f, _screenSize.height * 0.6f));
_introMessage->setVisible(true);
this->addChild(_introMessage, kForeground);
_gameOverMessage = Sprite::createWithSpriteFrameName
("gameover.png");
_gameOverMessage->setPosition(Vec2
(_screenSize.width * 0.5f, _screenSize.height * 0.65f));
_gameOverMessage->setVisible(false);
this->addChild(_gameOverMessage, kForeground);
What just happened?
There is a lot of new informaon regarding sprites in the previous code. So let's go over
it carefully:
We started by adding the clouds. We put the sprites inside a vector so we can move
the clouds later. Noce that they are also part of our batch node.
Next comes the bomb sprite and our rst new call:
_bomb->getTexture()->generateMipmap();
With this we are telling the framework to create analiased copies of this texture
in diminishing sizes (mipmaps), since we are going to scale it down later. This is
oponal of course; sprites can be resized without rst generang mipmaps, but if
you noce loss of quality in your scaled sprites, you can x that by creang mipmaps
for their texture.
The texture must have size values in so-called POT (power of 2: 2, 4, 8, 16, 32,
64, 128, 256, 512, 1024, 2048, and so on). Textures in OpenGL must always be
sized this way; when they are not, Cocos2d-x will do one of two things: it will
either resize the texture in memory, adding transparent pixels until the image
reaches a POT size, or stop the execution on an assert. With textures used for
mipmaps, the framework will stop execution for non-POT textures.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 62 ]
I add the sparkle and the halo sprites as children to the _bomb sprite. This will
use the container characterisc of nodes to our advantage. When I grow the bomb,
all its children will grow with it.
Noce too that I use a third parameter to addChild for halo and sparkle:
bomb->addChild(halo, kMiddleground, kSpriteHalo);
This third parameter is an integer tag from yet another enumerated list declared
in GameLayer.h. I can use this tag to retrieve a parcular child from a sprite
as follows:
auto halo = (Sprite *)
bomb->getChildByTag(kSpriteHalo);
We now have our game screen in place:
Next come object pools.
www.it-ebooks.info
Chapter 4
[ 63 ]
Time for action – creating our object pools
The pools are just vectors of objects. And here are the steps to create them:
1. Inside the createPools method, we rst create a pool for meteors:
void GameLayer::createPools() {
int i;
_meteorPoolIndex = 0;
for (i = 0; i < 50; i++) {
auto sprite = Sprite::createWithSpriteFrameName("meteor.png");
sprite->setVisible(false);
_gameBatchNode->addChild(sprite, kMiddleground, kSpriteMeteor);
_meteorPool.pushBack(sprite);
}
2. Then we create an object pool for health packs:
_healthPoolIndex = 0;
for (i = 0; i < 20; i++) {
auto sprite = Sprite::createWithSpriteFrameName("health.png");
sprite->setVisible(false);
sprite->setAnchorPoint(Vec2(0.5f, 0.8f));
_gameBatchNode->addChild(sprite, kMiddleground, kSpriteHealth);
_healthPool.pushBack(sprite);
}
3. We'll use the corresponding pool index to retrieve objects from the vectors as the
game progresses.
What just happened?
We now have a vector of invisible meteor sprites and a vector of invisible health sprites.
We'll use their respecve pool indices to retrieve these from the vector as needed as you'll
see in a moment. But rst we need to take care of acons and animaons.
With object pools, we reduce the number of instanaons during the
main loop, and it allows us to never destroy anything that can be reused.
But if you need to remove a child from a node, use ->removeChild or
->removeChildByTag if a tag is present.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 64 ]
Actions in a nutshell
If you remember, a node will store informaon about posion, scale, rotaon, visibility, and
opacity of a node. And in Cocos2d-x, there is an Action class to change each one of these
values over me, in eect animang these transformaons.
Acons are usually created with a stac method create. The majority of these acons are
me-based, so usually the rst parameter you need to pass an acon is the me length for
the acon. So for instance:
auto fadeout = FadeOut::create(1.0f);
This creates a fadeout acon that will take one second to complete. You can run it on a
sprite, or node, as follows:
mySprite->runAction(fadeout);
Cocos2d-x has an incredibly exible system that allows us to create any combinaon of
acons and transformaons to achieve any eect we desire.
You may, for instance, choose to create an acon sequence (Sequence) that contains more
than one acon; or you can apply easing eects (EaseIn, EaseOut, and so on) to your
acons. You can choose to repeat an acon a certain number of mes (Repeat) or forever
(RepeatForever); and you can add callbacks to funcons you want called once an acon is
completed (usually inside a Sequence acon).
Time for action – creating actions with Cocos2d-x
Creang acons with Cocos2d-x is a very simple process:
1. Inside our createActions method, we will instanate the acons we can use
repeatedly in our game. Let's create our rst acons:
void GameLayer::createActions() {
//swing action for health drops
auto easeSwing = Sequence::create(
EaseInOut::create(RotateTo::create(1.2f, -10), 2),
EaseInOut::create(RotateTo::create(1.2f, 10), 2),
nullptr);//mark the end of a sequence with a nullptr
_swingHealth = RepeatForever::create( (ActionInterval *)
easeSwing );
_swingHealth->retain();
www.it-ebooks.info
Chapter 4
[ 65 ]
2. Acons can be combined in many dierent forms. Here, the retained
_swingHealth acon is a RepeatForever acon of Sequence that will rotate
the health sprite rst one way, then the other, with EaseInOut wrapping the
RotateTo acon. RotateTo takes 1.2 seconds to rotate the sprite rst to -10
degrees and then to 10. And the easing has a value of 2, which I suggest you
experiment with to get a sense of what it means visually. Next we add three
more acons:
//action sequence for shockwave: fade out, callback when
//done
_shockwaveSequence = Sequence::create(
FadeOut::create(1.0f),
CallFunc::create(std::bind(&GameLayer::shockwaveDone, this)),
nullptr);
_shockwaveSequence->retain();
//action to grow bomb
_growBomb = ScaleTo::create(6.0f, 1.0);
_growBomb->retain();
//action to rotate sprites
auto rotate = RotateBy::create(0.5f , -90);
_rotateSprite = RepeatForever::create( rotate );
_rotateSprite->retain();
3. First, another Sequence. This will fade out the sprite and call the shockwaveDone
funcon, which is already implemented in the class and turns the _shockwave
sprite invisible when called.
4. The last one is a RepeatForever acon of a RotateBy acon. In half a second, the
sprite running this acon will rotate -90 degrees and will do that again and again.
What just happened?
You just got your rst glimpse of how to create acons in Cocos2d-x and how the framework
allows for all sorts of combinaons to accomplish any eect.
It may be hard at rst to read through a Sequence acon and understand what's happening,
but the logic is easy to follow once you break it down into its individual parts.
But we are not done with the createActions method yet. Next come sprite animaons.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 66 ]
Animating a sprite in Cocos2d-x
The key thing to remember is that an animaon is just another type of acon, one that
changes the texture used by a sprite over a period of me.
In order to create an animaon acon, you need to rst create an Animation object.
This object will store all the informaon regarding the dierent sprite frames you wish to
use in the animaon, the length of the animaon in seconds, and whether it loops or not.
With this Animation object, you then create a Animate acon. Let's take a look.
Time for action – creating animations
Animaons are a specialized type of acon that require a few extra steps:
1. Inside the same createActions method, add the lines for the two animaons we
have in the game. First, we start with the animaon that shows an explosion when a
meteor reaches the city. We begin by loading the frames into an Animation object:
auto animation = Animation::create();
int i;
for(i = 1; i <= 10; i++) {
auto name = String::createWithFormat("boom%i.png", i);
auto frame = SpriteFrameCache::getInstance()-
>getSpriteFrameByName(name->getCString());
animation->addSpriteFrame(frame);
}
2. Then we use the Animation object inside a Animate acon:
animation->setDelayPerUnit(1 / 10.0f);
animation->setRestoreOriginalFrame(true);
_groundHit =
Sequence::create(
MoveBy::create(0, Vec2(0,_screenSize.height * 0.12f)),
Animate::create(animation),
CallFuncN::create(CC_CALLBACK_1(GameLayer::animationDone,
this)), nullptr);
_groundHit->retain();
3. The same steps are repeated to create the other explosion animaon used when
the player hits a meteor or a health pack.
animation = Animation::create();
for(int i = 1; i <= 7; i++) {
www.it-ebooks.info
Chapter 4
[ 67 ]
auto name = String::createWithFormat("explosion_small%i.png", i);
auto frame = SpriteFrameCache::getInstance()-
>getSpriteFrameByName(name->getCString());
animation->addSpriteFrame(frame);
}
animation->setDelayPerUnit(0.5 / 7.0f);
animation->setRestoreOriginalFrame(true);
_explosion = Sequence::create(
Animate::create(animation),
CallFuncN::create(CC_CALLBACK_1(GameLayer::animationDone,
this)), nullptr);
_explosion->retain();
What just happened?
We created two instances of a very special kind of acon in Cocos2d-x: Animate. Here is
what we did:
First, we created an Animation object. This object holds the references to all the
textures used in the animaon. The frames were named in such a way that they
could easily be concatenated inside a loop (boom1, boom2, boom3, and so on).
There are 10 frames for the rst animaon and seven for the second.
The textures (or frames) are SpriteFrame objects we grab from
SpriteFrameCache, which as you remember, contains all the informaon from the
sprite_sheet.plist data le. So the frames are in our sprite sheet.
Then when all frames are in place, we determine the delay of each frame by
dividing the total amount of seconds we want the animaon to last by the total
number of frames.
The setRestoreOriginalFrame method is important here. If we set
setRestoreOriginalFrame to true, then the sprite will revert to its original
appearance once the animaon is over. For example, if I have an explosion
animaon that will run on a meteor sprite, then by the end of the explosion
animaon, the sprite will revert to displaying the meteor texture.
Time for the actual acon. Animate receives the Animation object as its
parameter. (In the rst animaon, we shi the posion of the sprite just before
the explosion appears, so there is an extra MoveBy method.)
And in both instances, I make a call to an animationDone callback already
implemented in the class. It makes the calling sprite invisible:
void GameLayer::animationDone (Node* pSender) {
pSender->setVisible(false);
}
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 68 ]
We could have used the same method for both callbacks (animationDone
and shockwaveDone) as they accomplish the same thing. But I wanted to
show you a callback that receives as an argument, the node that made the call
and one that did not. Respectively, these are CallFuncN and CallFunc,
and were used inside the action sequences we just created.
Time to make our game tick!
Okay, we have our main elements in place and are ready to add the nal bit of logic to run
the game. But how will everything work?
We will use a system of countdowns to add new meteors and new health packs, as well as a
countdown that will incrementally make the game harder to play.
On touch, the player will start the game if the game is not running, and also add bombs and
explode them during gameplay. An explosion creates a shockwave.
On update, we will check against collision between our _shockwave sprite (if visible) and all
our falling objects. And that's it. Cocos2d-x will take care of all the rest through our created
acons and callbacks!
So let's implement our touch events rst.
Time for action – handling touches
Time to bring the player to our party:
1. Time to implement our onTouchBegan method. We'll begin by handling the two
game states, intro and game over:
bool GameLayer::onTouchBegan
(Touch * touch, Event * event){
//if game not running, we are seeing either intro or
//gameover
if (!_running) {
//if intro, hide intro message
if (_introMessage->isVisible()) {
_introMessage->setVisible(false);
//if game over, hide game over message
} else if (_gameOverMessage->isVisible()) {
www.it-ebooks.info
Chapter 4
[ 69 ]
SimpleAudioEngine::getInstance()->stopAllEffects();
_gameOverMessage->setVisible(false);
}
this->resetGame();
return true;
}
2. Here we check to see if the game is not running. If not, we check to see if
any of our messages are visible. If _introMessage is visible, we hide it. If
_gameOverMessage is visible, we stop all current sound eects and hide the
message as well. Then we call a method called resetGame, which will reset
all the game data (energy, score, and countdowns) to their inial values, and set
_running to true.
3. Next we handle the touches. But we only need to handle one each me so we use
->anyObject() on Set:
auto touch = (Touch *)pTouches->anyObject();
if (touch) {
//if bomb already growing...
if (_bomb->isVisible()) {
//stop all actions on bomb, halo and sparkle
_bomb->stopAllActions();
auto child = (Sprite *) _bomb->getChildByTag(kSpriteHalo);
child->stopAllActions();
child = (Sprite *) _bomb->getChildByTag(kSpriteSparkle);
child->stopAllActions();
//if bomb is the right size, then create shockwave
if (_bomb->getScale() > 0.3f) {
_shockWave->setScale(0.1f);
_shockWave->setPosition(_bomb->getPosition());
_shockWave->setVisible(true);
_shockWave->runAction(ScaleTo::create(0.5f, _bomb-
>getScale() * 2.0f));
_shockWave->runAction(_shockwaveSequence->clone());
SimpleAudioEngine::getInstance()->playEffect("bombRelease.
wav");
} else {
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 70 ]
SimpleAudioEngine::getInstance()->playEffect("bombFail.
wav");
}
_bomb->setVisible(false);
//reset hits with shockwave, so we can count combo hits
_shockwaveHits = 0;
//if no bomb currently on screen, create one
} else {
Point tap = touch->getLocation();
_bomb->stopAllActions();
_bomb->setScale(0.1f);
_bomb->setPosition(tap);
_bomb->setVisible(true);
_bomb->setOpacity(50);
_bomb->runAction(_growBomb->clone());
auto child = (Sprite *) _bomb->getChildByTag(kSpriteHalo);
child->runAction(_rotateSprite->clone());
child = (Sprite *) _bomb->getChildByTag(kSpriteSparkle);
child->runAction(_rotateSprite->clone());
}
}
4. If _bomb is visible, it means it's already growing on the screen. So on touch,
we use the stopAllActions() method on the bomb and we use the
stopAllActions() method on its children that we retrieve through our tags:
child = (Sprite *) _bomb->getChildByTag(kSpriteHalo);
child->stopAllActions();
child = (Sprite *) _bomb->getChildByTag(kSpriteSparkle);
child->stopAllActions();
5. If _bomb is the right size, we start our _shockwave. If it isn't, we play a bomb failure
sound eect; there is no explosion and _shockwave is not made visible.
6. If we have an explosion, then the _shockwave sprite is set to 10 percent of the
scale. It's placed at the same spot as the bomb, and we run a couple of acons on it:
we grow the _shockwave sprite to twice the scale the bomb was when it went o
and we run a copy of _shockwaveSequence that we created earlier.
7. Finally, if no _bomb is currently visible on screen, we create one. And we run clones
of previously created acons on the _bomb sprite and its children. When _bomb
grows, its children grow. But when the children rotate, the bomb does not: a parent
changes its children, but the children do not change their parent.
www.it-ebooks.info
Chapter 4
[ 71 ]
What just happened?
We just added part of the core logic of the game. It is with touches that the player creates
and explodes bombs to stop meteors from reaching the city. Now we need to create our
falling objects. But rst, let's set up our countdowns and our game data.
Time for action – starting and restarting the game
Let's add the logic to start and restart the game.
1. Let's write the implementaon for resetGame:
void GameLayer::resetGame(void) {
_score = 0;
_energy = 100;
//reset timers and "speeds"
_meteorInterval = 2.5;
_meteorTimer = _meteorInterval * 0.99f;
_meteorSpeed = 10;//in seconds to reach ground
_healthInterval = 20;
_healthTimer = 0;
_healthSpeed = 15;//in seconds to reach ground
_difficultyInterval = 60;
_difficultyTimer = 0;
_running = true;
//reset labels
_energyDisplay->setString(std::to_string((int) _energy) +
"%");
_scoreDisplay->setString(std::to_string((int) _score));
}
2. Next, add the implementaon of stopGame:
void GameLayer::stopGame() {
_running = false;
//stop all actions currently running
int i;
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 72 ]
int count = (int) _fallingObjects.size();
for (i = count-1; i >= 0; i--) {
auto sprite = _fallingObjects.at(i);
sprite->stopAllActions();
sprite->setVisible(false);
_fallingObjects.erase(i);
}
if (_bomb->isVisible()) {
_bomb->stopAllActions();
_bomb->setVisible(false);
auto child = _bomb->getChildByTag(kSpriteHalo);
child->stopAllActions();
child = _bomb->getChildByTag(kSpriteSparkle);
child->stopAllActions();
}
if (_shockWave->isVisible()) {
_shockWave->stopAllActions();
_shockWave->setVisible(false);
}
if (_ufo->isVisible()) {
_ufo->stopAllActions();
_ufo->setVisible(false);
auto ray = _ufo->getChildByTag(kSpriteRay);
ray->stopAllActions();
ray->setVisible(false);
}
}
What just happened?
With these methods we control gameplay. We start the game with default values through
resetGame(), and we stop all acons with stopGame().
Already implemented in the class is the method that makes the game more dicult as me
progresses. If you take a look at the method (increaseDifficulty) you will see that it
reduces the interval between meteors and reduces the me it takes for meteors to reach
the ground.
All we need now is the update method to run the countdowns and check for collisions.
www.it-ebooks.info
Chapter 4
[ 73 ]
Time for action – updating the game
We already have the code that updates the countdowns inside the update. If it's me to
add a meteor or a health pack we do it. If it's me to make the game more dicult to play,
we do that too.
It is possible to use an acon for these mers: a Sequence acon with a
Delay acon object and a callback. But there are advantages to using these
countdowns. It's easier to reset them and to change them, and we can take
them right into our main loop.
So it's me to add our main loop:
1. What we need to do is check for collisions. So add the following code:
if (_shockWave->isVisible()) {
count = (int) _fallingObjects.size();
for (i = count-1; i >= 0; i--) {
auto sprite = _fallingObjects.at(i);
diffx = _shockWave->getPositionX() - sprite->getPositionX();
diffy = _shockWave->getPositionY() - sprite->getPositionY();
if (pow(diffx, 2) + pow(diffy, 2) <= pow(_shockWave-
>getBoundingBox().size.width * 0.5f, 2)) {
sprite->stopAllActions();
sprite->runAction( _explosion->clone());
SimpleAudioEngine::getInstance()->playEffect("boom.wav");
if (sprite->getTag() == kSpriteMeteor) {
_shockwaveHits++;
_score += _shockwaveHits * 13 + _shockwaveHits * 2;
}
//play sound
_fallingObjects.erase(i);
}
}
_scoreDisplay->setString(std::to_string(_score));
}
2. If _shockwave is visible, we check the distance between it and each sprite
in _fallingObjects vector. If we hit any meteors, we increase the value of
the _shockwaveHits property so we can award the player for mulple hits.
Next we move the clouds:
//move clouds
for (auto sprite : _clouds) {
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 74 ]
sprite->setPositionX(sprite->getPositionX() + dt * 20);
if (sprite->getPositionX() > _screenSize.width + sprite-
>getBoundingBox().size.width * 0.5f)
sprite->setPositionX(-sprite->getBoundingBox().size.width *
0.5f);
}
3. I chose not to use a MoveTo acon for the clouds to show you the amount of code
that can be replaced by a simple acon. If not for Cocos2d-x acons, we would have
to implement logic to move, rotate, swing, scale, and explode all our sprites!
4. And nally:
if (_bomb->isVisible()) {
if (_bomb->getScale() > 0.3f) {
if (_bomb->getOpacity() != 255)
_bomb->setOpacity(255);
}
}
5. We give the player an extra visual cue to when a bomb is ready to explode by
changing its opacity.
What just happened?
The main loop is prey straighorward when you don't have to worry about updang
individual sprites, as our acons take care of that for us. We prey much only need to run
collision checks between our sprites, and to determine when it's me to throw something
new at the player.
So now the only thing le to do is grab the meteors and health packs from the pools when
their mers are up. So let's get right to it.
Time for action – retrieving objects from the pool
We just need to use the correct index to retrieve the objects from their respecve vector:
1. To retrieve meteor sprites, we'll use the resetMeteor method:
void GameLayer::resetMeteor(void) {
//if too many objects on screen, return
if (_fallingObjects.size() > 30) return;
auto meteor = _meteorPool.at(_meteorPoolIndex);
_meteorPoolIndex++;
www.it-ebooks.info
Chapter 4
[ 75 ]
if (_meteorPoolIndex == _meteorPool.size())
_meteorPoolIndex = 0;
int meteor_x = rand() % (int) (_screenSize.width * 0.8f) +
_screenSize.width * 0.1f;
int meteor_target_x = rand() % (int) (_screenSize.width * 0.8f)
+ _screenSize.width * 0.1f;
meteor->stopAllActions();
meteor->setPosition(Vec2(meteor_x, _screenSize.height +
meteor->getBoundingBox().size.height * 0.5));
//create action
auto rotate = RotateBy::create(0.5f , -90);
auto repeatRotate = RepeatForever::create( rotate );
auto sequence = Sequence::create (
MoveTo::create(_meteorSpeed, Vec2(meteor_target_x,
_screenSize.height * 0.15f)),
CallFunc::create(std::bind(&GameLayer::fallingObjec
tDone, this, meteor) ), nullptr);
meteor->setVisible ( true );
meteor->runAction(repeatRotate);
meteor->runAction(sequence);
_fallingObjects.pushBack(meteor);
}
2. We grab the next available meteor from the pool, then we pick a random start
and end x value for its MoveTo acon. The meteor starts at the top of the screen
and will move to the boom towards the city, but the x value is randomly picked
each me.
3. We rotate the meteor inside a RepeatForever acon, and we use Sequence to
move the sprite to its target posion and then call back fallingObjectDone when
the meteor has reached its target. We nish by adding the new meteor we retrieved
from the pool to the _fallingObjects vector so we can check collisions with it.
4. The method to retrieve the health (resetHealth) sprites is prey much the same,
except that swingHealth acon is used instead of rotate. You'll nd that method
already implemented in GameLayer.cpp.
What just happened?
So in resetGame we set the mers, and we update them in the update method. We use
these mers to add meteors and health packs to the screen by grabbing the next available
one from their respecve pool, and then we proceed to run collisions between an exploding
bomb and these falling objects.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 76 ]
Noce that in both resetMeteor and resetHealth we don't add new sprites if too many
are on screen already:
if (_fallingObjects->size() > 30) return;
This way the game does not get ridiculously hard, and we never run out of unused objects
in our pools.
And the very last bit of logic in our game is our fallingObjectDone callback, called
when either a meteor or a health pack has reached the ground, at which point it awards
or punishes the player for leng sprites through.
When you take a look at that method inside GameLayer.cpp, you will noce how we use
->getTag() to quickly ascertain which type of sprite we are dealing with (the one calling
the method):
if (pSender->getTag() == kSpriteMeteor) {
If it's a meteor, we decrease energy from the player, play a sound eect, and run the
explosion animaon; an autorelease copy of the _groundHit acon we retained earlier,
so we don't need to repeat all that logic every me we need to run this acon.
If the item is a health pack, we increase the energy or give the player some points,
play a nice sound eect, and hide the sprite.
Play the game!
We've been coding like mad, and it's nally me to run the game. But rst, don't forget to
release all the items we retained. In GameLayer.cpp, add our destructor method:
GameLayer::~GameLayer () {
//release all retained actions
CC_SAFE_RELEASE(_growBomb);
CC_SAFE_RELEASE(_rotateSprite);
CC_SAFE_RELEASE(_shockwaveSequence);
CC_SAFE_RELEASE(_swingHealth);
CC_SAFE_RELEASE(_groundHit);
CC_SAFE_RELEASE(_explosion);
CC_SAFE_RELEASE(_ufoAnimation);
CC_SAFE_RELEASE(_blinkRay);
_clouds.clear();
_meteorPool.clear();
_healthPool.clear();
_fallingObjects.clear();
}
www.it-ebooks.info
Chapter 4
[ 77 ]
The actual game screen will now look something like this:
Once again, you can refer to 4198_04_FINAL_PROJECT.zip if you nd any problems
running the code.
Now, let's take this to Android.
Time for action – running the game in Android
Follow these steps to deploy the game to Android:
1. This me, there is no need to alter the manifest because the default sengs are the
ones we want. So, navigate to proj.android and then to the jni folder and open
the Android.mk le in a text editor.
2. Edit the lines in LOCAL_SRC_FILES to read as follows:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/GameLayer.cpp
3. Follow the instrucons from the HelloWorld and AirHockey examples to import
the game into Eclipse.
4. Save it and run your applicaon. This me, you can try out dierent size screens if
you have the devices.
www.it-ebooks.info
Fun with Sprites – Sky Defense
[ 78 ]
What just happened?
You just ran a universal app in Android. And nothing could have been simpler.
As a bonus, I've added another version of the game with an extra type of enemy to deal with:
a UFO hell-bent on zapping the city! You may nd this in 4198_04_BONUS_PROJECT.zip.
Pop quiz – sprites and actions
Q1. A SpriteBatchNode can contain what types of elements?
1. Sprites using textures from two or more sprite sheets.
2. Sprites using the same source texture.
3. Blank sprites.
4. Sprites using textures from one sprite sheet and one other image.
Q2. In order to run an acon nonstop, what do I need to use?
1. RepeatForever.
2. Repeat.
3. The default behavior of an acon is to run nonstop.
4. Acons can't repeat forever.
Q3. In order to animate a sprite so that it would move to a certain point on the screen and
then fade out, what acons would I need?
1. A Sequence lisng an EaseIn and EaseOut acon.
2. A Sequence lisng a FadeOut and MoveTo acon.
3. A Sequence lisng a MoveTo or MoveBy and a FadeOut acon.
4. A Sequence lisng a RotateBy and FadeOut acon.
Q4. To create a sprite frame animaon, what group of classes are absolutely essenal?
1. Sprite, SpriteBatchNode, and EaseIn.
2. SpriteFrameCache, RotateBy, and ActionManager.
3. Sprite, Layer, and FadeOut.
4. SpriteFrame, Animation, and Animate.
www.it-ebooks.info
Chapter 4
[ 79 ]
Summary
In my opinion, aer nodes and all their derived objects, acons are the second best thing
about Cocos2d-x. They are me savers and can quickly spice things up in any project with
professional-looking animaons. And I hope with the examples found in this chapter, and
along with the ones found in the Cocos2d-x samples test project, you will be able to create
any acon you need with Cocos2d-x.
In the next chapter, I'll introduce you to another simple way you can spice things up in your
game: with parcles!
www.it-ebooks.info
www.it-ebooks.info
[ 81 ]
On the Line – Rocket Through
In our third game, Rocket Through, we'll use particle effects to spice things up
a bit, and we'll use DrawNode to make our own OpenGL drawings on screen.
And be advised, this game uses quite a bit of vector math, but luckily for us,
Cocos2d-x comes bundled with a sweet pack of helper methods to deal with
that as well.
You will learn:
How to load and set up a parcle system
How to draw primives (lines, circles, and more) with DrawNode
How to use vector math helper methods included in Cocos2d-x
The game – Rocket Through
In this sci- version of the classic Snake game engine, you control a rocket ship that must
move around seven planets, collecng ny supernovas. But here's the catch: you can only
steer the rocket by rotang it around pivot points put in place through touch events. So the
vector of movement we set for the rocket ship is at mes linear and at mes circular.
The game settings
This is a universal game designed for the regular iPad and then scaled up and down to match
the screen resoluon of other devices. It is set to play in portrait mode and it does not
support multouches.
5
www.it-ebooks.info
On the Line – Rocket Through
[ 82 ]
Play rst, work later
Download the 4198_05_START_PROJECT.zip and 4198_05_FINAL_PROJECT.zip les
from this book's Support page.
You will, once again, use the Start Project opon to work on; this way, you won't need to
type logic or syntax already covered in previous chapters. The Start Project opon contains
all the resource les and all the class declaraons as well as placeholders for all the methods
inside the classes' implementaon les. We'll go over these in a moment.
You should run the nal project version to acquaint yourself with the game. By pressing and
dragging your nger on the rocket ship, you draw a line. Release the touch and you create a
pivot point. The ship will rotate around this pivot point unl you press on the ship again to
release it. Your aim is to collect the bright supernovas and avoid the planets.
www.it-ebooks.info
Chapter 5
[ 83 ]
The start project
If you run the Start Project opon, you should see the basic game screen already in place.
There is no need to repeat the steps we've taken in our previous tutorial to create a batch
node and place all the screen sprites. We once again have a _gameBatchNode object and a
createGameScreen method.
But by all means, read through the code inside the createGameScreen method. What is of
key importance here is that each planet we create is stored inside the _planets vector. And
we also create our _rocket object (the Rocket class) and our _lineContainer object
(the LineContainer class) here. More on these soon.
In the Start Project opon, we also have our old friend GameSprite, which extends Sprite
here with an extra method to get the radius() method of our sprites. The Rocket object
and all the planets are GameSprite objects.
Screen settings
So if you have the Start Project opon opened in Xcode, let's review the screen sengs for
this game in AppDelegate.cpp. Inside the applicationDidFinishLaunching method,
you should see this:
auto designSize = Size(1536, 2048);
glview->setDesignResolutionSize(designSize.width, designSize.height,
ResolutionPolicy::EXACT_FIT);
std::vector<std::string> searchPaths;
if (screenSize.width > 768) {
searchPaths.push_back("ipadhd");
director->setContentScaleFactor(1536/designSize.width);
} else if (screenSize.width > 320) {
searchPaths.push_back("ipad");
director->setContentScaleFactor(768/designSize.width);
} else {
searchPaths.push_back("iphone");
director->setContentScaleFactor(380/designSize.width);
}
auto fileUtils = FileUtils::getInstance();
fileUtils->setSearchPaths(searchPaths);
So we basically start the same way we did in the previous game. The majority of sprites in
this game are circle-shaped and you may noce some distoron in dierent screens; you
should test the same conguraon but using dierent ResolutionPolicies, such as
SHOW_ALL.
www.it-ebooks.info
On the Line – Rocket Through
[ 84 ]
So what are particles?
Parcles or parcle systems are a way to add special eects to your applicaons. In general
terms this is achieved by the use of a large number of ny textured sprites (parcles), which
are animated and run through a series of transformaons. You can use these systems to
create smoke, explosions, sparks, lightening, rain, snow, and other such eects.
As I menoned in Chapter 1, Installing Cocos2d-x, you should seriously consider geng
yourself a program to help you design your parcle systems. In this game, the parcles
were created in ParcleDesigner.
It's me to add them to our game!
Time for action – creating particle systems
For parcles, all we need is the XML le describing the parcle system's properes.
1. So let's go to GameLayer.cpp.
2. The game inializes by calling createGameScreen, which is already in place, then
createParticles and createStarGrid, which is also implemented. So let's go
over the createParticles method now.
3. Go to that method in GameLayer.cpp and add the following code:
_jet = ParticleSystemQuad::create("jet.plist");
_jet->setSourcePosition(Vec2(-_rocket->getRadius() * 0.8f,0));
_jet->setAngle(180);
_jet->stopSystem();
this->addChild(_jet, kBackground);
_boom = ParticleSystemQuad::create("boom.plist");
_boom->stopSystem();
this->addChild(_boom, kForeground);
_comet = ParticleSystemQuad::create("comet.plist");
_comet->stopSystem();
_comet->setPosition(Vec2(0, _screenSize.height * 0.6f));
_comet->setVisible(false);
this->addChild(_comet, kForeground);
_pickup = ParticleSystemQuad::create("plink.plist");
_pickup->stopSystem();
this->addChild(_pickup, kMiddleground);
www.it-ebooks.info
Chapter 5
[ 85 ]
_warp = ParticleSystemQuad::create("warp.plist");
_warp->setPosition(_rocket->getPosition());
this->addChild(_warp, kBackground);
_star = ParticleSystemQuad::create("star.plist");
_star->stopSystem();
_star->setVisible(false);
this->addChild(_star, kBackground, kSpriteStar);
What just happened?
We created our rst parcles. ParcleDesigner exports the parcle system data as a .plist
le, which we used to create our ParticleSystemQuad objects. You should open one
of these les in Xcode to review the number of sengs used in a parcle system. From
Cocos2d-x you can modify any of these sengs through seers inside ParticleSystem.
The parcles we'll use in this game are as follows:
_jet: This is aached to the _rocket object and it will trail behind the _rocket
object. We set the system's angle and source posion parameters to match the
_rocket sprite.
_boom: This is the parcle system used when _rocket explodes.
_comet: This is a parcle system that moves across the screen at set intervals and
can collide with _rocket.
_pickup: This is used when a star is collected.
_warp: This marks the inial posion of the rocket.
_star: This is the parcle system used for the star that the rocket must collect.
The following screenshot shows these various parcles:
www.it-ebooks.info
On the Line – Rocket Through
[ 86 ]
All parcle systems are added as children to GameLayer; they cannot be added to our
SpriteBatchNode class. And you must call stopSystem() on each system as they're
created otherwise they will start playing as soon as they are added to a node.
In order to run the system, you make a call to resetSystem().
Cocos2d-x comes bundled with some common parcle systems you can
modify for your own needs. If you go to the test folder at: tests/cpp-
tests/Classes/ParticleTest, you will see examples of these
systems being used. The actual parcle data les are found at: tests/
cpp-tests/Resources/Particles.
Creating the grid
Let's take some me now to review the grid logic in the game. This grid is created inside the
createStarGrid method in GameLayer.cpp. What the method does is determine all
possible spots on the screen where we can place the _star parcle system.
We use a C++ vector list called _grid to store the available spots:
std::vector<Point> _grid;
The createStarGrid method divides the screen into mulple cells of 32 x 32 pixels,
ignoring the areas too close to the screen borders (gridFrame). Then we check the distance
between each cell and the planet sprites stored inside the vector _planets. If the cell is far
enough from the planets, we store it inside the _grid vector as Point.
In the following gure, you can get an idea of the result we're aer. We want all the white
cells not overlapping any of the planets.
www.it-ebooks.info
Chapter 5
[ 87 ]
We output a message to the console with Log stang how many cells we end up with:
CCLOG("POSSIBLE STARS: %i", _grid.size());
This vector list will be shued at each new game, so we end up with a random sequence of
possible posions for our star:
std::random_shuffle(_grid.begin(), _grid.end());
This way we never place a star on top of a planet or so close to it that the rocket could not
reach it without colliding with the planet.
Drawing primitives in Cocos2d-x
One of the main elements in the game is the LineContainer.cpp class. It is a DrawNode
derived class that allows us to draw lines and circles on the screen.
DrawNode comes bundled with a list of drawing methods you can use to draw lines, points,
circles, polygons, and so on.
The methods we'll use are drawLine and drawDot.
Time for action – let's do some drawing!
Time to implement the drawing inside LineContainer.cpp. You will noce that this class
already has most of its methods implemented, so you can save a lile typing. I'll go over
what these methods represent once we add the game's main update method. But basically
LineContainer will be used to display the lines the player draws on screen to manipulate
_rocket sprite, as well as display an energy bar that acts as a sort of mer in our game:
1. What we need to change here is the update method. So this is what you need to
type inside that method:
_energy -= dt * _energyDecrement;
if (_energy < 0) _energy = 0;
clear();
switch (_lineType) {
case LINE_NONE:
break;
case LINE_TEMP:
drawLine(_tip, _pivot, Color4F(1.0, 1.0, 1.0, 1.0));
drawDot(_pivot, 5, Color4F(Color3B::WHITE));
break;
www.it-ebooks.info
On the Line – Rocket Through
[ 88 ]
case LINE_DASHED:
drawDot(_pivot, 5, Color4F(Color3B::WHITE));
int segments = _lineLength / (_dash + _dashSpace);
float t = 0.0f;
float x_;
float y_;
for (int i = 0; i < segments + 1; i++) {
x_ = _pivot.x + t * (_tip.x - _pivot.x);
y_ = _pivot.y + t * (_tip.y - _pivot.y);
drawDot(Vec2(x_, y_), 5, Color4F(Color3B::WHITE));
t += (float) 1 / segments;
}
break;
}
2. We end our drawing calls by drawing the energy bar in the same
LineContainer node:
drawLine(Vec2(_energyLineX, _screenSize.height * 0.1f),
Vec2(_energyLineX, _screenSize.height * 0.9f), Color4F(0.0, 0.0,
0.0, 1.0));
drawLine(Vec2(_energyLineX, _screenSize.height * 0.1f),
Vec2(_energyLineX, _screenSize.height * 0.1f + _energy *
_energyHeight ), Color4F(1.0, 0.5, 0.0, 1.0));
What just happened?
You just learned how to draw inside DrawNode. One important line in that code is the
clear() call. It clears all the drawings in that node before we update them with their
new state.
In LineContainer, we use a switch statement to determine how to draw the player's line.
If the _lineType property is set to LINE_NONE, we don't draw anything (this will, in eect,
clear the screen of any drawings done by the player).
If _lineType is LINE_TEMP, this means the player is currently dragging a nger away from
the _rocket object, and we want to show a white line from the _rocket current posion
to the player's current touch posion. These points are called tip and pivot, respecvely.
We also draw a dot right on the pivot point.
drawLine(_tip, _pivot, Color4F(1.0, 1.0, 1.0, 1.0));
drawDot(_pivot, 5, Color4F(Color3B::WHITE));
www.it-ebooks.info
Chapter 5
[ 89 ]
If _lineType is LINE_DASHED, it means the player has removed his or her nger from the
screen and set a new pivot point for the _rocket to rotate around. We draw a white doed
line, using what is known as the Bezier linear formula to draw a series of ny circles from the
_rocket current posion and the pivot point:
for (int i = 0; i < segments + 1; i++) {
x_ = _pivot.x + t * (_tip.x - _pivot.x);
y_ = _pivot.y + t * (_tip.y - _pivot.y);
drawDot(Vec2(x_, y_), 5, Color4F(Color3B::WHITE));
t += (float) 1 / segments;
}
And nally, for the energy bar, we draw a black line underneath an orange one. The orange
one resizes as the value for _energy in LineContainer is reduced. The black one stays the
same and it's here to show contrast. You layer your drawings through the order of your draw
calls; so the rst things drawn appear underneath the laer ones.
The rocket sprite
Time to tackle the second object in the game: the rocket.
Once again, I already put in place the part of the logic that's old news to you. But please
review the code already inside Rocket.cpp. We have a method to reset the rocket every
me a new game starts (reset), and a method to show the selected state of the rocket
(select(bool flag)) by changing its displayed texture:
if (flag) {
this->setDisplayFrame(SpriteFrameCache::getInstance()-
>getSpriteFrameByName("rocket_on.png"));
} else {
this->setDisplayFrame(SpriteFrameCache::getInstance()-
>getSpriteFrameByName("rocket.png"));
}
This will either show the rocket with a glow around it, or not.
And nally a method to check collision with the sides of the screen (collidedWithSides).
If there is a collision, we adjust the rocket so it moves away from the screen side it collided
with, and we release it from any pivot point.
What we really need to worry about here is the rocket's update method. And that's what
we'll add next.
www.it-ebooks.info
On the Line – Rocket Through
[ 90 ]
Time for action – updating our rocket sprite
The game's main loop will call the rocket's update method in every iteraon.
1. Inside the empty update method in Rocket.cpp, add the following lines:
Point position = this->getPosition();
if (_rotationOrientation == ROTATE_NONE) {
position.x += _vector.x * dt;
position.y += _vector.y * dt;
} else {
float angle = _angularSpeed * dt;
Point rotatedPoint = position.rotateByAngle(_pivot, angle);
position.x = rotatedPoint.x;
position.y = rotatedPoint.y;
float rotatedAngle;
Point diff = position;
diff.subtract(_pivot);
Point clockwise = diff.getRPerp();
if (_rotationOrientation == ROTATE_COUNTER) {
rotatedAngle = atan2 (-1 * clockwise.y, -1 * clockwise.x);
} else {
rotatedAngle = atan2 (clockwise.y, clockwise.x);
}
_vector.x = _speed * cos (rotatedAngle);
_vector.y = _speed * sin (rotatedAngle);
this->setRotationFromVector();
if (this->getRotation() > 0) {
this->setRotation( fmodf(this->getRotation(), 360.0f) );
} else {
this->setRotation( fmodf(this->getRotation(), -360.0f) );
}
}
www.it-ebooks.info
Chapter 5
[ 91 ]
2. Here we are saying, if the rocket is not rotang (_rotationOrientation ==
ROTATE_NONE), just move it according to its current _vector. If it is rotang, then
use the Cocos2d-x helper rotateByAngle method to nd its next posion around
its pivot point:
3. The method will rotate any point around a pivot by a certain angle. So we rotate
the rocket's updated posion around its pivot (determined by the player) using a
property of Rocket class called _angularSpeed; we'll see in a moment how it
gets calculated.
4. Based on whether the rocket is rotang clockwise or counterclockwise, we adjust its
rotaon so the rocket will be at a 90 degree angle with the line drawn between the
rocket and its pivot point. Then we change the rocket's movement vector based on
this rotated angle, and we wrap the value of that angle between 0 and 360.
5. Finish up the update method with these lines:
if (_targetRotation > this->getRotation() + 180) {
_targetRotation -= 360;
}
if (_targetRotation < this->getRotation() - 180) {
_targetRotation += 360;
}
this->setPosition(position);
_dr = _targetRotation - this->getRotation() ;
_ar = _dr * _rotationSpring;
_vr += _ar ;
_vr *= _rotationDamping;
float rotationNow = this->getRotation();
rotationNow += _vr;
this->setRotation(rotationNow);
6. With these lines we determine the new target rotaon of our sprite and we run an
animaon to rotate the rocket to its target rotaon (with a bit of a spring to it).
www.it-ebooks.info
On the Line – Rocket Through
[ 92 ]
What just happened?
We just wrote the logic that will move the rocket around the screen, whether the rocket is
rotang or not.
So when the player picks a pivot point for the _rocket sprite, this pivot point is passed to
both Rocket and LineContainer. The former will use it to rotate its vector around it and
the laer will use it to draw a doed line between _rocket and the pivot point.
We can't use Action to rotate the sprite because the target rotaon
is updated too many mes in our logic, and Action needs me to
inialize and run.
So it's me to code the touch events to make all that logic fall into place.
Time for action – handling touches
We need to implement onTouchBegan, onTouchMoved, and onTouchEnded.
1. Now in GameLayer.cpp, inside onTouchBegan, add the following lines:
if (!_running) return true;
Point tap = touch->getLocation();
float dx = _rocket->getPositionX() - tap.x;
float dy = _rocket->getPositionY() - tap.y;
if (dx * dx + dy * dy <= pow(_rocket->getRadius(), 2) ) {
_lineContainer->setLineType ( LINE_NONE );
_rocket->setRotationOrientation ( ROTATE_NONE );
_drawing = true;
}
return true;
When a touch begins, we only need to determine whether it's touching the ship.
If it is, we set our _drawing property to true. This will indicate we have a valid
point (one that began by touching the _rocket sprite).
2. We clear any lines we may be currently drawing in _lineContainer by calling
setLineType( LINE_NONE ), and we make sure _rocket will not rotate unl we
have a pivot point by releasing _rocket (setRotationOrientation ( ROTATE_
NONE )), so it will connue to move on its current linear trajectory (_vector).
www.it-ebooks.info
Chapter 5
[ 93 ]
3. From here, we begin drawing a new line with the next onTouchMoved method.
Inside that method, we add the following lines:
if (!_running) return;
if (_drawing) {
Point tap = touch->getLocation();
float dx = _rocket->getPositionX() - tap.x;
float dy = _rocket->getPositionY() - tap.y;
if (dx * dx + dy * dy > pow (_minLineLength, 2)) {
_rocket->select(true);
_lineContainer->setPivot ( tap );
_lineContainer->setLineType ( LINE_TEMP );
} else {
_rocket->select(false);
_lineContainer->setLineType ( LINE_NONE );
}
}
4. We'll handle touch moved only if we are using _drawing, which means the player
has pressed on the ship and is now dragging his or her nger across the screen.
Once the distance between the nger and _rocket is greater than the
_minLineLength distance we spulate in game init, then we give a visual
cue to the player by adding a glow around _rocket (_rocket->select(true)),
and we draw the new line in _lineContainer by passing it the touch's current
posion and seng the line type to LINE_TEMP. If the minimum length is not
reached, we don't show a line and nor do we show the player selected.
5. Next comes onTouchEnded. There is logic in place already inside our
onTouchEnded method which deals with game states. You should uncomment the
calls to resetGame and add a new else if statement inside the method:
} else if (_state == kGamePaused) {
_pauseBtn->setDisplayFrame(SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("btn_pause_off.png"));
_paused->setVisible(false);
_state = kGamePlay;
_running = true;
return;
}
6. If the game is paused, we change the texture in the _pauseBtn sprite through
Sprite->setDisplayFrame, and we start running the game again.
www.it-ebooks.info
On the Line – Rocket Through
[ 94 ]
7. Now we begin handling the touch. First, we determine whether it's landing on the
Pause buon:
if (!_running) return;
if(touch != nullptr) {
Point tap = touch->getLocation();
if (_pauseBtn->getBoundingBox().containsPoint(tap)) {
_paused->setVisible(true);
_state = kGamePaused;
_pauseBtn->setDisplayFrame(SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("btn_pause_on.png"));
_running = false;
return;
}
}
8. If so, we change the game state to kGamePaused, change the texture on the
_pauseBtn sprite (by retrieving another sprite frame from SpriteFrameCache),
stop running the game (pausing it), and return from the funcon.
9. We can nally do something about the rocket ship. So, connuing inside the same
if(touch != nullptr) { condional seen previously, add these lines:
_drawing = false;
_rocket->select(false);
if (_lineContainer->getLineType() == LINE_TEMP) {
_lineContainer->setPivot (tap);
_lineContainer->setLineLength ( _rocket->getPosition().
distance( tap ) );
_rocket->setPivot (tap);
10. We start by deselecng the _rocket sprite, and then we check whether we are
currently showing a temporary line in _lineContainer. If we are, this means we
can go ahead and create our new pivot point with the player's released touch. We
pass this informaon to _lineContainer with our setPivot method, along with
the line length. The _rocket sprite also receives the pivot point informaon.
Then, things get hairy! The _rocket sprite is moving at a pixel-based speed. Once
_rocket starts rotang, it will move at an angular-based speed through Point.
rotateByAngle. So the following lines are added to translate the _rocket current
pixel-based speed into angular speed:
float circle_length = _lineContainer->getLineLength() * 2 * M_PI;
int iterations = floor(circle_length / _rocket->getSpeed());
_rocket->setAngularSpeed ( 2 * M_PI / iterations);
www.it-ebooks.info
Chapter 5
[ 95 ]
11. It grabs the length of the circumference about to be described by _rocket (line
length * 2 * PI) and divides it by the rocket's speed, geng in return the
number of iteraons needed for the rocket to complete that length. Then the 360
degrees of the circle is divided by the same number of iteraons (but we do it in
radians) to arrive at the fracon of the circle that the rocket must rotate at each
iteraon: its angular speed.
12. What follows next is even more math, using the amazingly helpful methods from
Cocos2d-x related to vector math (Point.getRPerp, Point.dot, Point.
subtract, to name a few) some of which we've seen already in the Rocket class:
Vec2 diff = _rocket->getPosition();
diff.subtract(_rocket->getPivot());
Point clockwise = diff.getRPerp();
float dot =clockwise.dot(_rocket->getVector());
if (dot > 0) {
_rocket->setAngularSpeed ( _rocket->getAngularSpeed() * -1 );
_rocket->setRotationOrientation ( ROTATE_CLOCKWISE );
_rocket->setTargetRotation
( CC_RADIANS_TO_DEGREES( atan2(clockwise.y, clockwise.x) ) );
} else {
_rocket->setRotationOrientation ( ROTATE_COUNTER );
_rocket->setTargetRotation ( CC_RADIANS_TO_DEGREES
(atan2(-1 * clockwise.y, -1 * clockwise.x) ) );
}
_lineContainer->setLineType ( LINE_DASHED );
13. What they do here is determine which direcon the rocket should rotate to:
clockwise or counterclockwise, based on its current vector of movement.
14. The line the player just drew between _rocket and pivot point, which we get by
subtracng (Point.subtract) those two points, has two perpendicular vectors:
one to the right (clockwise) that you get through Point.getRPerp and one to the
le (counterclockwise) that you get through Point.getPerp. We use the angle
of one of these vectors as the _rocket target rotaon so the rocket will rotate to
be at 90 degrees with the line drawn in LineContainer. And we nd the correct
perpendicular through the dot product of the _rocket current vector and one of
the perpendiculars (Point.dot).
What just happened?
I know. A lot of math and all at once! Thankfully, Cocos2d-x made it all much easier to handle.
We just added the logic that allows the player to draw lines and set new pivot points for
the _rocket sprite.
www.it-ebooks.info
On the Line – Rocket Through
[ 96 ]
The player will steer the _rocket sprite through the planets by giving the rocket a pivot
point to rotate around. And by releasing the _rocket from pivot points, the player will make
it move in a straight line again. All that logic gets managed here in the game's touch events.
And don't worry about the math. Though understanding how to deal with vectors is a very
useful tool in any game developer's toolbox, and you should denitely research the topic,
there are countless games you can sll build with lile or no math; so cheer up!
The game loop
It's me to create our good old cker! The main loop will be in charge of collision detecon,
updang the points inside _lineContainer, adjusng our _jet parcle system to our
_rocket sprite, and a few other things.
Time for action – adding the main loop
Let's implement our main update method.
1. In GameLayer.cpp, inside the update method, add the following lines:
if (!_running || _state != kGamePlay) return;
if (_lineContainer->getLineType() != LINE_NONE) {
_lineContainer->setTip (_rocket->getPosition() );
}
if (_rocket->collidedWithSides()) {
_lineContainer->setLineType ( LINE_NONE );
}
_rocket->update(dt);
//update jet particle so it follows rocket
if (!_jet->isActive()) _jet->resetSystem();
_jet->setRotation(_rocket->getRotation());
_jet->setPosition(_rocket->getPosition());
We check to see if we are not currently on pause. Then, if there is a line for our ship
that we need to show in _lineContainer, we update the line's tip point with the
_rocket current posion.
We run collision checks between _rocket and the screen sides, update the
_rocket sprite, and posion and rotate our _jet parcle system to align it with
the _rocket sprite.
www.it-ebooks.info
Chapter 5
[ 97 ]
2. Next we update _comet (its countdown, inial posion, movement, and collision
with _rocket if _comet is visible):
_cometTimer += dt;
float newY;
if (_cometTimer > _cometInterval) {
_cometTimer = 0;
if (_comet->isVisible() == false) {
_comet->setPositionX(0);
newY =
(float)rand()/((float)RAND_MAX/_screenSize.height *
0.6f) + _screenSize.height * 0.2f;
if (newY > _screenSize.height * 0.9f)
newY = _screenSize.height * 0.9f;
_comet->setPositionY(newY);
_comet->setVisible(true);
_comet->resetSystem();
}
}
if (_comet->isVisible()) {
//collision with comet
if (pow(_comet->getPositionX() - _rocket->getPositionX(),
2) + pow(_comet->getPositionY() - _rocket->getPositionY(),
2) <= pow (_rocket->getRadius() , 2)) {
if (_rocket->isVisible()) killPlayer();
}
_comet->setPositionX(_comet->getPositionX() + 50 * dt);
if (_comet->getPositionX() > _screenSize.width * 1.5f) {
_comet->stopSystem();
_comet->setVisible(false);
}
}
3. Next we update _lineContainer, and slowly reduce the opacity of the _rocket
sprite based on the _energy level in _lineContainer:
_lineContainer->update(dt);
_rocket->setOpacity(_lineContainer->getEnergy() * 255);
This will add a visual cue for the player that me is running out as the _rocket
sprite will slowly turn invisible.
www.it-ebooks.info
On the Line – Rocket Through
[ 98 ]
4. Run collision with planets:
for (auto planet : _planets) {
if (pow(planet->getPositionX() - _rocket->getPositionX(),
2)
+ pow(planet->getPositionY() - _rocket->getPositionY(), 2)
<= pow (_rocket->getRadius() * 0.8f + planet->getRadius()
* 0.65f, 2)) {
if (_rocket->isVisible()) killPlayer();
break;
}
}
5. And collision with the star:
if (pow(_star->getPositionX() - _rocket->getPositionX(), 2)
+ pow(_star->getPositionY() - _rocket->getPositionY(), 2)
<=
pow (_rocket->getRadius() * 1.2f, 2)) {
_pickup->setPosition(_star->getPosition());
_pickup->resetSystem();
if (_lineContainer->getEnergy() + 0.25f < 1) {
_lineContainer->setEnergy(_lineContainer->getEnergy() +
0.25f);
} else {
_lineContainer->setEnergy(1.0);
}
_rocket->setSpeed(_rocket->getSpeed() + 2);
if (_rocket->getSpeed() > 70) _rocket->setSpeed(70);
_lineContainer->setEnergyDecrement(0.0002f);
SimpleAudioEngine::getInstance()->playEffect("pickup.
wav");
resetStar();
int points = 100 - _timeBetweenPickups;
if (points < 0) points = 0;
_score += points;
_scoreDisplay->setString(String::createWithFormat("%i",
_score)->getCString());
_timeBetweenPickups = 0;
}
When we collect _star, we acvate the _pickup parcle system on the spot
where _star was, we ll up the player's energy level, we make the game slightly
harder, and we immediately reset _star to its next posion to be collected again.
The score is based on the me it took the player to collect _star.
www.it-ebooks.info
Chapter 5
[ 99 ]
6. And we keep track of this me on the last lines of update where we also check the
energy level:
_timeBetweenPickups += dt;
if (_lineContainer->getEnergy() == 0) {
if (_rocket->isVisible()) killPlayer();
}
What just happened?
We added the main loop to our game and nally have all the pieces talking to each other.
But you probably noced quite a few calls to methods we have not implemented yet,
such as killPlayer and resetStar. We'll nish our game logic with these methods.
Kill and reset
It's that me again! Time to kill our player and reset the game! We also need to move the
_star sprite to a new posion whenever it's picked up by the player.
Time for action – adding our resets and kills
We need to add logic to restart our game and to move our pickup star to a new posion.
But rst, let's kill the player!
1. Inside the killPlayer method, add the following lines:
void GameLayer::killPlayer() {
SimpleAudioEngine::getInstance()->stopBackgroundMusic();
SimpleAudioEngine::getInstance()->stopAllEffects();
SimpleAudioEngine::getInstance()->playEffect("shipBoom.wav");
_boom->setPosition(_rocket->getPosition());
_boom->resetSystem();
_rocket->setVisible(false);
_jet->stopSystem();
_lineContainer->setLineType ( LINE_NONE );
_running = false;
_state = kGameOver;
_gameOver->setVisible(true);
_pauseBtn->setVisible(false);
}
www.it-ebooks.info
On the Line – Rocket Through
[ 100 ]
2. Inside resetStar, add the following lines:
void GameLayer::resetStar() {
Point position = _grid[_gridIndex];
_gridIndex++;
if (_gridIndex == _grid.size()) _gridIndex = 0;
//reset star particles
_star->setPosition(position);
_star->setVisible(true);
_star->resetSystem();
}
3. And nally, our resetGame method:
void GameLayer::resetGame () {
_rocket->setPosition(Vec2(_screenSize.width * 0.5f,
_screenSize.height * 0.1f));
_rocket->setOpacity(255);
_rocket->setVisible(true);
_rocket->reset();
_cometInterval = 4;
_cometTimer = 0;
_timeBetweenPickups = 0.0;
_score = 0;
_scoreDisplay->setString(String::createWithFormat("%i", _
score)->getCString());
_lineContainer->reset();
//shuffle grid cells
std::random_shuffle(_grid.begin(), _grid.end());
_gridIndex = 0;
resetStar();
_warp->stopSystem();
_running = true;
SimpleAudioEngine::getInstance()->playBackgroundMusic("backgro
und.mp3", true);
SimpleAudioEngine::getInstance()->stopAllEffects();
SimpleAudioEngine::getInstance()->playEffect("rocket.wav",
true);
}
www.it-ebooks.info
Chapter 5
[ 101 ]
What just happened?
That's it. We're done. It took more math than most people are comfortable with. But what
can I tell you, I just love messing around with vectors!
Now, let's move on to Android!
Time for action – running the game in Android
Follow these steps to deploy the game to Android:
1. Open the manifest le and set the app orientaon to portrait.
2. Next, open the Android.mk le in a text editor.
3. Edit the lines in LOCAL_SRC_FILES to read:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/GameSprite.cpp \
../../Classes/LineContainer.cpp \
../../Classes/Rocket.cpp \
../../Classes/GameLayer.cpp
4. Import the game into Eclipse and build it.
5. Save and run your applicaon. This me, you can try out dierent size screens
if you have the devices.
What just happened?
You now have Rocket Through running in Android.
Have a go hero
Add logic to the resetStar method so that the new posion picked is not too close to the
_rocket sprite. So, make the funcon a recurrent one unl a proper posion is picked.
And take the warp parcle system, which right now does not do a whole lot, and use it as a
random teleport eld so that the rocket may get sucked in by a randomly placed warp and
moved farther away from the target star.
www.it-ebooks.info
On the Line – Rocket Through
[ 102 ]
Summary
Congratulaons! You now have enough informaon about Cocos2d-x to produce awesome
2D games. First sprites, then acons, and now parcles.
Parcles make everything look shiny! They are easy to implement and are a very good way to
add an extra bit of animaon to your game. But it's very easy to overdo it, so be careful. You
don't want to give your players epilepc ts. Also, running too many parcles at once could
stop your game in its tracks.
In the next chapter, we'll see how to use Cocos2d-x to quickly test and develop game ideas.
www.it-ebooks.info
[ 103 ]
Quick and Easy Sprite – Victorian
Rush Hour
In our fourth example of a game built with Cocos2d-x, I'll show you a simple
technique for rapid prototyping. Often in game development, you want to test
the core ideas of your game as soon as possible, because a game may sound
fun in your head but in reality it just doesn't work. Rapid prototyping techniques
allow you to test your game as early as possible in the development process as
well as build up on the good ideas.
Here's what you'll learn:
How to quickly create placeholder sprites
How to code collisions for a plaorm game
How to create varied terrain for a side-scroller
The game – Victorian Rush Hour
In this game (Victorian Rush Hour), you control a cyclist in Victorian London trying to avoid
rush-hour trac on his way home. For reasons no one can explain, he's riding his bike on top
of the buildings. As the player, it is your job to ensure he makes it.
The controls are very simple: you tap the screen to make the cyclist jump and while he's in
the air, if you tap the screen again, the cyclist will open his trusty umbrella, either slowing
his descent or adding a boost to his jump.
6
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 104 ]
This game is of a type commonly known as a dash game or endless runner, a genre that
has become increasingly popular online and on various app stores. Usually in these types
of games you, the developer, have two choices: either make the terrain the main obstacle
and challenge in the game, or make what's added to the terrain the main challenge
(enemies, pick-ups, obstacles, and so on). With this game, I decided on the rst opon.
So our challenge is to create a game where the terrain is the enemy but not an
unbeatable one.
The game settings
The game is a universal applicaon, designed for the iPad rena display but with support for
other display sizes. It is played in the landscape mode and it does not support multouch.
Rapid prototyping with Cocos2d-x
The idea behind this is to create sprites as placeholders for your game elements as quickly
as possible, so you can test your game ideas and rene them. Every game in this book was
inially developed in the way I'm about to show you, with simple rectangles in place of
textured sprites.
The technique shown here allows you to create rectangles of any size and of any color to be
used in your game logic:
www.it-ebooks.info
Chapter 6
[ 105 ]
Time for action – creating placeholder sprites
So let me show you how to do that:
1. Go ahead and download the 4198_06_START_PROJECT.zip le if you haven't
done so already.
2. When you open the project in Xcode, you will see all the classes we'll need for the
game, and we'll go over them in a second. But for now, just go to GameLayer.cpp.
3. Scroll down to the last createGameScreen method and add the following lines:
auto quickSprite = Sprite::create("blank.png");
quickSprite->setTextureRect(Rect(0, 0, 100, 100));
quickSprite->setColor(Color3B(255,255,255));
quickSprite->setPosition(Vec2(_screenSize.width * 0.5, _
screenSize.height * 0.5));
this->addChild(quickSprite);
And that's it. The sprite is created with a texture called blank.png. This is a 1 x 1
pixel white square you will nd in the Resources folder. Then we set the size of
the sprite's texture rectangle to 100 x 100 pixels (setTextureRect), and ll it with
a white color (setColor). By resizing the texture rectangle, we in eect resize the
sprite. If you run the game now, you should see a white square smack in the middle
of the screen.
4. Now delete the previous lines and replace them with these:
_gameBatchNode = SpriteBatchNode::create("blank.png", 200);
this->addChild(_gameBatchNode, kMiddleground);
This creates _gameBatchNode that uses as its source texture the same blank.png
le. Now we are ready to place as many rectangles inside _gameBatchNode
as we'd like, and set a dierent color for each one of them if we want. We can, in
other words, build an enre test game with one ny image. Which is what we'll
proceed to do now.
5. So, to nish up here, add these last lines:
_terrain = Terrain::create();
_gameBatchNode->addChild(_terrain, kMiddleground);
_player = Player::create();
_gameBatchNode->addChild(_player, kBackground);
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 106 ]
What just happened?
We just created a placeholder sprite we can use to test gameplay ideas quickly and
painlessly. And we created our game's two main objects: the Player and Terrain object.
These are empty shells at the moment, but we'll start working on them next. But rst let's
go over the dierent game elements.
The Player object
This represents our cyclist. It will jump, oat, and collide with the _terrain object.
Its x speed is passed to the _terrain object causing the Terrain object to move,
side scrolling to the le of the screen.
The Player object derives, once again, from a GameSprite class. This one has geers
and seers for next posion, vector of movement, and the sprite's width and height.
The Player interface has inline helper methods to retrieve informaon about its rectangle
boundaries related to its current posion (le, right, top, boom), and its next posion
(next_left, next_right, next_top, next_bottom). These will be used in collision
detecon with the _terrain object.
The Block object
These objects form the individual pieces of the _terrain object. They can take the shape
of a building, or an empty gap between buildings. We'll have four dierent types of buildings,
which later will represent four dierent types of textures when we nally bring in our sprite
sheets. These blocks can have dierent widths and heights.
Block also derives from GameSprite and it also has inline helper methods to retrieve
informaon about its boundaries, but only in relaon to its current posion, since Block
doesn't technically move.
The terrain object
This object contains the individual Block objects that form the landscape. It contains just
enough Block objects to ll the screen, and as the _terrain object scrolls to the le, the
Block objects that leave the screen are moved to the far right side of the _terrain and
reused as new blocks, ensuring connuous scrolling.
www.it-ebooks.info
Chapter 6
[ 107 ]
The _terrain object is also responsible for collision checks with the _player object,
since it has quick access to all informaon we'll need for collision detecon; namely the list
of blocks currently on the screen, their size, type, and posion. Our main loop then will call
on the Terrain object to test for collision with the player object.
Let's work on these main objects, starng with the Player object.
Time for action – coding the player
Open up the Player.cpp class.
1. The _player object is created through a stac method that uses our blank.png
le to texture the sprite. That method also makes a call to initPlayer, and this is
what you should type for that method:
void Player::initPlayer () {
this->setAnchorPoint(Vec2(0.5f, 1.0f));
this->setPosition(Vec2(_screenSize.width * 0.2f, _
nextPosition.y));
_height = 228;
_width = 180;
this->setTextureRect(Rect(0, 0, _width, _height));
this->setColor(Color3B(255,255,255));
}
The _player object will have its registraon point at the top of the sprite.
The reason behind this top center anchor point has much more to do with the
way the _player object will be animated when oang, than with any collision
logic requirements.
2. Next comes setFloating:
void Player::setFloating (bool value) {
if (_floating == value) return;
if (value && _hasFloated) return;
_floating = value;
if (value) {
_hasFloated = true;
_vector.y += PLAYER_JUMP * 0.5f;
}
}
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 108 ]
The _hasFloated property will ensure the player can only open the umbrella once
while in the air. And when we set _floating to true, we give the _player.y
vector a boost.
3. We begin the update method of _player with:
void Player::update (float dt) {
if (_speed + P_ACCELERATION <= _maxSpeed) {
_speed += P_ACCELERATION;
} else {
_speed = _maxSpeed;
}
_vector.x = _speed;
The game will increase _maxSpeed of the _player object as me goes on, making
the game more dicult. These rst lines make the change from the _players
current _speed up to _maxSpeed a bit smoother and not an immediate change.
Victorian Rush Hour has no levels, so it's important to figure out a way to
make it incrementally harder to play, and yet not impossible. Finding that
sweet spot in your logic may take some time and it's one more reason to
test game ideas as soon as possible. Here we make the game harder by
increasing the player's speed and the size of the gaps between buildings.
These are updated inside a countdown in the main loop.
4. Next, we update the _player object based on its _state of movement:
switch (_state) {
case kPlayerMoving:
_vector.y -= FORCE_GRAVITY;
if (_hasFloated) _hasFloated = false;
break;
case kPlayerFalling:
if (_floating ) {
_vector.y -= FLOATNG_GRAVITY;
_vector.x *= FLOATING_FRICTION;
} else {
_vector.y -= FORCE_GRAVITY;
_vector.x *= AIR_FRICTION;
_floatingTimer = 0;
}
break;
case kPlayerDying:
www.it-ebooks.info
Chapter 6
[ 109 ]
_vector.y -= FORCE_GRAVITY;
_vector.x = -_speed;
this->setPositionX(this->getPositionX() + _vector.x);
break;
}
We have dierent values for gravity and fricon depending on move state.
We also have a me limit for how long the _player object can be oang,
and we reset that mer when the _player object is not oang. If the _player
object is dying (collided with a wall), we move the _player object backward and
downward unl it leaves the screen.
5. We nish with:
if (_jumping) {
_state = kPlayerFalling;
_vector.y += PLAYER_JUMP * 0.25f;
if (_vector.y > PLAYER_JUMP ) _jumping = false;
}
if (_vector.y < -TERMINAL_VELOCITY)
_vector.y = -TERMINAL_VELOCITY;
_nextPosition.y = this->getPositionY() + _vector.y;
if (_floating) {
_floatingTimer += dt;
if (_floatingTimer > _floatingTimerMax) {
_floatingTimer = 0;
this->setFloating(false);
}
}
}
When the player presses the screen for a jump, we shouldn't make the sprite
jump immediately. Changes in state should always happen smoothly. So we have
a boolean property in _player called _jumping. It is set to true when the player
presses the screen and we slowly add the jump force to _vector.y. So the longer
the player presses the screen, the higher the jump will be and a quick tap will result
in a shorter jump. This is a nice feature to add to any plaorm game.
We next limit the y speed with a terminal velocity, update the next posion of the
_player object, and update the oang mer if _player is oang.
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 110 ]
What just happened?
The _player object is updated through a series of states. Touching the screen will make
changes to this _state property, as will the results of collision checking with _terrain.
Now let's work on the Block class.
Time for action – coding the Block object
Once again a stac method, create, will use blank.png to create our Block sprite.
Only this me, we don't actually change the texture rectangle for Block inside create:
1. The Block object is properly textured inside the setupBlock method:
void Block::setupBlock (int width, int height, int type) {
_type = type;
_width = width * _tileWidth;
_height = height * _tileHeight;
this->setAnchorPoint(Vec2(0,0));
this->setTextureRect(Rect(0, 0, _width, _height));
A Block object's appearance will be based on its type, width, and height.
The Block sprite's registraon point is set to top le. And we nally change the
Block object's texture rectangle size here.
2. Then we set the Block object's color based on type:
switch (type) {
case kBlockGap:
this->setVisible(false);
return;
case kBlock1:
this->setColor(Color3B(200,200,200));
break;
case kBlock2:
this->setColor(Color3B(150,150,150));
break;
www.it-ebooks.info
Chapter 6
[ 111 ]
case kBlock3:
this->setColor(Color3B(100,100,100));
break;
case kBlock4:
this->setColor(Color3B(50,50,50));
break;
}
this->setVisible(true);
}
kBlockGap means there is no building, just a gap the _player object must jump.
We make the block invisible in that case and return from the funcon. So again,
gaps are actually types of blocks in our logic.
In this test version, the dierent types of buildings are represented with dierent colors.
Later we'll use dierent textures.
What just happened?
The Block object is very simple. We just need its values for _width and _height
whether it's a gap or not, so we can properly run collision detecon with these objects.
Planning the Terrain class
Before we jump to coding the Terrain class, we need to discuss a few things
regarding randomness.
It is a very common mistake among game developers to confuse randomness with
variableness, and very important to know when you need what.
A random number can be anything. 1234 is a random series of numbers. And the next
me you want a random series of numbers and you once again get 1234 this will be just
as random as the previous one. But not varied.
If you decide to build a random terrain, you will probably be disappointed in the result as
it won't necessarily be varied. Also, remember that we need to make the terrain the key
challenge of the game; but this means it can be neither too easy nor too dicult. True
randomness would not allow us enough control here, or worse, we would end up with a long
list of condionals to make sure we have the correct combinaon of blocks, and that would
result in at least one recurrent funcon inside our main loop, which is not a good idea.
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 112 ]
We need instead to control the results and their variableness by applying our own
paerns to them.
So we'll apply this logic of paerns to our _terrain object, forming a kind of pool of
proper random choices. We'll use four arrays to store possible results in our decision
making, and we'll shue three of these arrays during the game to add the "randomness"
feel to our terrain.
These arrays are:
int patterns[] = {1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3,3,3};
This holds the informaon of how many buildings (Blocks) we have in a row, between gaps.
You can easily change the patterns value just by adding new values or by increasing or
reducing the number of mes one value appears. So here we're making a terrain with far
more groupings of two buildings between gaps, than groups of three or one.
Next, consider the following lines:
int widths[] = {2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4};
int heights[] =
{0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2,2,2,3,3,3,3,3,3,4};
The preceding lines specify the widths and heights of each new building. These will be
mulplied with the le size determined for our game to get the nal width and height
values as you saw in Block:setupBlock.
We'll use a 0 value for height to mean there is no change in height from the previous
building. A similar logic could be easily applied to widths.
And nally:
int types[] =
{1,2,3,4,1,3,2,4,3,2,1,4,2,3,1,4,2,3,1,2,3,2,3,4,1,2,4,3,1,3,1,4,2,4,
2,1,2,3};
These are building types and this array will not be shued unlike the three previous ones,
so this is the patterns array of types we'll use throughout the game and it will loop
connuously. You can make it as long as you wish.
www.it-ebooks.info
Chapter 6
[ 113 ]
Building the terrain object
So every me we need to create a new block, we'll set it up based on the informaon
contained in these arrays.
This gives us far more control over the terrain, so that we don't create impossible
combinaons of obstacles for the player: a common mistake in randomly-built terrain
for dash games.
But at the same me, we can easily expand this logic to t every possible need. For instance,
we could apply level logic to our game by creang mulple versions of these arrays, so as
the game gets harder, we begin sampling data from arrays that contain parcularly hard
combinaons of values.
And we can sll use a condional loop to rene results even further and I'll give you at
least one example of this.
The values you saw in the patterns arrays will be stored inside the lists called
_blockPattern, _blockWidths, _blockHeights, and _blockTypes.
The Terrain class then takes care of building the game's terrain in three stages.
First we inialize the _terrain object, creang among other things a pool for Block
objects. Then we add the rst blocks to the _terrain object unl a minimum width is
reached to ensure the whole screen is populated with Blocks. And nally we distribute
the various block objects.
Time for action – initializing our Terrain class
We'll go over these steps next:
1. The rst important method to implement is initTerrain:
void Terrain::initTerrain () {
_increaseGapInterval = 5000;
_increaseGapTimer = 0;
_gapSize = 2;
//init object pools
for (int i = 0; i < 20; i++) {
auto block = Block::create();
this->addChild(block);
_blockPool.pushBack(block);
}
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 114 ]
_minTerrainWidth = _screenSize.width * 1.5f;
random_shuffle(_blockPattern.begin(), _blockPattern.end());
random_shuffle(_blockWidths.begin(), _blockWidths.end());
random_shuffle(_blockHeights.begin(), _blockHeights.end());
this->addBlocks(0);
}
We have a mer to increase the width of gaps (we begin with gaps two les long).
We create a pool for blocks so we don't instanate any during the game. And 20
blocks is more than enough for what we need.
The blocks we are currently using in the terrain will be stored inside a
_blocks vector.
We determine that the minimum width the _terrain object must have is 1.5
mes the screen width. We'll keep adding blocks unl the _terrain object
reaches this minimum width. We end by shuing the patterns arrays and
adding the blocks.
2. The addBlocks method should look like this:
void Terrain::addBlocks(int currentWidth) {
while (currentWidth < _minTerrainWidth)
{
auto block = _blockPool.at(_blockPoolIndex);
_blockPoolIndex++;
if (_blockPoolIndex == _blockPool.size()) {
_blockPoolIndex = 0;
}
this->initBlock(block);
currentWidth += block->getWidth();
_blocks.pushBack(block);
}
this->distributeBlocks();
}
The logic inside the while loop will connue to add blocks unl currentWidth
of the _terrain object reaches _minTerrainWidth. Every new block we
retrieve from the pool in order to reach _minTerrainWidth gets added to the
_blocks vector.
www.it-ebooks.info
Chapter 6
[ 115 ]
3. Blocks are distributed based on their widths:
void Terrain::distributeBlocks() {
int count = (int) _blocks.size();
int i;
for (i = 0; i < count; i++) {
auto block = _blocks.at(i);
if (i != 0) {
auto prev_block = _blocks.at(i - 1);
block->setPositionX( prev_block->getPositionX() + prev_
block->getWidth());
}
else
{
block->setPositionX ( 0 );
}
}
}
What just happened?
Terrain is a container of Blocks, and we just added the logic that will add a new block
object to this container. Inside addBlocks, we call an initBlock method, which will
use the informaon from our patterns arrays to inialize each block used in the terrain.
It is this method we'll implement next.
Time for action – initializing our Blocks object
Finally, we will discuss the method that inializes the blocks based on our patterns array:
1. So inside the Terrain class, we start the initBlock method as follows:
void Terrain::initBlock(Block * block) {
int blockWidth;
int blockHeight;
int type = _blockTypes[_currentTypeIndex];
_currentTypeIndex++;
if (_currentTypeIndex == _blockTypes.size()) {
_currentTypeIndex = 0;
}
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 116 ]
Begin by determining the type of building we are inializing. See how we loop
through the _blockTypes array using the index stored in _currentTypeIndex.
We'll use a similar logic for the other patterns arrays.
2. Then, let's start building our blocks:
if (_startTerrain) {
//...
} else {
_lastBlockHeight = 2;
_lastBlockWidth = rand() % 2 + 2;
block->setupBlock (_lastBlockWidth, _lastBlockHeight, type);
}
The player must tap the screen to begin the game (_startTerrain). Unl then,
we show buildings with the same height (two les) and random width:
We will store _lastBlockHeight and _lastBlockWidth because the
more informaon we have about the terrain the beer we can apply our own
condions to it, as you will see in a moment.
3. Consider that we are set to _startTerrain:
if (_startTerrain) {
if (_showGap) {
int gap = rand() % _gapSize;
if (gap < 2) gap = 2;
block->setupBlock (gap, 0, kBlockGap);
_showGap = false;
} else {
//...
www.it-ebooks.info
Chapter 6
[ 117 ]
In the following screenshot, you can see the dierent widths used for our blocks:
The informaon inside _blockPattern determines how many buildings we
show in a row, and once a series is completed, we show a gap by seng the
boolean value of _showGap to true. A gap's width is based on the current value
of _gapSize, which may increase as the game gets harder and it can't be less than
two mes the le width.
4. If we are not creang a gap this me, we determine the width and height of the new
block based on the current indexed values of _blockWidths and _blockHeights:
} else {
blockWidth = _blockWidths[_currentWidthIndex];
_currentWidthIndex++;
if (_currentWidthIndex == _blockWidths.size()) {
random_shuffle(_blockWidths.begin(),
_blockWidths.end());
_currentWidthIndex = 0;
}
if (_blockHeights[_currentHeightIndex] != 0) {
//change height of next block
blockHeight = _blockHeights[_currentHeightIndex];
//if difference too high, decrease it
if (blockHeight - _lastBlockHeight > 2 && _gapSize ==
2)
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 118 ]
{
blockHeight = 1;
}
} else {
blockHeight = _lastBlockHeight;
}
_currentHeightIndex++;
if (_currentHeightIndex == _blockHeights.size()) {
_currentHeightIndex = 0;
random_shuffle(_blockHeights.begin(),
_blockHeights.end());
}
block->setupBlock (blockWidth, blockHeight, type);
_lastBlockWidth = blockWidth;
_lastBlockHeight = blockHeight;
Noce how we reshue the arrays once we are done iterang through them
(random_shuffle).
We use _lastBlockHeight to apply an extra condion to our terrain. We don't
want the next block to be too tall in relaon to the previous building, at least not
in the beginning of the game, which we can determine by checking the value for
_gapSize, which is only increased when the game gets harder.
And if the value from _blockHeights is 0, we don't change the height of the new
building and use instead the same value from _lastBlockHeight.
5. We nish by updang the count in the current series of buildings to determine
whether we should show a gap next, or not:
//select next block series pattern
_currentPatternCnt++;
if (_currentPatternCnt > _blockPattern[_currentPatternIndex]) {
_showGap = true;
//start new pattern
_currentPatternIndex++;
if (_currentPatternIndex == _blockPattern.size()) {
random_shuffle(_blockPattern.begin(),
_blockPattern.end());
_currentPatternIndex = 0;
}
_currentPatternCnt = 1;
}
}
www.it-ebooks.info
Chapter 6
[ 119 ]
What just happened?
We nally got to use our patterns arrays and build the blocks inside the terrain.
The possibilies are endless here in how much control we can have in building our blocks.
But the key idea here is to make sure the game does not become ridiculously hard, and I
advise you to play some more with the values to achieve even beer results (don't take my
choices for granted).
Before we tackle collision, let's add the logic to move and reset the terrain.
Time for action – moving and resetting
We move the terrain inside the move method.
1. The move method receives as a parameter the amount of movement in the x axis:
void Terrain::move (float xMove) {
if (xMove < 0) return;
if (_startTerrain) {
if (xMove > 0 && _gapSize < 5)
_increaseGapTimer += xMove;
if (_increaseGapTimer > _increaseGapInterval) {
_increaseGapTimer = 0;
_gapSize += 1;
}
}
this->setPositionX(this->getPositionX() - xMove);
auto block = _blocks.at(0);
if (_position.x + block->getWidth() < 0) {
auto firstBlock = _blocks.at(0);
_blocks.erase(0);
_blocks.pushBack(firstBlock);
_position.x += block->getWidth();
float width_cnt = this->getWidth() - block->getWidth() - (
_blocks.at(0))->getWidth();
this->initBlock(block);
this->addBlocks(width_cnt);
}
}
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 120 ]
The value for xMove comes from the _player speed.
We start by updang the mer that will make the gaps wider. Then we move the
terrain to the le. If aer moving the terrain, a block leaves the screen, we move
the block back to the end of the _blocks vector and reinialize it as a new block
through initBlock.
We make a call to addBlocks just in case the reinialized block made the total
width of the terrain less than the minimum width required.
2. Next, our reset method:
void Terrain::reset() {
this->setPosition(Vec2(0,0));
_startTerrain = false;
int currentWidth = 0;
for (auto block : _blocks) {
this->initBlock(block);
currentWidth += block->getWidth();
}
while (currentWidth < _minTerrainWidth) {
auto block = _blockPool.at(_blockPoolIndex);
_blockPoolIndex++;
if (_blockPoolIndex == _blockPool.size()) {
_blockPoolIndex = 0;
}
_blocks.pushBack(block);
this->initBlock(block);
currentWidth += block->getWidth();
}
this->distributeBlocks();
_increaseGapTimer = 0;
_gapSize = 2;
}
The reset method is called whenever we restart the game. We move _terrain
back to its starng point, and we reinialize all the current Block objects currently
inside the _terrain object. This is done because we are back to _startTerrain
= false, which means all blocks should have the same height and a random width.
If at the end of the reset we need more blocks to reach _minTerrainWidth,
we add them accordingly.
www.it-ebooks.info
Chapter 6
[ 121 ]
What just happened?
We can now move the _terrain object and all the blocks it contains, and we can restart
the process all over again if we need to.
Once again, using the container behavior of nodes simplied our job tremendously.
When you scroll the terrain, you scroll all the Block objects it contains.
So we are nally ready to run collision logic.
Platform collision logic
We have in place all the informaon we need to check for collision through the inline
methods found in Player and Block.
In this game, we'll need to check collision between the _player object's boom side and
the block object's top side, and between the _player object's right side and the Block
class' le side. And we'll do that by checking the _player object's current posion and its
next posion. We are looking for these condions:
The diagram represents the condions for boom side collision, but the same idea applies
to right side collision.
In the current posion, the _player object must be above the top of the block or
touching it. In the next posion, the _player object must be either touching the top of
the block or already overlapping it (or has moved past it altogether). This would mean a
collision has occurred.
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 122 ]
Time for action – adding collision detection
Let's see how that translates to code:
1. Sll in Terrain.cpp:
void Terrain::checkCollision (Player * player) {
if (player->getState() == kPlayerDying) return;
bool inAir = true;
for (auto block : _blocks) {
if (block->getType() == kBlockGap) continue;
//if within x, check y (bottom collision)
if (player->right() >= this->getPositionX() + block->left()
&& player->left() <= this->getPositionX() + block->right()) {
if (player->bottom() >= block->top() && player->next_
bottom() <= block->top() && player->top() > block->top()) {
player->setNextPosition(Vec2(player-
>getNextPosition().x, block->top() + player->getHeight()));
player->setVector ( Vec2(player->getVector().x, 0) );
player->setRotation(0.0);
inAir = false;
break;
}
}
}
First we state that the _player object is currently falling with inAir = true;
we'll let the collision check determine if this will remain true or not.
We don't check the collision if _player is dying and we skip collision checks with
any gap blocks.
We check collision on the y axis, which here means the boom of the _player and
top of the block. We rst need to determine if the _player object is within range of
the block we want to check against collision. This means the center of the _player
object must be between the le and right side of the block; otherwise, the block is
too far from the _player object and may be ignored.
Then we run a basic check to see if there is a collision between the _player
object's current posion and next posion, using the condions I explained earlier.
If so, we x the _player object's posion and change its y vector speed to 0 and
we determine that inAir = false aer all, the _player object has landed.
www.it-ebooks.info
Chapter 6
[ 123 ]
2. Next we check collision on the x axis, meaning the right side of the _player object
with the le side of the blocks:
for (auto block : _blocks) {
if (block->getType() == kBlockGap) continue;
//now if within y, check x (side collision)
if ((player->bottom() < block->top() && player->top() >
block->bottom()) || (player->next_bottom() < block->top() &&
player->next_top() > block->bottom())) {
if (player->right() >= this->getPositionX() + block-
>getPositionX()
&& player->left() < this->getPositionX() + block->getPositionX())
{
player->setPositionX( this->getPositionX() +
block->getPositionX() - player->getWidth() * 0.5f );
player->setNextPosition(Vec2(this->getPositionX() +
block->getPositionX() - player->getWidth() * 0.5f,
player->getNextPosition().y));
player->setVector ( Vec2(player->getVector().x * -0.5f,
player->getVector().y) );
if (player->bottom() + player->getHeight() * 0.2f <
block->top()) {
player->setState(kPlayerDying);
return;
}
break;
}
}
}
Similar steps are used to determine if we have a viable block or not.
If we do have a side collision, the _player state is changed to kPlayerDying,
we reverse its x speed so the _player state will move to the le and o the
screen, and we return from this method.
3. We end by updang the _player object's state based on our collision results:
if (inAir) {
player->setState(kPlayerFalling);
} else {
player->setState(kPlayerMoving);
player->setFloating (false);
}
}
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 124 ]
What just happened?
We just added the collision logic to our plaorm game. As we did in our rst game,
Air Hockey, we test the player's current posion for collision as well as its next posion
to determine if a collision occurred between the current iteraon and the next one.
The test simply looks for overlaps between the player's and block's boundaries.
Adding the controls
It is fairly common in a dash game such as this to have very simple controls. Oen the player
must only press the screen for jumping. But we spiced things up a bit, adding a oang state.
And remember we want smooth transions between states, so pay aenon to how jumping
is implemented: not by immediately applying a force to the player's vector but by simply
changing a boolean property and leng the _player object's update method handle the
change smoothly.
We'll handle the touch events next.
Time for action – handling touches
Let's go back to GameLayer.cpp and add our game's nal touches (pun intended).
1. First we work on our onTouchBegan method:
bool GameLayer::onTouchBegan(Touch* touch, Event* event) {
if (!_running) {
if (_player->getState() == kPlayerDying) {
_terrain->reset();
_player->reset();
resetGame();
}
return true;
}
If we are not running the game and the _player object died, we reset the
game on touch.
www.it-ebooks.info
Chapter 6
[ 125 ]
2. Next, if the terrain has not started, insert the following:
if (!_terrain->getStartTerrain()) {
_terrain->setStartTerrain ( true );
return true;
}
Remember that at rst the buildings are all the same height and there are
no gaps. Once the player presses the screen, we begin changing that through
setStartTerrain.
3. We nish with:
if (touch) {
if (_player->getState() == kPlayerFalling) {
_player->setFloating ( _player->getFloating() ? false
: true );
} else {
if (_player->getState() != kPlayerDying) _player-
>setJumping(true);
}
return true;
}
return false;
}
Now we are in play, and if the _player object is falling, we either open or close
the umbrella, whichever the case may be, through a call to setFloating.
And if the _player object is not falling, nor dying, we make it jump with
setJumping(true).
4. With touches ended, we just need to stop any jumps:
void GameLayer::onTouchEnded(Touch* touch, Event* event) {
_player->setJumping(false);
}
What just happened?
We added the logic for the game's controls. The _player object will change to oang if
currently falling or to jumping if currently riding on top of a building.
It's me to add our main game loop.
www.it-ebooks.info
Quick and Easy Sprite – Victorian Rush Hour
[ 126 ]
Time for action – coding the main loop
Finally, it's me for the last part in our logic.
1. Inside GameLayer.cpp:
void GameLayer::update(float dt) {
if (!_running) return;
if (_player->getPositionY() < -_player->getHeight() ||
_player->getPositionX() < -_player->getWidth() * 0.5f)
{
_running = false;
}
If the _player object is o screen, we stop the game.
2. Now update all the elements, posions and check for collision:
_player->update(dt);
_terrain->move(_player->getVector().x);
if (_player->getState() != kPlayerDying)
_terrain->checkCollision(_player);
_player->place();
3. Move _gameBatchNode in relaon to the _player object:
if (_player->getNextPosition().y > _screenSize.height * 0.6f) {
_gameBatchNode->setPositionY( (_screenSize.height *
0.6f - _player->getNextPosition().y) * 0.8f);
} else {
_gameBatchNode->setPositionY ( 0 );
}
www.it-ebooks.info
Chapter 6
[ 127 ]
4. Make the game more dicult as me goes on by increasing the _player object's
maximum speed:
if (_terrain->getStartTerrain() && _player->getVector().x > 0) {
_speedIncreaseTimer += dt;
if (_speedIncreaseTimer > _speedIncreaseInterval) {
_speedIncreaseTimer = 0;
_player->setMaxSpeed (_player->getMaxSpeed() + 4);
}
}
}
What just happened?
We have our test game in place. From here, we can test our terrain paerns, our speeds,
and our general gameplay to nd spots where things could be improved.
We should check in parcular whether the game gets too hard too fast or whether we have
combinaons of buildings that are just impossible to get past.
I nd, for instance, that starng with larger groups of buildings, say four or ve, and then
slowly reducing them to two and one between gaps can make the game even more fun to
play, so the paerns could be changed to reect that idea.
Summary
Every game has a simple idea for its gameplay at its core. But oen, this idea needs a
whole lot of tesng and improvement before we can determine whether it's fun or not,
which is why rapid prototyping is vital.
We can use Cocos2d-x to quickly test core gameplay ideas and run them in the simulator
or on a device in a maer of minutes.
Also, the techniques shown here can be used to build interface elements (such as the
energy bar from our previous game) as well as an enre game! If you don't believe me,
check out the game Square Ball in an App Store near you.
Now, with all the logic for gameplay in its proper place, we can proceed to making this game
look good! We'll do that in the next chapter.
www.it-ebooks.info
www.it-ebooks.info
[ 129 ]
Adding the Looks – Victorian
Rush Hour
Now that we have our test game, it's time to make it all pretty! We'll go over
the new sprite elements added to make the game look nice, and cover a new
topic or two. However, by now, you should be able to understand everything in
the final code of this project.
So you can sit back and relax a bit. This time, I won't make you type so
much. Promise!
In this chapter, you will learn:
How to use mulple sprites to texture a led terrain
How to use mulple containers inside SpriteBatchNode
How to create a parallax eect
How to add a menu to your game
How to build a game tutorial
7
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 130 ]
Victorian Rush Hour – the game
Download the 4198_07_START_PROJECT.zip le from this book's Support page
(www.packtpub.com/support) and run the project in Xcode. You should be able to
recognize all the work we did in the test version, and pinpoint the few extra elements.
You will also see that nothing was added to the actual gameplay.
In Victorian Rush Hour, I wanted to make the terrain the main challenge in the game,
but I also wanted to show you how easily you can add new elements to the buildings
and interact with them.
You can later use the same logic to add enemies, obstacles, or pickups for the cyclist sprite.
All you need to do really is extend the collision detecon logic to check for the new items.
You could, for instance, add umbrellas as pickups, and every me the _player object
oated, he would be minus one umbrella.
Next, I'll list the new elements added to the game.
New sprites
Quite a few sprites were added to our game:
There is a group of cyclists at the beginning of the game represenng the trac.
We add a background layer (cityscape) and a foreground layer (lamp posts)
to help us with our parallax eect. The clouds in the background are also part of
the eect.
We add chimneys to the buildings. These pu smoke as the player taps the screen.
www.it-ebooks.info
Chapter 7
[ 131 ]
And, of course, the usual stu—score label, game logo, and a game over message.
In the following screenshot, you can see an image of the player sprite and the
group of cyclists:
Animations
Some of the sprites now run animaon acons:
The _player sprite runs an animaon showing him riding the bicycle
(_rideAnimation).
I also added our old friend, the swinging animaon, shown when the _player
sprite is oang (_floatAnimation). This is the reason for the odd registraon
point on the cyclist sprite, as the swing animaon looks beer if the sprite's anchor
point is not centered.
Our group of cyclists is also animated during the introducon secon of the game,
and is moved oscreen when the game starts (_jamAnimate, _jamMove).
We show a pu of smoke coming out of the chimneys whenever the player jumps.
This animaon is stored inside the new Block.cpp class and it's created through
a series of acons, including a frame animaon (_puffAnimation, _puffSpawn,
_puffMove, _puffFade, and _puffScale).
In GameLayer.cpp, when the _player object dies, we run a few acons on a _hat
sprite to make it rise in the air and drop down again, just to add some humor.
Now let's go over the added logic.
Texturing our buildings with sprites
So in the test version we just coded, our game screen was divided into les of 128 pixels in
the iPad rena screen. The width and height properes of the Block objects are based on
this measurement. So a building two les wide and three les tall would have, in eect, 256
pixels in width and 384 pixels in height. A gap too would be measured this way, though its
height is set to 0.
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 132 ]
The logic we use to texture the buildings will take these les into account.
So let's take a look at the code to add texture to our buildings.
Time for action – texturing the buildings
There are a few changes to the way the initBlock method runs now:
1. Each block will store references to four dierent types of texture, represenng the
four types of buildings used in the game (_tile1, _tile2, _tile3, and _tile4).
So we now store that informaon in the initBlock method:
void Block::initBlock() {
_tile1 = SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("building_1.png");
_tile2 = SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("building_2.png");
_tile3 = SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("building_3.png");
_tile4 = SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("building_4.png");
2. Each block also stores references to two types of textures for the building roof le
(_roof1 and _roof2):
_roof1 = SpriteFrameCache::getInstance()->
getSpriteFrameByName ("roof_1.png");
_roof2 = SpriteFrameCache::getInstance()-
>getSpriteFrameByName ("roof_2.png");
3. Next, we create and distribute the various sprite les that form our building:
//create tiles
for (int i = 0; i < 5; i++) {
auto tile = Sprite::createWithSpriteFrameName("roof_1.png");
www.it-ebooks.info
Chapter 7
[ 133 ]
tile->setAnchorPoint(Vec2(0, 1));
tile->setPosition(Vec2(i * _tileWidth, 0));
tile->setVisible(false);
this->addChild(tile, kMiddleground, kRoofTile);
_roofTiles.pushBack(tile);
for (int j = 0; j < 4; j++) {
tile =
Sprite::createWithSpriteFrameName("building_1.png");
tile->setAnchorPoint(Vec2(0, 1));
tile->setPosition(Vec2(i * _tileWidth, -1 *
(_tileHeight * 0.47f + j * _tileHeight)));
tile->setVisible(false);
this->addChild(tile, kBackground, kWallTile);
_wallTiles.pushBack(tile);
}
}
A block comprises 20 sprites stored inside a _wallTiles vector and ve sprites
stored in a _roofTiles vector. So, when we inialize a Block object, we in eect
create a building that is ve les wide and four les tall. I made the decision that no
building in the game would exceed this size. If you decide to change this, then here
is where you would need to do it.
4. The initBlock method also creates ve chimney sprites and places them at the
top of the building. These will be spread out later according to the building type and
could be very easily turned into obstacles for our _player sprite. We also create
the animaon acons for the pus of smoke, here inside initBlock.
5. Moving on to our new setupBlock method, this is where the unnecessary les and
chimneys are turned invisible and where we spread out the visible chimneys. We
begin the method as follows:
void Block::setupBlock (int width, int height, int type) {
this->setPuffing(false);
_type = type;
_width = width * _tileWidth;
//add the roof height to the final height of the block
_height = height * _tileHeight + _tileHeight * 0.49f;
this->setPositionY(_height);
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 134 ]
SpriteFrame * wallFrame;
SpriteFrame * roofFrame = rand() % 10 > 6 ? _roof1 :
_roof2;
int num_chimneys;
float chimneyX[] = {0,0,0,0,0};
6. Then, based on building type, we give dierent x posions for the chimney sprites
and determine the texture we'll use on the wall les:
switch (type) {
case kBlockGap:
this->setVisible(false);
return;
case kBlock1:
wallFrame = _tile1;
chimneyX[0] = 0.2f;
chimneyX[1] = 0.8f;
num_chimneys = 2;
break;
case kBlock2:
wallFrame = _tile2;
chimneyX[0] = 0.2f;
chimneyX[1] = 0.8f;
chimneyX[2] = 0.5f;
num_chimneys = 3;
break;
case kBlock3:
wallFrame = _tile3;
chimneyX[0] = 0.2f;
chimneyX[1] = 0.8f;
chimneyX[2] = 0.5f;
num_chimneys = 3;
break;
case kBlock4:
wallFrame = _tile4;
chimneyX[0] = 0.2f;
chimneyX[1] = 0.5f;
num_chimneys = 2;
break;
}
www.it-ebooks.info
Chapter 7
[ 135 ]
7. The method then proceeds to posion the visible chimneys. And we nally move
to texturing the building. The logic to texture the roof and wall les is the same; for
instance, here's how the walls are led by changing the texture of each wall sprite
through the setDisplayFrame method and then turning unused les invisible:
count = _wallTiles->count();
for (i = 0; i < count; i++) {
tile = (Sprite *) _wallTiles->objectAtIndex(i);
if (tile->getPositionX() < _width && tile
->getPositionY() > -_height) {
tile->setVisible(true);
tile->setDisplayFrame(wallFrame);
} else {
tile->setVisible(false);
}
}
}
What just happened?
When we instanate a block in initBlock, we create a 5 x 4 building made out of wall les
and roof les, each a sprite. And when we need to turn this building into a 3 x 2 building,
or a 4 x 4 building, or whatever, we simply turn the excess les invisible at the end of
setupBlock.
The texture used for the roof is picked randomly, but the one picked for the walls is based
on building type (from our patterns array). It is also inside this for loop that all the les
posioned at a point greater than the new building's width and height are turned invisible.
Containers within containers
Before we move to the parallax eect logic, there is something I wanted to talk about
related to the layering of our _gameBatchNode object, which you'll recall is a
SpriteBatchNode object.
If you go to the stac create method inside Terrain.cpp, you will noce that the object is
sll created with a reference to a blank.png texture:
terrain->initWithSpriteFrameName("blank.png")
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 136 ]
In fact, the same 1 x 1 pixel image used in the test version is now in our sprite sheet,
only this me the image is transparent.
This is a bit of a hack, but necessary, because a sprite can only be placed inside a batch
node if its texture source is the same used to create the batch node. But Terrain is just a
container, it has no texture. However, by seng its blank texture to something contained
in our sprite sheet, we can place _terrain inside _gameBatchNode.
The same thing is done with the Block class, which now, in the nal version of the game,
behaves like another textureless container. It will contain the various sprites for the wall
and roof les as well as chimneys and pu animaons as its children.
The organizaon of the layers inside our _gameBatchNode object can seem complex and at
mes even absurd. Aer all, in the same node, we have a foreground "layer" of lampposts,
a middle-ground "layer" of buildings, and a background "layer" containing a cityscape. The
player is also placed in the background but on top of the cityscape. Not only that, but all
three layers are moved at dierent speeds to create our parallax eect, and all this inside
the same SpriteBatchNode!
But the amount of code this arrangement saves us juses any confusion we might have
at mes when aempng to keep the batch node organized. Now we can animate the
pus of smoke, for instance, and never worry about keeping them "aached" to their
respecve chimney sprite as the terrain scrolls to the le. The container will take care
of keeping things together.
Creating a parallax effect
Cocos2d-x has a special node called ParallaxNode, and one surprising thing about it is
how lile you get to use it! ParallaxNode helps create a parallax eect with nite layers,
or nite scrolling, which means that you can use it if your game screen has a limit to how
much it can scroll each way. Implemenng ParallaxNode to a game screen that can scroll
indenitely, such as the one in Victorian Rush Hour, usually requires more eort than it takes
to build your own eect.
A parallax eect is created by moving objects at dierent depths at dierent speeds.
The farther a layer appears from the screen, the slower its speed should be. In a game,
this usually means that the player sprite's speed is fraconed to all the layers that appear
behind it, and mulplied for the layers that appear in front of the player sprite:
www.it-ebooks.info
Chapter 7
[ 137 ]
Let's add this to our game.
Time for action – creating a parallax effect
The parallax eect in our game takes place inside the main loop:
1. So in our update method, you will nd the following lines of code:
if (_player->getVector().x > 0) {
_background->setPositionX(_background->getPosition().x -
_player->getVector().x * 0.25f);
First, we move the _background sprite, which contains the cityscape texture
repeated three mes along the x axis, and we move it at one-fourth of the speed
of the _player sprite.
2. The _background sprite scrolls to the le, and as soon as the rst cityscape texture
is o the screen, we shi the enre _background container to the right at precisely
the spot where the second cityscape texture would appear if allowed to connue.
We get this value by subtracng where the sprite would be from the total width of
the sprite:
float diffx;
if (_background->getPositionX() < -_background
->getContentSize().width) {
diffx = fabs(_background->getPositionX()) - _background
->getContentSize().width;
_background->setPositionX(-diffx);
}
So, in eect, we only ever scroll the rst texture sprite inside the container.
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 138 ]
3. A similar process is repeated with the _foreground sprite and the three lamppost
sprites it contains. Only the _foreground sprite moves at four mes the speed of
the _player sprite. These are coded as follows:
_foreground->setPositionX(_foreground->getPosition().x - _player-
>getVector().x * 4);
if (_foreground->getPositionX() < -_foreground
->getContentSize().width * 4) {
diffx = fabs(_foreground->getPositionX()) - _foreground
->getContentSize().width * 4;
_foreground->setPositionX(-diffx);
}
4. We also employ our cloud sprites in the parallax eect. Since they appear behind
the cityscape, so even farther away from _player, the clouds move at an even
lower rate (0.15):
for (auto cloud : _clouds) {
cloud->setPositionX(cloud->getPositionX() - _player-
>getVector().x * 0.15f);
if (cloud->getPositionX() + cloud->getBoundingBox().size.width
* 0.5f < 0 ) {
cloud->setPositionX(_screenSize.width + cloud-
>getBoundingBox().size.width * 0.5f);
}
}
What just happened?
We just added the parallax eect in our game by simply using the player speed at dierent
raos at dierent depths. The only slightly complicated part of the logic is how to ensure the
sprites scroll connuously. But the math of it is very simple. You just need to make sure the
sprites align correctly.
Adding a menu to our game
Right now, we only see the game logo on our introducon screen. We need to add buons to
start the game and also for the opon to play a tutorial.
In order to do that, we'll use a special kind of Layer class, called Menu.
www.it-ebooks.info
Chapter 7
[ 139 ]
Menu is a collecon of MenuItems. The layer is responsible for distribung its items as well
as tracking touch events on all items. Items can be sprites, labels, images, and so on.
Time for action – creating Menu and MenuItem
In GameLayer.cpp, scroll down to the createGameScreen method. We'll add the new
logic to the end of this method.
1. First, create the menu item for our start game buon:
auto menuItemOn =
Sprite::createWithSpriteFrameName("btn_new_on.png");
auto menuItemOff =
Sprite::createWithSpriteFrameName("btn_new_off.png");
auto starGametItem = MenuItemSprite::create( menuItemOff,
menuItemOn, CC_CALLBACK_1(GameLayer::startGame, this));
We create a MenuItemSprite object by passing it one sprite per state of the
buon. When the user touches a MenuItemSprite object, the o state sprite is
turned invisible and the on state sprite is turned visible, all inside the touch began
event. If the touch is ended or cancelled, the o state is displayed once again.
We also pass the callback funcon for this item; in this case,
GameLayer::StartGame.
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 140 ]
2. Next, we add the tutorial buon:
menuItemOn =
Sprite::createWithSpriteFrameName("btn_howto_on.png");
menuItemOff =
Sprite::createWithSpriteFrameName("btn_howto_off.png");
auto howToItem = MenuItemSprite::create( menuItemOff,
menuItemOn, CC_CALLBACK_1(GameLayer::showTutorial, this));
3. Then it's me to create the menu:
_mainMenu = Menu::create(howToItem, starGametItem, nullptr);
_mainMenu->alignItemsHorizontallyWithPadding(120);
_mainMenu->setPosition(Vec2(_screenSize.width *
0.5f, _screenSize.height * 0.54));
this->addChild(_mainMenu, kForeground);
The Menu constructor can receive as many MenuItemSprite objects as you
wish to display. These items are then distributed with one of the following calls:
alignItemsHorizontally, alignItemsHorizontallyWithPadding,
alignItemsHorizontally, alignItemsVerticallyWithPadding,
alignItemsInColumns, and alignItemsInRows. And the items appear
in the order they are passed to the Menu constructor.
4. Then we need to add our callback funcons:
void GameLayer::startGame (Ref* pSender) {
_tutorialLabel->setVisible(false);
_intro->setVisible(false);
_mainMenu->setVisible(false);
_jam->runAction(_jamMove);
SimpleAudioEngine::getInstance()
->playEffect("start.wav");
_terrain->setStartTerrain ( true );
_state = kGamePlay;
}
void GameLayer::showTutorial (Ref* pSender) {
_tutorialLabel->setString
("Tap the screen to make the player jump.");
_state = kGameTutorialJump;
_jam->runAction(_jamMove);
_intro->setVisible(false);
_mainMenu->setVisible(false);
SimpleAudioEngine::getInstance()
->playEffect("start.wav");
www.it-ebooks.info
Chapter 7
[ 141 ]
_tutorialLabel->setVisible(true);
}
These are called when our menu buons are clicked on, one method to start the
game and one to show the tutorial.
What just happened?
We just created our game's main menu. Menu can save us a lot of me handling all the
interacvity logic of buons. Though it might not be as exible as other items in Cocos2d-x,
it's sll good to know it's there if we need it.
We'll tackle the tutorial secon next.
Adding a tutorial to our game
Let's face it. With the possible excepon of Air Hockey, every game so far in this book could
benet from a tutorial, or a "how to play" secon. With Victorian Rush Hour, I'm going to
show you a quick way to implement one.
The unspoken rule of game tutorials is—make it playable. And that's what we'll aempt to
do here.
We'll create a game state for our tutorial, and we'll add a Label object to our stage and
make it invisible unless the tutorial state is on. We'll use the Label object to display our
tutorial text, as shown in the image here:
Let's go over the steps necessary to create our game tutorial.
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 142 ]
Time for action – adding a tutorial
Let's move back to our createGameScreen method.
1. Inside that method, add the following lines to create our Label object:
_tutorialLabel = Label::createWithTTF("", "fonts/Times.ttf", 60);
_tutorialLabel->setPosition(Vec2 (_screenSize.width *
0.5f, _screenSize.height * 0.6f) );
this->addChild(_tutorialLabel, kForeground);
_tutorialLabel->setVisible(false);
2. We add four states to our enumerated list of game states. These will represent the
dierent steps in our tutorial:
typedef enum {
kGameIntro,
kGamePlay,
kGameOver,
kGameTutorial,
kGameTutorialJump,
kGameTutorialFloat,
kGameTutorialDrop
} GameState;
The rst tutorial state, kGameTutorial, acts as a separator from all other game
states. So, if the value for _state is greater than kGameTutorial, we are in
tutorial mode.
Now, depending on the mode, we display a dierent message and we wait on a
dierent condion to change to a new tutorial state.
3. If you recall, our showTutorial method starts with a message telling the player to
tap the screen to make the sprite jump:
_tutorialLabel->setString
("Tap the screen to make the player jump.");
_state = kGameTutorialJump;
4. Then, at the end of the update method, we start adding the lines that will display
the rest of our tutorial informaon. First, if the player sprite is in the midst of a
jump and has just begun falling, we use the following:
if (_state > kGameTutorial) {
if (_state == kGameTutorialJump) {
www.it-ebooks.info
Chapter 7
[ 143 ]
if (_player->getState() == kPlayerFalling && _player
->getVector().y < 0) {
_player->stopAllActions();
_jam->setVisible(false);
_jam->stopAllActions();
_running = false;
_tutorialLabel->setString
("While in the air, tap the screen to float.");
_state = kGameTutorialFloat;
}
As you can see, we let the player know that another tap will open the umbrella and
cause the sprite to oat.
5. Next, as the sprite is oang, when it reaches a certain distance from the buildings,
we inform the player that another tap will close the umbrella and cause the sprite to
drop. Here's the code for these instrucons:
} else if (_state == kGameTutorialFloat) {
if (_player->getPositionY() < _screenSize.height *
0.95f) {
_player->stopAllActions();
_running = false;
_tutorialLabel->setString
("While floating, tap the screen again to drop.");
_state = kGameTutorialDrop;
}
6. Aer that, the tutorial will be complete and we show the message that the player
may start the game:
} else {
_tutorialLabel->setString
("That's it. Tap the screen to play.");
_state = kGameTutorial;
}
}
Whenever we change a tutorial state, we pause the game momentarily and wait for
a tap. We handle the rest of our logic inside onTouchBegan, so we'll add that next.
7. Inside onTouchBegan, in the switch statement, add the following cases:
case kGameTutorial:
_tutorialLabel->setString("");
_tutorialLabel->setVisible(false);
www.it-ebooks.info
Adding the Looks – Victorian Rush Hour
[ 144 ]
_terrain->setStartTerrain ( true );
_state = kGamePlay;
break;
case kGameTutorialJump:
if (_player->getState() == kPlayerMoving) {
SimpleAudioEngine::getInstance()
->playEffect("jump.wav");
_player->setJumping(true);
}
break;
case kGameTutorialFloat:
if (!_player->getFloating()) {
_player->setFloating (true);
_running = true;
}
break;
case kGameTutorialDrop:
_player->setFloating (false);
_running = true;
break;
What just happened?
We added a tutorial to our game! As you can see, we used quite a few new states.
But now we can incorporate the tutorial right into our game and have one ow smoothly
into the other. All these changes can be seen in acon in the nal version of this project,
4198_07_FINAL_PROJECT.zip, which you can nd on this book's Support page.
Now, you guessed it, let's run it in Android.
Time for action – running the game in Android
Follow these steps to deploy the game to Android:
1. Open your project's Android.mk le in a text editor.
2. Edit the lines in LOCAL_SRC_FILES to read:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/Block.cpp \
www.it-ebooks.info
Chapter 7
[ 145 ]
../../Classes/GameSprite.cpp \
../../Classes/Player.cpp \
../../Classes/Terrain.cpp \
../../Classes/GameLayer.cpp
3. Import the game into Eclipse and wait unl all classes are compiled.
4. That's it. Save it and run your applicaon.
What just happened?
You now have Victorian Rush Hour running in Android.
Summary
Aer we got all the gameplay details ironed out in our test game, bringing in a sprite sheet
and game states seems remarkably simple and easy.
But during this stage, we can also think of new ways to improve gameplay. For instance,
the realizaon that clouds of smoke coming out of chimneys would oer a nice visual cue
to the player to idenfy where the buildings were, if the cyclist happened to jump too high.
Or that a hat ying through the air could be funny!
Now it's me to bring physics to our games, so head on to the next chapter.
www.it-ebooks.info
www.it-ebooks.info
[ 147 ]
Getting Physical – Box2D
It's time to tackle physics! Cocos2d-x comes bundled with Box2D and Chipmunk.
These are so-called 2D physics engines – the first written in C++ and the second
in C. Chipmunk has a more recent Objective-C port but Cocos2d-x must use the
original one written in C for portability.
We'll be using Box2D for the examples in this book. The next two games I'll
show you will be developed with that engine, starting with a simple pool game
to illustrate all the main points about using Box2D in your projects.
In this chapter, you will learn:
How to set up and run a Box2D simulaon
How to create bodies
How to use the debug draw feature to quickly test your concepts
How to use collision lters and listeners
Building a Box2D project with Cocos2d-x
With version 3.x of the framework, we no longer need to specify that we want to use a
physics engine. The projects add these APIs by default. So, all you need in order to create
a Box2D project is to create a regular Cocos2d-x project as we've been doing with the
examples so far.
There is, however, one extra step you need to perform if you wish to use something called a
debug draw in your project. So let's set that up now.
8
www.it-ebooks.info
Geng Physical – Box2D
[ 148 ]
Time for action – using debug draw in your Box2D project
Let's start by creang the project. In my machine, I created a game called MiniPool in my
desktop. Here are the steps:
1. Open Terminal and enter the following command:
cocos new MiniPool -p com.rengelbert.MiniPool -l cpp -d /Users/
rengelbert/Desktop/MiniPool
2. Open the new project in Xcode.
3. Now navigate to the Tests folder inside the Cocos2d-x framework folder. This can
be found in tests/cpp-tests/Classes. Then open the Box2DTestBed folder.
4. Drag the les GLES-Render.h and GLES-Render.cpp to your project in Xcode.
5. You can also open the Box2dTest.cpp class in the test folder Box2DTest,
as we're going to copy and paste a few of the methods from there.
6. In the HelloWorldScene.h header le, leave the includes in place, but change
the class declaraons to match these:
class HelloWorld : public cocos2d::Layer {
public:
virtual ~HelloWorld();
HelloWorld();
static cocos2d::Scene* scene();
void initPhysics();
void update(float dt);
virtual void draw(Renderer *renderer, const Mat4 &transform,
uint32_t flags) override;
private:
GLESDebugDraw * _debugDraw;
b2World* world;
Mat4 _modelViewMV;
void onDraw();
CustomCommand _customCommand;
};
7. Then add this include statement at the top:
#include "GLES-Render.h"
www.it-ebooks.info
Chapter 8
[ 149 ]
8. Then, in the HelloWorldScene.cpp implementaon le, replace the lines
between the using namespace CocosDenshion and HelloWorld::scene
methods with these:
#define PTM_RATIO 32
HelloWorld::HelloWorld()
{
this->initPhysics();
scheduleUpdate();
}
HelloWorld::~HelloWorld()
{
delete world;
world = nullptr;
delete _debugDraw;
_debugDraw = nullptr;
}
void HelloWorld::initPhysics() {
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
world = new b2World(gravity);
// Do we want to let bodies sleep?
world->SetAllowSleeping(true);
world->SetContinuousPhysics(true);
_debugDraw = new (std::nothrow) GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(_debugDraw);
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
// flags += b2Draw::e_jointBit;
// flags += b2Draw::e_aabbBit;
// flags += b2Draw::e_pairBit;
// flags += b2Draw::e_centerOfMassBit;
_debugDraw->SetFlags(flags);
}
www.it-ebooks.info
Geng Physical – Box2D
[ 150 ]
void HelloWorld::update(float dt)
{
world->Step(dt, 8, 1);
}
9. Now comes the implementaon of the draw methods. You can copy and paste
most of this code from the Box2DTest folder:
void GameLayer::draw(Renderer *renderer, const Mat4 &transform,
uint32_t flags)
{
//
// IMPORTANT:
// This is only for debug purposes
// It is recommended to disable it
//
Layer::draw(renderer, transform, flags);
GL::enableVertexAttribs( cocos2d::GL::VERTEX_ATTRIB_FLAG_
POSITION );
auto director = Director::getInstance();
CCASSERT(nullptr != director, "Director is null when setting
matrix stack");
director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_
MODELVIEW);
_modelViewMV = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_
STACK_MODELVIEW);
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(GameLayer::onDraw, this);
renderer->addCommand(&_customCommand);
director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_
MODELVIEW);
}
void GameLayer::onDraw()
{
auto director = Director::getInstance();
Mat4 oldMV;
oldMV = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_
MODELVIEW);
www.it-ebooks.info
Chapter 8
[ 151 ]
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_
MODELVIEW, _modelViewMV);
_world->DrawDebugData();
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_
MODELVIEW, oldMV);
}
What just happened?
The GLES-Render class is necessary to use the debug draw feature in Box2D. This will draw
all the elements from the simulaon on the screen. The debug draw object is created inside
the initPhysics method alongside the Box2D simulaon (b2World). We'll go over that
logic in a moment.
As the comment inside the draw method states, the debug draw feature should be switched
o once you're done developing your game. So all the lines pertaining to that object as well
as the draw method should be commented out when you're ready for a release version.
So what is a physics engine?
The famous Isaac Newton said, every acon has a reacon. Right aer he said, who the hell
threw that apple?
So far in our games, we have covered very simple collision systems, basically only ever
checking to see if simple shapes (circles and rectangles) overlapped each other. The reacons
from these collisions were also very simple in our games so far: with vector inversions or
simply by making things disappear once they touch. With Box2D, you get way more!
Box2D is a very robust collision detecon engine and can certainly be used just for that
purpose. But the simulaon will also process and return a bunch of informaon derived from
the collisions and the interacons between bodies, meaning how the objects should behave,
based on their shapes, mass, and all the forces at play in the simulaon.
Meeting Box2D
At the core of the engine, you have the b2World object. This is the simulaon.
You ll the world with b2Body objects, and then you step through the simulaon with
b2World->Step(). And you take the results of the simulaon and display them to the
user through your sprites, by grabbing a b2Body object's posion and rotaon and applying
them to a sprite.
The debug draw object allows you to see the simulaon without using any sprites. Sort of
like a version of our test project from Chapter 6, Quick and Easy Sprite – Victorian Rush Hour.
www.it-ebooks.info
Geng Physical – Box2D
[ 152 ]
Meeting the world
Most of the me, the physics simulaon will mean the creaon of a b2World object.
Note, however, that you can get interesng results managing more than one world
object in the same game, for mulple views for instance. But that's for another book.
In our simplied basic project, the world is created like this:
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
world = new b2World(gravity);
// Do we want to let bodies sleep?
world->SetAllowSleeping(true);
world->SetContinuousPhysics(true);
_debugDraw = new (std::nothrow) GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(_debugDraw);
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
// flags += b2Draw::e_jointBit;
// flags += b2Draw::e_aabbBit;
// flags += b2Draw::e_pairBit;
// flags += b2Draw::e_centerOfMassBit;
_debugDraw->SetFlags(flags);
Box2D has its own vector structure, b2Vec2, and we use it here to create the world's gravity.
The b2World object receives that as its parameter. A simulaon does not always require
gravity, of course; in that case, the argument will be a (0, 0) vector.
SetAllowSleeping means if objects are not moving and therefore not generang derived
data, skip checking for derived data from those objects.
SetContinuousPhysics means we have some fast objects in our hands, which we'll later
point out to the simulaon, so it can pay extra aenon for collisions.
Then we create the debug draw object. This is oponal, as I said before. The ags indicate
what you wish to see in the drawing. In the code we saw before, we only want to see the
shapes of the objects.
Then comes PTM_RATIO, the dened constant we passed as a parameter to the debug draw.
Box2D uses meters instead of pixels for a variety of reasons that are really enrely unnecessary
for anyone to know. Except for one reason, pixel to meter (PTM), so every pixel posion value
used in the game will be divided by this rao constant. If the result from this division ever gets
above 10 or below 0.1, increase or decrease the value for PTM_RATIO accordingly.
www.it-ebooks.info
Chapter 8
[ 153 ]
You have some leeway, of course. By all means, play with this value once your game is
completed, and pay special aenon to the subtle dierences in speed (another common
value for this rao is 100).
Running the simulation
As I said before, you use the Step method to run the simulaon, usually inside your main
loop, though not necessarily:
world->Step(dt, 8, 1);
You need to pass it the me step, here represented by the delta me in the main loop.
Then pass the number of velocity iteraons and posion iteraons in the step. This basically
means how many mes velocity and posion will be processed inside a step.
In the previous example, I'm using the default values from the Box2D template in Cocos2d-x.
Usually, a xed me step is beer than the delta, and a higher value for posion iteraon
may be necessary if things move really fast in your game. But always remember to play with
these values, aiming at nding the lowest possible ones.
No Ref objects in Box2D
Box2D does not use Ref objects. So, no memory management! Remember to get rid
of all the Box2D objects through delete and not release. If you knew it already... well,
you remember:
HelloWorld::~HelloWorld(){
delete world;
world = nullptr;
delete _debugDraw;
_debugDraw = nullptr;
}
As I menoned before, C++11 introduces smart pointers, which are
memory managed, meaning you don't have to delete these objects yourself.
However, the topic of shared pointers is beyond the scope of this book, and
using unique pointers in this chapter would add way too many lines that had
nothing to do with Box2D. And although smart pointers are amazing, their
syntax and usage is, well, let's say very "C++ish".
www.it-ebooks.info
Geng Physical – Box2D
[ 154 ]
Meeting the bodies
The b2Body object is the thing you'll spend most of your me dealing with inside a Box2D
simulaon. You have three main types of b2Bodies: dynamic, stac, and kinemac. The rst
two are of greater importance and are the ones we'll use in our game.
Bodies are created by combining a body denion with a body xture. The body denion
is a structure that holds informaon about type, posion, velocity, and angle, among
other things. The xture holds informaon about the shape, including its density,
elascity, and fricon.
So, to create a circle that is 40 pixels wide, you would use the following:
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
//or make it static bodyDef.type = b2_staticBody;
b2Body * body = world->CreateBody(&bodyDef);
//create circle shape
b2CircleShape circle;
circle.m_radius = 20.0/PTM_RATIO;
//define fixture
b2FixtureDef fixtureDef;
fixtureDef.shape = &circle;
fixtureDef.density = 1;
fixtureDef.restitution = 0.7;
fixtureDef.friction = 0.4;
body->CreateFixture(&fixtureDef);
To create a box that is 40 pixels wide, you would use this:
//create body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
b2Body * body = world->CreateBody(&bodyDef);
//define shape
b2PolygonShape box;
box.SetAsBox(20 /PTM_RATIO, 20 / PTM_RATIO);
//Define fixture
b2FixtureDef fixtureDef;
www.it-ebooks.info
Chapter 8
[ 155 ]
fixtureDef.shape = &box;
fixtureDef.density = 2;
fixtureDef.restitution = 0;
body->CreateFixture(&fixtureDef);
Note that you use the world object to create the bodies. And also note that boxes are
created with half their desired width and height.
Density, fricon, and restuon all have default values, so you don't always need to
set these.
Our game – MiniPool
Our game consists of sixteen balls (circles), one cue (box), and a pool table made out of
six lines (edges) and six pockets (circles). This is all there is to it as far as the Box2D
simulaon is concerned.
Download the nal project from this book's Support page if you wish to follow along with the
nal code. Box2D is a complex API and it will be best to review and expose the logic rather than
work on it by doing a lot of typing. So there will be no start project to work from this me. You
may choose any manner to add les from the nished project to the one we started when I
showed you how to set up the debug draw object. The nal game will look like this:
www.it-ebooks.info
Geng Physical – Box2D
[ 156 ]
Game settings
This is a portrait-orientaon-only game, with no screen rotaon allowed, and universal
applicaon. The game is designed for the regular iPhone (320 x 480) and its resoluon size
is set to kResolutionShowAll. This will show borders around the main screen in devices
that do not match the 1.5 screen rao of the iPhone.
//in AppDelegate.cpp
auto screenSize = glview->getFrameSize();
auto designSize = Size(320, 480);
glview->setDesignResolutionSize(designSize.width, designSize.height,
ResolutionPolicy::SHOW_ALL);
std::vector<std::string> searchPaths;
if (screenSize.width > 640) {
searchPaths.push_back("ipadhd");
director->setContentScaleFactor(1280/designSize.width);
} else if (screenSize.width > 320) {
searchPaths.push_back("ipad");
director->setContentScaleFactor(640/designSize.width);
} else {
searchPaths.push_back("iphone");
director->setContentScaleFactor(320/designSize.width);
}
auto fileUtils = FileUtils::getInstance();
fileUtils->setSearchPaths(searchPaths);
Note that I use the iPhone's dimensions to idenfy larger screens. So the iPad and the
iPhone rena are considered to be two mes 320 x 480 and the rena iPad is considered
to be four mes 320 x 480.
Sprite plus b2Body equal to b2Sprite
The most common way to work with b2Body objects in Cocos2d-x is to combine them with
sprites. In the games I'll show you, I created a class called b2Sprite that extends sprite with
the addion of a _body member property that points to its very own b2Body. I also add a
few helper methods to deal with our pesky PTM_RATIO. Feel free to add as many of these as
you think necessary.
b2Body objects have an incredibly helpful property called userData. You can store anything
you wish inside it and the bodies will carry it with them throughout the simulaon. So, what
most developers do is that they store inside the body's userData property a reference to
the instance of sprite wrapping it. So b2Sprite knows about its body, and the body knows
about its b2Sprite.
www.it-ebooks.info
Chapter 8
[ 157 ]
As a maer of fact, composion is key when working with Box2D.
So, when designing your games, make sure every object knows of every
other object or can get to them quickly. This will help immensely.
Creating the pool table
In the debug draw view, this is what the table looks like:
All the elements seen here are created inside the initPhysics method in
GameLayer.cpp. The table has no visual representaon other than the background image
we use in the game. So there are no sprites aached to the individual pockets, for example.
The pocket bodies are created inside a for loop, with the best algorithm I could come up
with to distribute them correctly on screen. This logic is found in the initPhysics method,
so let's take a look at that and see how our rst b2Body objects are created:
b2Body * pocket;
b2CircleShape circle;
float startX = _screenSize.width * 0.07;
float startY = _screenSize.height * 0.92f;
for (int i = 0; i < 6; i++) {
bodyDef.type = b2_staticBody;
if (i < 3) {
bodyDef.position.Set(startX/PTM_RATIO,
(startY - i * (_screenSize.height * 0.84f * 0.5f))/PTM_RATIO);
} else {
www.it-ebooks.info
Geng Physical – Box2D
[ 158 ]
bodyDef.position.Set(
(startX + _screenSize.width * 0.85f)/PTM_RATIO,
(startY - (i-3) * (_screenSize.height * 0.84f *
0.5f))/PTM_RATIO);
}
pocket = _world->CreateBody(&bodyDef);
fixtureDef.isSensor = true;
circle.m_radius = (float) (1.5 * BALL_RADIUS) / PTM_RATIO;
fixtureDef.shape = &circle;
pocket->CreateFixture(&fixtureDef);
auto pocketData = new b2Sprite(this, kSpritePocket);
pocket->SetUserData(pocketData);
}
The pocket bodies are stac bodies and we determine in their xture denion that they
should behave like sensors:
fixtureDef.isSensor = true;
This switches o all the physics from an object and turns it into a collision hot spot. A sensor
serves only to determine if something is touching it or not.
It's almost always best to ignore Box2D sensors and use your own sprites
or points in your collision logic. One neat feature in sensors is that they
make it very easy to determine when something has just ceased touching
them, as you'll see once we cover contact listeners.
Creating edges
If a shape can only be hit on one side, an edge is probably what you need. Here is how we
create edges in our game:
b2BodyDef tableBodyDef;
tableBodyDef.position.Set(0, 0);
b2Body* tableBody = _world->CreateBody(&tableBodyDef);
// Define the table edges
b2EdgeShape tableBox;
// bottom edge
tableBox.Set(b2Vec2(_screenSize.width * 0.14f/PTM_RATIO, _screenSize.
height * 0.09f/PTM_RATIO),
www.it-ebooks.info
Chapter 8
[ 159 ]
b2Vec2(_screenSize.width * 0.86f/PTM_RATIO, _screenSize.height *
0.09f/PTM_RATIO));
tableBody->CreateFixture(&tableBox,0);
// top edge
tableBox.Set(b2Vec2(_screenSize.width * 0.14f/PTM_RATIO, _screenSize.
height * 0.91f/PTM_RATIO),
b2Vec2(_screenSize.width * 0.86f/PTM_RATIO, _screenSize.height *
0.91f/PTM_RATIO));
tableBody->CreateFixture(&tableBox,0);
So the same b2Body object can have as many edges as you need. You set an edge with its
start and end points (in this case, the b2Vec2 structures) and add it as a xture to the body,
with a density of 0.
Creating the ball objects
In the game, there is a class called Ball that extends b2Sprite, used for both the target
balls and the cue ball. These objects are also created inside the initPhysics method.
Here is the basic conguraon of that object:
//create Box2D body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
_body = _game->getWorld()->CreateBody(&bodyDef);
_body->SetLinearDamping(1.2f);
_body->SetAngularDamping(0.2f);
//create circle shape
b2CircleShape circle;
circle.m_radius = BALL_RADIUS/PTM_RATIO;
//define fixture
b2FixtureDef fixtureDef;
fixtureDef.shape = &circle;
fixtureDef.density = 5;
fixtureDef.restitution = 0.7f;
//add collision filters so only white ball can be hit by cue
if (_type == kSpriteBall) {
fixtureDef.filter.categoryBits = 0x0010;
} else if (_type == kSpritePlayer) {
www.it-ebooks.info
Geng Physical – Box2D
[ 160 ]
//white ball is tracked as bullet by simulation
_body->SetBullet(true);
fixtureDef.filter.categoryBits = 0x0100;
}
//set sprite texture
switch (_color) {
case kColorBlack:
this->initWithSpriteFrameName("ball_black.png");
break;
case kColorRed:
this->initWithSpriteFrameName("ball_red.png");
break;
case kColorYellow:
this->initWithSpriteFrameName("ball_yellow.png");
break;
case kColorWhite:
this->initWithSpriteFrameName("ball_white.png");
break;
}
_body->CreateFixture(&fixtureDef);
//store the b2Sprite as the body's userData
_body->SetUserData(this);
The friction xture property involves the reacon of two touching surfaces (two bodies).
In this case, we want to create "fricon" with the table surface, which is not a body at all.
So what we need to use instead is damping. This will apply a similar eect to fricon, but
without the need for an extra surface. Damping can be applied to the linear velocity vector
of a body as follows:
_body->SetLinearDamping(1.2);
And to the angular velocity as follows:
_body->SetAngularDamping(0.2);
Also, the white ball is set to be a bullet:
_body->SetBullet(true);
This will make the simulaon pay extra aenon to this object in terms of collision.
We could make all balls in the game behave as bullets, but this is not only unnecessary
(something revealed through tesng) but also not very processing-friendly.
www.it-ebooks.info
Chapter 8
[ 161 ]
Creating collision lters
In the ball object, there is a filter property inside the xture denion that we use
to mask collisions. Meaning we determine what bodies can collide with each other.
The cue ball receives a dierent value for categoryBits than the other balls.
fixtureDef.filter.categoryBits = 0x0100;
When we create the cue body, we set a maskBits property in its xture denion
as follows:
fixtureDef.filter.maskBits = 0x0100;
We set this to the same value as the white ball's categoryBits.
The result of all this? Now the cue can only hit bodies with the same categoryBits,
which here means the cue can only collide with the white ball.
It is possible to add more than one category to a mask with a bitwise | opon, as seen here:
fixtureDef.filter.maskBits = 0x0100 | 0x0010;
Or to collide with everything except the cue ball, for instance, as seen in the following line:
fixtureDef.filter.maskBits = 0xFFFF & ~0x0100;
Creating the cue
The cue ball also extends b2Sprite, and its body is set as a box.
//create body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
_body = _game->getWorld()->CreateBody(&bodyDef);
_body->SetLinearDamping(8);
_body->SetAngularDamping(5);
//Define shape
b2PolygonShape box;
box.SetAsBox(BALL_RADIUS * 21 /PTM_RATIO, BALL_RADIUS * 0.2 / PTM_
RATIO);
//Define fixture
b2FixtureDef fixtureDef;
fixtureDef.shape = &box;
www.it-ebooks.info
Geng Physical – Box2D
[ 162 ]
fixtureDef.filter.maskBits = 0x0100;
fixtureDef.density = 10;
fixtureDef.restitution = 1;
_body->CreateFixture(&fixtureDef);
_body->SetUserData(this);
It has very high damping values because, in the rare occasions when the player misses the
cue ball, the cue will not y o the screen but halt a few pixels from the white ball.
If we wanted to create the cue ball as a trapezium or a triangle, we would need to give the
b2PolygonShape opon the verces we want. Here's an example of this:
b2Vec2 vertices[3];
vertices[0].Set(0.0f, 0.0f);
vertices[1].Set(1.0f, 0.0f);
vertices[2].Set(0.0f, 1.0f);
int32 count = 3;
b2PolygonShape triangle;
triangle.Set(vertices, count);
And the verces must be added counterclockwise to the array. Meaning, if we add the top
vertex of the triangle rst, the next vertex must be the one to the le.
Once all the elements are in place, the debug draw looks like this:
www.it-ebooks.info
Chapter 8
[ 163 ]
Creating a contact listener
Besides collision lters, one other feature in Box2D that helps with collision management is
the creaon of a contact listener.
Inside the initPhysics method, we create the world object like this:
b2Vec2 gravity;
gravity.Set(0.0f, 0.0f);
_world = new b2World(gravity);
_world->SetAllowSleeping(true);
_world->SetContinuousPhysics(true);
_collisionListener = new CollisionListener();
_world->SetContactListener(_collisionListener);
Our CollisionListener class extends the Box2D b2ContactListener class, and it must
implement at least one of the following methods:
void BeginContact(b2Contact* contact);
void EndContact(b2Contact* contact);
void PreSolve(b2Contact* contact, const b2Manifold*
oldManifold);
void PostSolve(b2Contact* contact, const b2ContactImpulse*
impulse);
These events are all related to a contact (collision) and are red at dierent stages of
a contact.
Sensor objects can only ever re the BeginContact and
EndContact events.
In our game, we implement two of these methods. The rst is:
void CollisionListener::BeginContact(b2Contact* contact) {
b2Body * bodyA = contact->GetFixtureA()->GetBody();
b2Body * bodyB = contact->GetFixtureB()->GetBody();
b2Sprite * spriteA = (b2Sprite *) bodyA->GetUserData();
b2Sprite * spriteB = (b2Sprite *) bodyB->GetUserData();
if (spriteA && spriteB) {
www.it-ebooks.info
Geng Physical – Box2D
[ 164 ]
//track collision between balls and pockets
if (spriteA->getType() == kSpritePocket) {
spriteB->setVisible(false);
} else if (spriteB->getType() == kSpritePocket) {
spriteA->setVisible(false);
} else if (spriteA->getType() == kSpriteBall &&
spriteB->getType() == kSpriteBall) {
if (spriteA->mag() > 10 || spriteB->mag() > 10) {
SimpleAudioEngine::getInstance()-
>playEffect("ball.wav");
}
} else if ((spriteA->getType() == kSpriteBall &&
spriteB->getType() == kSpritePlayer) ||
(spriteB->getType() == kSpriteBall &&
spriteA->getType() == kSpritePlayer)) {
if (spriteA->mag() > 10 || spriteB->mag() > 10) {
SimpleAudioEngine::getInstance()-
>playEffect("ball.wav");
}
}
}
}
You can see now how important the userData property is. We can quickly access sprites
aached to the bodies listed in the b2Contact object through the userData property.
Besides that, all our sprites have a _type property that behaves like idenfying tags in our
logic. Note that you could certainly use the Cocos2d-x tags for that, but I nd that at mes,
if you can combine the Sprite tags with their _type value, you may produce interesng
sorng logic.
So, in BeginContact, we track the collisions between balls and pockets. But we also track
collision between balls. In the rst case, the balls are turned invisible when they touch the
pockets. And, in the second case, we play a sound eect whenever two balls touch each
other, but only if they are at a certain speed (we determine that through a b2Sprite helper
method that retrieves the squared magnitude of a sprite's velocity vector).
The other method in our listener is:
void CollisionListener::PreSolve(b2Contact* contact, const b2Manifold*
oldManifold) {
b2Body * bodyA = contact->GetFixtureA()->GetBody();
b2Body * bodyB = contact->GetFixtureB()->GetBody();
www.it-ebooks.info
Chapter 8
[ 165 ]
b2Sprite * spriteA = (b2Sprite *) bodyA->GetUserData();
b2Sprite * spriteB = (b2Sprite *) bodyB->GetUserData();
if (spriteA && spriteB) {
//track collision between player and cue ball
if (spriteA->getType() == kSpriteCue && spriteA->mag() > 2) {
if (spriteB->getType() == kSpritePlayer && spriteA-
>isVisible()) {
SimpleAudioEngine::getInstance()-
>playEffect("hit.wav");
spriteA->setVisible(false);
spriteB->getGame()->setCanShoot(false);
}
} else if (spriteB->getType() == kSpriteCue && spriteA->mag()
> 2) {
if (spriteA->getType() == kSpritePlayer && spriteB-
>isVisible()) {
SimpleAudioEngine::getInstance()-
>playEffect("hit.wav");
spriteB->setVisible(false);
spriteA->getGame()->setCanShoot(false);
}
}
}
}
Here, we listen to a collision before its reacons are calculated. If there is a collision between
the cue and white ball, we play a sound eect and we hide the cue.
If you want to force your own logic to the collision reacon and override
Box2D on this, you should do so in the PreSolve method. In this game,
however, we could have added all this collision logic to the BeginContact
method and it would work just as well.
The game controls
In the game, the player must click on the white ball and then drag his or her nger to
acvate the cue ball. The farther the nger gets from the white ball, the more powerful
the shot will be.
So let's add the events to handle user input.
www.it-ebooks.info
Geng Physical – Box2D
[ 166 ]
Time for action – adding the touch events
We'll deal with onTouchBegan rst.
1. In the onTouchBegan method, we start by updang the game state:
bool GameLayer::onTouchBegan(Touch * touch, Event * event) {
if (!_running) return true;
if (_gameState == kGameOver) {
if (_gameOver->isVisible()) _gameOver->setVisible(false);
resetGame();
return true;
}
2. Next, we check on the value of _canShoot. This returns true if the white ball
is not moving.
if (!_canShoot) return true;
3. Next, we determine whether the touch is landing on the white ball. If it is, we
start the game if it is not currently running yet and we make our mer visible.
Here's the code to do this:
if (touch) {
auto tap = touch->getLocation();
auto playerPos = _player->getPosition();
float diffx = tap.x - playerPos.x;
float diffy = tap.y - playerPos.y;
float diff = pow(diffx, 2) + pow(diffy, 2);
if (diff < pow(BALL_RADIUS * 4, 2)) {
if (_gameState != kGamePlay) {
_gameState = kGamePlay;
if (_intro->isVisible()) _intro->setVisible(false);
_timer->setVisible(true);
}
}
}
Note that we use a larger radius for the white ball in our logic (four mes larger).
This is because we don't want the target area to be too small, since this game will
run on both iPhones and iPads. We want the player to comfortably hit the white
ball with his or her nger.
www.it-ebooks.info
Chapter 8
[ 167 ]
4. We store where in the ball the point lies. This way, the player can hit the ball at
dierent spots, causing it to move at dierent angles:
//make point lie within ball
if (diff > pow(BALL_RADIUS * 2, 2)) {
float angle = atan2(diffy, diffx);
_cueStartPoint = Vec2(
playerPos.x + BALL_RADIUS * 0.8f * cos(angle),
playerPos.y + BALL_RADIUS * 0.8f * sin(angle));
} else {
_cueStartPoint = playerPos;
}
Since we made the white ball a much larger target for our touch event, now we
must make sure the actual point picked by the player lies within the ball. So we may
have to make some adjustments here.
5. We pass the point to our LineContainer object and we prepare the cue body to
be used, as follows:
_lineContainer->setBallPoint(_cueStartPoint);
_cue->getBody()->SetLinearVelocity(b2Vec2(0,0));
_cue->getBody()->SetAngularVelocity(0.0);
_touch = touch;
We once again have a LineContainer node so we can draw a dashed line between
the cue and the spot on the ball where the cue will hit. This serves as a visual aid for
the player to prepare his or her shot. The visual aid eect is demonstrated here:
www.it-ebooks.info
Geng Physical – Box2D
[ 168 ]
6. In onTouchMoved, we only need to move the cue body based on the posion of
the player's nger. So we calculate the distance between the moving touch and
the white ball. If the cue body is sll too close to the ball, we set its body object to
sleep and its texture object to invisible.
void GameLayer::onTouchMoved(Touch * touch, Event * event) {
if (touch && touch == _touch) {
Point tap = touch->getLocation();
float diffx = tap.x - _player->getPositionX();
float diffy = tap.y - _player->getPositionY();
if (pow(diffx,2) + pow(diffy,2) < pow(BALL_RADIUS * 2,2))
{
_usingCue = false;
_lineContainer->setDrawing(false);
_cue->setVisible(false);
_cue->getBody()->SetAwake(false);
} else {
_usingCue = true;
_cue->setVisible(true);
_lineContainer->setDrawing(true);
placeCue(tap);
_cue->getBody()->SetAwake(true);
}
}
}
7. Otherwise, we awaken the body and call the placeCue method as follows:
void GameLayer::placeCue(Point position) {
float diffx = _cueStartPoint.x - position.x;
float diffy = _cueStartPoint.y - position.y;
float angle = atan2(diffy, diffx);
float distance = sqrt(pow (diffx, 2) + pow(diffy, 2));
_pullBack = distance * 0.5f;
Point cuePosition = Vec2(
_cueStartPoint.x - (BALL_RADIUS * 21 + _pullBack) *
cos(angle),
_cueStartPoint.y - (BALL_RADIUS * 21 + _pullBack) *
sin(angle)
);
www.it-ebooks.info
Chapter 8
[ 169 ]
_cue->getBody()->SetTransform(
b2Vec2(cuePosition.x/PTM_RATIO,
cuePosition.y/PTM_RATIO),
angle);
_lineContainer->setCuePoint(Vec2(
_cueStartPoint.x - ( _pullBack) * cos(angle),
_cueStartPoint.y - ( _pullBack) * sin(angle)));
}
This method then calculates the angle and posion of the cue body and transforms
the cue's b2Body method accordingly. The SetTransform opon of a b2Body
method takes care of both its posion and angle.
8. Finally, in onTouchEnded, we let go of the cue body as follows:
void GameLayer::onTouchEnded(Touch* touch, Event* event) {
if (_usingCue && _touch) {
auto cueBody = _cue->getBody();
float angle = cueBody->GetAngle();
//release cue!
cueBody->ApplyLinearImpulse(
b2Vec2 (_pullBack * cos(angle) * SHOT_POWER,
_pullBack * sin(angle) * SHOT_POWER),
cueBody->GetWorldCenter());
}
_usingCue = false;
_touch = nullptr;
_lineContainer->setDrawing(false);
}
We use ApplyLinearImpulse. This method receives a vector for the impulse
to be applied and the posion on the body where this impulse should be applied.
The _pullback variable stores the informaon of how far the cue body was from
the ball when the player released the cue body. The farther it was, the strongest the
shot will be.
www.it-ebooks.info
Geng Physical – Box2D
[ 170 ]
What just happened?
We added the touch events that allow the player to hit the white ball with the cue body.
The process is a very simple one. We rst need to make sure the player is touching the white
ball; then we move the cue body as the player drags his or her nger. Finally, when the touch
is released, we make the cue spring towards the white ball with ApplyLinearImpulse.
We may also move a body in Box2D by using SetLinearVelocity or ApplyForce, each
with subtle and not-so-subtle dierences. I recommend that you play around with these.
The main loop
As I showed you before, the simulaon only requires that you call its Step() method inside
the main loop. Box2D takes care of all of its side of the bargain.
What remains usually is the rest of the game logic: scoring, game states, and updang your
sprites to match the b2Bodies method.
It's important to call the update method of each ball and cue. This is what our b2Sprite
update method looks like:
void b2Sprite::update(float dt) {
if (_body && this->isVisible()) {
this->setPositionX(_body->GetPosition().x * PTM_RATIO);
this->setPositionY(_body->GetPosition().y * PTM_RATIO);
this->setRotation(_RADIANS_TO_DEGREES(-1 * _body-
>GetAngle()));
}
}
All you need to do is make sure the Sprite method matches the informaon in the b2Body
object. And make sure that you convert meters back to pixels when you do so.
So let's add our main loop.
Time for action – adding the main loop
It's inside our main loop that we update our b2World object.
1. Start by updang the simulaon as follows:
void GameLayer::update(float dt) {
if (!_running) return;
if (_gameState == kGameOver) return;
_world->Step(dt, 10, 10);
www.it-ebooks.info
Chapter 8
[ 171 ]
2. Next, we need to determine if the game has nished by checking on the number of
balls currently in play. We use the following for that:
//track invisible objects
for (auto ball : _balls) {
if (!ball->isVisible() && ball->getInPlay()) {
ball->setInPlay(false);
ball->hide();
//count down balls
_ballsInPlay--;
SimpleAudioEngine::getInstance()-
>playEffect("drop.wav");
if (_ballsInPlay == 0) {
_gameState = kGameOver;
_gameOver->setVisible(true);
}
} else {
ball->update(dt);
}
}
3. Next, we connue to update the sprites as follows:
if (!_cue->isVisible()) {
_cue->hide();
} else {
_cue->update(dt);
}
if (!_player->isVisible()) {
_player->reset();
_player->setVisible(true);
SimpleAudioEngine::getInstance()-
>playEffect("whitedrop.wav");
}
_player->update(dt);
4. And we also determine when it's me to allow the player a new shot. I decided
to only let that happen if the white ball has stopped. And the quickest way to
determine that is to check on its vector. Here's how:
//check to see if player ball is slow enough for a new shot
if (_player->mag() < 0.5f && !_canShoot) {
_player->getBody()->SetLinearVelocity(b2Vec2_zero);
_player->getBody()->SetAngularVelocity(0);
_canShoot = true;
}
www.it-ebooks.info
Geng Physical – Box2D
[ 172 ]
What just happened?
We added our main loop. This will update the Box2D simulaon and then it's up to us to
take care of posioning our sprites based on the resulng informaon.
One very important aspect of Box2D is understanding what can be
changed inside a b2World::Step call and what can't.
For instance, a body cannot be made inacve (b2Body::SetActive)
or be destroyed (b2World::DestroyBody) inside a step. You will
need to check on condions outside the step to make these changes. For
instance, in our game, we check to see if the ball sprites are visible or not,
and if not then we set their bodies as inacve. And all this is done aer
the call to b2World::Step.
Adding a timer to our game
In MiniPool, we count the number of seconds it takes the player to clear the table.
Let me show you how to do that.
Time for action – creating a timer
We create mers in prey much the same way we create our main loop.
1. First, we add a second scheduled event by adding this line to our GameLayer
constructor:
this->schedule(CC_SCHEDULE_SELECTOR(GameLayer::ticktock), 1.5f);
2. With this, we create a separate mer that will run the ticktock method every 1.5
seconds (I decided in the end that 1.5 seconds looked beer).
3. The method keeps updang the value of the _time property and displaying it in
the _timer label.
void GameLayer::ticktock(float dt) {
if (_gameState == kGamePlay) {
_time++;
_timer->setString(std::to_string(_time));
}
}
www.it-ebooks.info
Chapter 8
[ 173 ]
What just happened?
We added a mer to our game by scheduling a second update—specifying the me interval
we wanted—using the schedule method.
If you wish to remove a mer, all you need to do is call the unschedule(SEL_SCHEDULE
selector) method of nodes anywhere in your class.
Now, let's take our Box2D game to Android.
Time for action – running the game in Android
Follow these steps to deploy a Box2D game to Android:
1. Open the Android.mk le in a text editor (you'll nd it in the folder
proj.android/jni).
2. Edit the lines in LOCAL_SRC_FILES to read:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/GLES-Render.cpp \
../../Classes/b2Sprite.cpp \
../../Classes/Ball.cpp \
../../Classes/CollisionListener.cpp \
../../Classes/Cue.cpp \
../../Classes/LineContainer.cpp \
../../Classes/GameLayer.cpp
3. Open the manifest le and set the app orientaon to portrait.
4. Import the game into Eclipse and wait ll all classes are compiled.
5. Build and run your applicaon.
What just happened?
That was it. There is no dierence between building a game that uses Box2D and one that
does not. The Box2D API is already included in the make le, in the line where the classes in
the external folder are imported.
And, of course, you don't need to add the GLES-Render class in your nal project.
www.it-ebooks.info
Geng Physical – Box2D
[ 174 ]
Have a go hero
A few changes to make gameplay more interesng could be: add a limit to the number of
mes the white ball can hit a pocket; and another opon is to have the mer work as a
countdown one, so the player has a limited me to clear the table before me runs out.
Also, this game could do with a few animaons. An Action method to scale down and fade
out a ball when it hits a pocket would look very nice.
Pop quiz
Q1. What is the main object in a Box2D simulaon?
1. b2Universe.
2. b2d.
3. b2World.
4. b2Simulation.
Q2. A b2Body object can be of which type?
1. b2_dynamicBody, b2_sensorBody, b2_liquidBody.
2. b2_dynamicBody, b2_staticBody, b2_kinematicBody.
3. b2_staticBody, b2_kinematicBody, b2_debugBody.
4. b2_kinematicBody, b2_transparentBody, b2_floatingBody.
Q3. Which of the following list of properes can be set in a xture denion?
1. Density, fricon, restuon, shape.
2. Posion, density, bullet state.
3. Angular damping, acve state, fricon.
4. Linear damping, restuon, xed rotaon.
Q4. If two bodies have the same unique value for their maskBits property in their xture
denion, this means:
1. The two bodies can never collide.
2. The two bodies will only trigger begin contact events.
3. The two bodies can only collide with each other.
4. The two bodies will only trigger end contact events.
www.it-ebooks.info
Chapter 8
[ 175 ]
Summary
Nowadays, it seems like everybody in the world has played or will play a physics-based game
at some point in their lives. Box2D is by far the most popular engine in the casual games
arena. The commands you learned here can be found in prey much every port of the
engine, including a JavaScript one that is growing in popularity as we speak.
Seng up the engine and geng it up and running is remarkably simple—perhaps too much
so. A lot of tesng and value tweaking goes into developing a Box2D game and prey soon
you learn that keeping the engine performing as you wish is the most important skill to
master when developing physics-based games. Picking the right values for fricon, density,
restuon, damping, me step, PTM rao, and so on can make or break your game.
In the next chapter, we'll connue to use Box2D, but we'll focus on what else Cocos2d-x can
do to help us organize our games.
www.it-ebooks.info
www.it-ebooks.info
[ 177 ]
On the Level – Eskimo
In our next game, we'll go over some important features most games
require, but which are not directly related to gameplay. So we'll step over
the architecture side of things and talk about reading and writing data,
using scene transitions, and creating custom events that your whole
application can listen to.
But, of course, I'll add a few gameplay ideas as well!
This me, you'll learn how to:
Create scene transions
Load external data
Save data using UserDefault
Create your own game events with the dispatcher
Use the accelerometer
Reuse Box2D bodies
9
www.it-ebooks.info
On the Level – Eskimo
[ 178 ]
The game – Eskimo
Lile Eskimo boy is late for supper. It is your mission, should you choose to accept it, to guide
the lile fella back to his igloo.
This is a Box2D game, and the controls are very simple. Tilt the device and the Eskimo
will move. If you tap the screen, the Eskimo switches shape between a snow ball and a
block of ice, each shape with its own physical characteriscs and degrees of maneuverability.
The ball has a higher fricon, for instance, and the block of ice has none.
And the only way the Eskimo may reach his desnaon is by hing the gravity switches
spread out all over the screen.
Eskimo combines elements from an arcade game with elements of a puzzle game, as each
level was planned with one perfect soluon in mind as to how to take the lile Eskimo home.
Note, however, that mulple soluons are possible.
Download the 4198_09_FINAL_PROJECT.zip le and run the game when you have a
chance. Once again, there is no need for extraneous typing as the logic used in the game is
prey much old news to you, and we'll go over the new bits in depth.
www.it-ebooks.info
Chapter 9
[ 179 ]
The game settings
This is a portrait-only game and accelerometer-based, so it should not autorotate.
It was designed for the regular iPhone and its screen resoluon size is set to
kResolutionShowAll, so the screen sengs are similar to the ones in our previous game.
Designing a game for the iPhone screen and using the kResolutionShowAll parameter
will result in the so-called leerbox view when playing the game in screens that do
not match the iPhone's 1.5 rao. This means you see borders around the game screen.
Alternavely, you could use the kResolutionNoBorders parameter, which results in a
zoom-in eect, causing the game to play at full screen but the areas around the borders
will be cropped.
The following screenshot illustrates these two cases:
The one on the le is the game screen on the iPad, using kResolutionShowAll.
The one on the right uses kResolutionNoBorders. Note how the screen is zoomed in
and cropped on the second one. When using kResolutionNoBorders, it's important to
design your game so that no vital gameplay element appears too close to the borders as it
may not be displayed.
www.it-ebooks.info
On the Level – Eskimo
[ 180 ]
Organizing the game
Once again, there is a b2Sprite class, and the Eskimo and Platform classes extend
b2Sprite. Then there are the regular Sprite classes, GSwitch (which stands for gravity
switch) and Igloo. The logic runs collision detecon between these last two and Eskimo, but
I chose not to have them as sensor bodies because I wanted to show you that 2D collision logic
for the Cocos2d-x elements can coexist with collision logic for the Box2D elements just ne.
But most importantly, this game now has three scenes. So far in this book, we've only used
one scene per game. This game's scene objects will wrap MenuLayer, LevelSelectLayer,
and GameLayer. Here's a brief note on all three:
In MenuLayer, you have the opon to play the game, which will take you to
LevelSelectLayer or to play a tutorial for the game, which will take you
to GameLayer.
In LevelSelectLayer, you may choose which available level you want to play,
and that will take you to GameLayer. Or you may go back to MenuLayer.
In GameLayer, you play the game, and may go back to MenuLayer upon game over.
The following image illustrates all three scenes in the game:
www.it-ebooks.info
Chapter 9
[ 181 ]
Using scenes in Cocos2d-x
Scenes are mini applicaons themselves. If you have experience as an Android developer,
you may think of scenes as acvies. Of all the classes based on node, the Scene applicaon
is the most architecturally relevant, because the Director class runs a scene, in eect
running your applicaon.
Part of the benet of working with scenes is also part of the drawback: they are wholly
independent and ignorant of each other. The need to share informaon between scenes
will be a major factor when planning your game class structure.
Also, memory management may become an issue. A currently running scene will not give up
its ghost unl a new scene is up and running. So, when you use transion animaons, keep
in mind that for a few seconds, both scenes will exist in memory.
In Eskimo, I inialize scenes in two dierent ways. With MenuLayer and
LevelSelectLayer, each me the user navigates to either one of these scenes, a new
layer object is created (either a new MenuLayer or a new LevelSelectLayer).
GameLayer, however, is dierent. It is a singleton Layer class that never stays out of memory
aer its rst instanaon, therefore speeding up the me from level selecon to the actual
playing. This may not work for every game, however. As I menoned earlier, when transioning
between scenes, both scenes stay in memory for a few seconds. But here we are adding to
that problem by keeping one layer in memory the whole me. Eskimo, however, is not very big
memory-wise. Note that we could sll have the opon of creang special condions for when
GameLayer should be destroyed, and condions when it should not.
So let me show you how to create scene transions. First, with a Scene class that creates a
fresh copy of its Layer each me it's created.
Time for action – creating a scene transition
You have, of course, been using scenes all along.
1. Hidden in AppDelegate.cpp, you've had lines like:
auto scene = GameLayer::scene();
// run
director->runWithScene(scene);
2. So, in order to change scenes, all you need to do is tell the Director class which
scene you wish it to run. Cocos2d-x will then get rid of all the content in the current
scene, if any (all their destructors will be called), and a new layer will be instanated
and wrapped inside the new Scene.
www.it-ebooks.info
On the Level – Eskimo
[ 182 ]
3. Breaking the steps down a lile further, this is how you usually create a new scene
for Director:
Scene* MenuLayer::scene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// add layer as a child to scene
auto layer = new MenuLayer();
scene->addChild(layer);
layer->release();
return scene;
}
4. The stac MenuLayer::scene method will create a blank scene, and then create a
new instance of MenuLayer and add it as a child to the new scene.
5. Now you can tell Director to run it as follows:
Director::getInstance()->replaceScene(MenuLayer::scene());
6. The logic changes a lile if you wish to use a transion eect. So, inside our
MenuLayer.cpp class, this is how we transion to LevelSelectLayer:
auto newScene = TransitionMoveInR::create(0.2f,
LevelSelectLayer::scene());
Director::getInstance()->replaceScene(newScene);
The code just described creates a new transion object that will slide in the new
scene from the right-hand side of the screen to lie on top of the current one.
The transion will take 0.2 seconds.
What just happened?
You created a scene transion animaon with Cocos2d-x.
As I menoned earlier, this form of scene change will cause a new instance of the new layer
to be created each me, and destroyed each me it's replaced by a new scene. So, in our
game, MenuLayer and LevelSelectLayer are instanated and destroyed as many mes
as the user switches between them.
There is also the opon to use pushScene instead of replaceScene. This creates a stack
of scene objects and keeps them all in memory. This stack can be navigated with popScene
and popToRootScene.
Now let me show you how to do the same thing but with a singleton layer.
It should be no surprise to you by now that you will nd many examples of these transion
classes in the Tests, project at tests/cpp-tests/Classes/TransitionsTest.
www.it-ebooks.info
Chapter 9
[ 183 ]
Time for action – creating transitions with a singleton
Layer class
We rst need to make sure the layer in queson can only be instanated once.
1. The scene stac method in GameLayer looks like this:
Scene* GameLayer::scene(int level, int levelsCompleted)
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// add layer as a child to scene
scene->addChild(GameLayer::create(level, levelsCompleted));
return scene;
}
This layer receives two parameters when created: the game level it should load and
the number of levels completed by the player. We create a new Scene object and
add GameLayer as its child.
2. But take a look at the stac create method in GameLayer:
GameLayer * GameLayer::create (int level, int levelsCompleted) {
if (!_instance) {
_instance = new GameLayer();
} else {
_instance->clearLayer();
}
_instance->setLevelsCompleted(levelsCompleted);
_instance->loadLevel(level);
_instance->scheduleUpdate();
return _instance;
}
3. An _instance stac property is declared at the top of GameLayer.cpp as follows:
static GameLayer* _instance = nullptr;
We can check, then, if the one instance of GameLayer is currently in memory and
instanate it if necessary.
4. The scene transion to GameLayer will look, on the surface, to be exactly like the
regular kind of transion. So, in LevelSelectLayer, we have the following:
auto newScene = TransitionMoveInR::create(0.2f,
GameLayer::scene(_firstIndex + i, _levelsCompleted));
Director::sharedDirector()->replaceScene(newScene);
www.it-ebooks.info
On the Level – Eskimo
[ 184 ]
What just happened?
We have created a Scene transion with a Layer class that never gets destroyed, so we
don't have to instanate new plaorm and gravity switch sprites with each new level.
There are, of course, problems and limitaons with this process. We cannot transion between
the two GameLayer objects, for instance, as we only ever have one of these objects.
There are also some special consideraons when leaving GameLayer and when geng
back to it. For instance, we must make sure we have our main loop running when we get
back to GameLayer.
The only way to do that is by unscheduling it whenever leaving GameLayer and scheduling
it again when returning, as follows:
//when leaving
unscheduleUpdate();
auto newScene = TransitionMoveInL::create(0.2f, MenuLayer::scene());
Director::sharedDirector()->replaceScene(newScene);
//when returning
_instance->scheduleUpdate();
Again, architecturally speaking, there are even beer opons. Possibly the
best one is creang your own game elements cache, or game manager, with
object pools and everything that needs instanang stored inside it. And then
have this cache be a singleton that every scene can access. This is also the
best way to share game-relevant data between scenes.
Loading external data from a .plist le
Eskimo has only ve game levels, plus a tutorial level (feel free to add more). The data for
these levels exist inside a levels.plist le, stored inside the Resources folder. A .plist
le is an XML-formaed data le, and as such can be created in any text editor. Xcode,
however, oers a nice GUI to edit the les.
Let me show you how to create them inside Xcode.
www.it-ebooks.info
Chapter 9
[ 185 ]
Time for action – creating a .plist le
You could, of course, create this in any text editor, but Xcode makes it extra easy to create
and edit .plist les.
1. Inside Xcode, go to New | File... and then select Resource and Property List.
When asked where to save the le, choose any locaon you want.
2. You need to decide what the Root element of your .plist le will be—either an
Array or a Diconary (the default) type. For Eskimo, the Root element is Array
containing a series of diconaries, each holding the data for a level in the game.
3. By selecng the Root element, you get a plus sign indicator right next to the Type
declaraon. Clicking on this plus sign will add an element to Root. You can then
pick the data type for this new item. The opons are Boolean, Data, Date, Number,
String, and again Array and Diconary. The last two can contain subitems in the
tree, just like the Root element.
www.it-ebooks.info
On the Level – Eskimo
[ 186 ]
4. Keep adding elements to the tree, trying to match the items in the
following screenshot:
What just happened?
You just created a property list le in Xcode. This is XML-structured data that Cocos2d-x can
load and parse. You've used them already when loading parcles and sprite sheet informaon.
Loading the level data
In Eskimo, since I only have ve levels, I chose to have one .plist le that contains all
levels. This may not be the best opon in a larger game.
Although Apple devices will load and parse the .plist les quickly, the same may not
be true for other targets. So limit the size of your .plist les by organizing the data into
mulple les. You've probably seen games that divide their levels into mulple groups or
packs. This is a simple way to create an extra preloading screen your game can use to parse
level data. This can also be used as a means to keep le sizes to a minimum.
www.it-ebooks.info
Chapter 9
[ 187 ]
In Eskimo, we could have the .plist les containing 10 levels each, for instance, and then
10 groups of these, totaling 100 levels.
So it's me to load our .plist le and parse the data for our levels.
Time for action – retrieving data from the .plist le
The level data is loaded in GameLayer.
1. Inside the GameLayer constructor, we load the data like this:
_levels = FileUtils::getInstance()-
>getValueVectorFromFile("levels.plist");
Cocos2d-x will take care of mapping FileUtils to the correct target. There is
FileUtils for each plaorm that is supported by the framework and they all can
be made to work with the .plist format. Sweet! If the data in the .plist le is an
Array, you must convert it to ValueVector; if it's Diconary, you must convert it to
a ValueMap. We'll do that next when we load the data for a specic level.
If we divide the levels into multiple .plist files, then we would need logic
to refresh the _levels array each time a new .plist file is loaded.
2. Inside the loadLevel method, we load the data for the level like this:
ValueMap levelData = _levels.at(_currentLevel).asValueMap();
Here, the data in the .plist le is Diconary, so we must convert the data
into a ValueMap.
And that's it for the loading and parsing. Now we can proceed to retrieving data
for our level.
Each level diconary starts with the data regarding the level's gravity (a level may
start with a dierent gravity value), the start point where the player should be
placed, and the end point where the igloo should be placed.
3. These values are retrieved like this in our code:
_gravity = levelData.at("gravity").asInt();
switch (_gravity) {
case kDirectionUp:
_world->SetGravity(b2Vec2(0,FORCE_GRAVITY));
break;
case kDirectionDown:
_world->SetGravity(b2Vec2(0,-FORCE_GRAVITY));
break;
www.it-ebooks.info
On the Level – Eskimo
[ 188 ]
case kDirectionLeft:
_world->SetGravity(b2Vec2(-FORCE_GRAVITY, 0));
break;
case kDirectionRight:
_world->SetGravity(b2Vec2(FORCE_GRAVITY, 0));
break;
}
_player->setSpritePosition(Vec2(
levelData.at("startx").asFloat() * TILE,
levelData.at("starty").asFloat() * TILE
));
_igloo->initIgloo(_gravity, Vec2(
levelData.at("endx").asFloat() * TILE,
levelData.at("endy").asFloat() * TILE
));
4. Inside this same diconary, we have an array for plaorms and an array for gravity
switches. These are retrieved like this:
ValueVector platforms =
levelData.at("platforms").asValueVector();
ValueVector switches =
levelData.at("switches").asValueVector();
5. These arrays contain even more diconaries containing data for the creaon
and placement of plaorms and gravity switches in each level. This data is passed
to the corresponding Platform and GSwitch classes, and boom—you've got
yourself a level.
for ( auto platformData : platforms){
ValueMap data = platformData.asValueMap();
platform->initPlatform ( data.at("width").asInt() * TILE,
data.at("angle").asFloat(),
Vec2(data.at("x").asFloat() * TILE,
data.at("y").asFloat() * TILE));
}
for (int i = 0; i < switches.size(); i++) {
auto gswitch = _gSwitchPool.at(i);
ValueMap data = switches.at(i).asValueMap();
gswitch->initGSwitch(data.at("gravity").asInt(),
Vec2(data.at("x").asFloat() * TILE,
data.at("y").asFloat() * TILE));
}
www.it-ebooks.info
Chapter 9
[ 189 ]
What just happened?
Parsing and retrieving data from a property list le is a breeze with Cocos2d-x. You will
always work with either an array of values or a diconary of values and map these to a
ValueVector or ValueMap respecvely.
Saving game data
When planning your games, you may soon decide you wish to store data related to your
applicaon, such as highest score or user preferences. In Cocos2d-x, you can do this by
simply accessing the UserDefault singleton.
With UserDefault, you can store integers, oats, doubles, strings, and Boolean with just
one simple call per each data type, as follows:
UserDefault::getInstance()->setIntegerForKey("levelsCompleted", _
levelsCompleted);
UserDefault::getInstance()->flush();
The other methods are setFloatForKey, setDoubleForKey, setStringForKey,
and setBoolForKey. To retrieve data, you use their respecve geers.
I'll show you next how to use that in our game.
Time for action – storing the completed levels
Open the LevelSelectLayer class.
1. This is how the number of levels completed is retrieved from inside the
layer's constructor:
_levelsCompleted = UserDefault::getInstance()-
>getIntegerForKey("levelsCompleted");
2. Inially, _levelsCompleted will equal 0 if no data is present. So we store level 1
as "unlocked". This is how that's done:
if (_levelsCompleted == 0) {
_levelsCompleted = 1;
UserDefault::getInstance()->setIntegerForKey("levelsComplet
ed", 1);
UserDefault::getInstance()->flush();
}
www.it-ebooks.info
On the Level – Eskimo
[ 190 ]
3. Then, whenever we start a new level, we update the number of levels completed if
the new level number is larger than the value stored.
if (_currentLevel > _levelsCompleted) {
_levelsCompleted = _currentLevel;
UserDefault::getInstance()->setIntegerForKey("levelsComplet
ed", _levelsCompleted);
UserDefault::getInstance()->flush();
}
You don't have to flush the data (using flush) each time you update
every single bit in it. You can group multiple updates under one flush,
or find a spot in your logic where you can safely flush updates before
exiting the app. Nodes come with extremely helpful methods for this:
onEnter, onExit, onEnterTransitionDidFinish, and
onExitTransitionDidStart.
What just happened?
For small bits of data related to your game, sengs, and preferences, UserDefault is an
excellent way to store informaon. Cocos2d-x once again will map this to whatever local
storage is available in each target system.
Using events in your game
Earlier versions of the framework used an Objecve-C-inspired feature of nocaons.
But this parcular API is already on its way to being deprecated. Instead, you should use
the all-knowing Director and its Dispatcher (the same object we've been talking to
when listening to touch events).
If you have ever worked with an MVC framework or developed a game AI system, you are
probably familiar with a design paern called the Observer Paern. This consists of a central
message dispatcher object other objects can subscribe to (observe) in order to listen to
special messages, or order it to dispatch their own messages to other subscribers. In other
words, it's an event model.
With Cocos2d-x, this is done very quickly and easily. Let me give you an example used
in Eskimo.
www.it-ebooks.info
Chapter 9
[ 191 ]
Time for action – using the event dispatcher
If we want the Platform sprite to listen to the special nocaon
NOTIFICATION_GRAVITY_SWITCH, all we need to do is add Platform as an observer.
1. Inside the Platform class, in its constructor, you will nd these lines:
auto onGravityChanged = [=] (EventCustom * event) {
if (this->isVisible()) {
switchTexture();
}
};
Director::getInstance()->getEventDispatcher()-
addEventListenerWithSceneGraphPriority(EventListenerCustom::create
(GameLayer::NOTIFICATION_GRAVITY_SWITCH, onGravityChanged), this);
And yes, it is one line of code! It is best to create a macro for both the dispatcher
and the add listener code; so, something like this:
#define EVENT_DISPATCHER Director::getInstance()-
>getEventDispatcher()
#define ADD_NOTIFICATION( __target__, __notification__,
__handler__) EVENT_DISPATCHER-
addEventListenerWithSceneGraphPriority(EventListenerCustom::create
(__notification__, __handler__), __target__);
This way the same line of code we used before would look like this:
ADD_NOTIFICATION(this, GameLayer::NOTIFICATION_GRAVITY_SWITCH,
onGravityChanged);
2. The message (or nocaon), NOTIFICATION_GRAVITY_SWITCH, is created as a
stac string in GameLayer:
const char* GameLayer::NOTIFICATION_GRAVITY_SWITCH =
"NOTIFICATION_GRAVITY_SWITCH";
The one-line call to the Director class's dispatcher tells it that the Platform
objects will listen to this dened message, and when such a message is dispatched,
every Platform object will call the onGravityChanged method. This method
does not need to be a block as I showed here, but it is more readable to have the
handler appear as close to the Add Listener call as possible. So, simple blocks are
a good way to organize listeners and their handlers.
www.it-ebooks.info
On the Level – Eskimo
[ 192 ]
3. In the game, each gravity switch is color coded, and when the Eskimo hits a switch,
the plaorm's texture changes to reect the new gravity by switching to the color
of the acvated gravity switch. This is all done through a simple nocaon we
dispatch inside GameLayer when a collision with a GSwitch object is detected
inside the main loop. This is how we do that:
Director::getInstance()->getEventDispatcher()-
>dispatchCustomEvent( GameLayer::NOTIFICATION_GRAVITY_SWITCH);
Or, if you are using the macro, use this:
EVENT_DISPATCHER->dispatchCustomEvent(
GameLayer::NOTIFICATION_GRAVITY_SWITCH);
4. You can also add a UserData object in the custom event as a second parameter in
the dispatch. This can be retrieved from the EventCustom * event in the event
handler, like this:
event->getUserData();
5. When Platform objects are destroyed, the Node destructor will take care of
removing the node as a listener.
What just happened?
You have just learned how to make your life as a developer much, much easier. Adding an
applicaon-wide event model to your game is such a powerful way to improve ow and
interacvity between objects and it's so simple to use that I'm sure you'll soon implement
this feature in all your games.
Using the accelerometer
Now let's move to the few new topics related to gameplay, the rst of which is the use of
accelerometer data. Again, nothing could be simpler.
Time for action – reading accelerometer data
Just as you do with touch events, you need to tell the framework you want to read
accelerometer data.
1. You tell the framework you wish to use the accelerometer with this one call inside
any Layer class:
Device::setAccelerometerEnabled(true);
www.it-ebooks.info
Chapter 9
[ 193 ]
2. Then, just as you've done with touch events, you subscribe to the accelerometer
events from the event dispatcher as follows:
auto listenerAccelerometer =
EventListenerAcceleration::create(CC_CALLBACK_2
(GameLayer::onAcceleration, this));
_eventDispatcher->addEventListenerWithSceneGraphPriority(listenerA
ccelerometer,
this);
3. In Eskimo, the accelerometer data changes the value of a Point vector
called _acceleration.
void GameLayer::onAcceleration(Acceleration *acc, Event *event) {
_acceleration = Vec2(acc->x * ACCELEROMETER_MULTIPLIER,
acc->y * ACCELEROMETER_MULTIPLIER);
}
This value is then read inside the main loop and used to move the Eskimo. In the
game, only one axis is updated at a me, depending on the current gravity. So you
can only ever move the Eskimo on the X axis or the Y axis with the accelerometer
data, but never both at the same me.
Keep in mind that there is also a Z axis value in the Acceleration data.
It might come in handy someday!
What just happened?
Yep. With a couple of lines, you added accelerometer controls to your game.
It is common pracce to add extra lters to these accelerometer values, as results may vary
between devices. These lters are raos you apply to acceleraon to keep values within a
certain range. You can also nd a variety of formulas for these raos online. But these will
depend on how sensive you need the controls to be or how responsive.
And, in the game, we only update the Eskimo with the accelerometer data if the sprite is
touching a plaorm. We can quickly ascertain that by checking whether or not the _player
body has a contact list, as follows:
if (_player->getBody()->GetContactList())
www.it-ebooks.info
On the Level – Eskimo
[ 194 ]
Reusing b2Bodies
In Eskimo, we have a pool of b2Bodies that are used inside the Platform objects and
we also change the shape of the lile Eskimo whenever the player taps the screen. This is
possible because Box2D makes it very easy to change the xture data of a b2Body xture
without having to destroy the actual body.
Let me show you how.
Time for action – changing a b2Body xture
All you have to do is make a call to body->DestroyFixture. Not surprisingly, this should
be done outside the simulaon step.
1. Inside the methods makeCircleShape and makeBoxShape in the Eskimo class,
you will nd these lines:
if (_body->GetFixtureList() ) {
_body->DestroyFixture(_body->GetFixtureList());
}
Here we just state that if there is a xture for this body, destroy it. We can then
switch from a box to a circle xture when the player taps the screen, but use the
same body throughout.
2. We use this feature with plaorms too. Plaorms inside the pool that are not being
used in the current level are set to inacve as follows:
_body->SetActive(false);
This removes them from the simulaon.
3. And when they are reinialized to be used in a level, we destroy their exisng
xture, update it to match the data from the .plist le, and set the body to
acve once again. This is how we do that:
//Define shape
b2PolygonShape box;
box.SetAsBox(width * 0.5f /PTM_RATIO, PLATFORM_HEIGHT *
0.5f / PTM_RATIO);
//Define fixture
b2FixtureDef fixtureDef;
fixtureDef.shape = &box;
fixtureDef.density = 1;
www.it-ebooks.info
Chapter 9
[ 195 ]
fixtureDef.restitution = 0;
//reutilize body from the pool: so destroy any existent fixture
if (_body->GetFixtureList()) {
_body->DestroyFixture(_body->GetFixtureList());
}
_body->CreateFixture(&fixtureDef);
_body->SetTransform(b2Vec2(position.x / PTM_RATIO, position.y /
PTM_RATIO), _DEGREES_TO_RADIANS(-angle));
_body->SetActive(true);
What just happened?
So, just as we've been doing with pools of sprites, we can apply the same logic to b2Bodies
and never instanate anything inside the main loop.
Now, let's see how Android handles all this level-loading business.
Time for action – running the game in Android
Time to deploy the game to Android.
1. Navigate to the proj.android folder and open the le AndroidManifest.xml
in a text editor. Then go to the folder jni and open the le Android.mk in a
text editor.
2. In the AndroidManifest.xml le, edit the following line in the activity
tag as follows:
android:screenOrientation="portrait"
3. Next, let's edit the make le, so open the Android.mk le and edit the lines in
LOCAL_SRC_FILES to read:
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/b2Sprite.cpp \
../../Classes/Eskimo.cpp \
../../Classes/GSwitch.cpp \
../../Classes/Igloo.cpp \
../../Classes/Platform.cpp \
../../Classes/LevelSelectLayer.cpp \
../../Classes/MenuLayer.cpp \
../../Classes/GameLayer.cpp
4. Now import the project into Eclipse and build it.
5. You can now save it and run the game in your Android device.
www.it-ebooks.info
On the Level – Eskimo
[ 196 ]
What just happened?
By now, you should be an expert at running your code in Android and hopefully your
experience with Eclipse has been a good one.
And that's all folks!
Play the game. Check out the source code (which is chock-full of comments). Add some
new levels and make the lile Eskimo's life a living hell!
Have a go hero
The gameplay for Eskimo could be further improved with a few new ideas that would force
the player to make more errors.
It is a common feature in these types of games to evaluate the degree of "completeness"
in which a level was played. There could be a me limit for each level and pick-up items
for the Eskimo, and the player could be evaluated at the end of each level and awarded a
bronze, silver, or golden star based on his or her performance. And new groups of levels
may only be unlocked if a certain number of golden stars were acquired.
Summary
Yes, you have a cool idea for a game, great! But a lot of eort will go into structuring and
opmizing it. Cocos2d-x can help with both sides of the job.
Yes, scenes can be a bit cumbersome depending on your needs, but they are undisputed
memory managers. When Director kills a scene, it kills it dead.
Loading external data can not only help with memory size, but also bring in more
developers into your project, focusing specically on level design and the external data
les that create them.
And events can quickly become a must in the way you structure your games. Prey soon,
you will nd yourself thinking in terms of events to handle game states and menu
interacvity, among other things.
Now, let's move to a whole new language!
www.it-ebooks.info
[ 197 ]
Introducing Lua!
In our last game, we'll move to the new Cocos IDE and develop an entire game
using the Lua scripting language. You'll get to know and use the Lua bindings
for the Cocos2d-x API, which is not much different from what we've been using
in C++; if anything, it's just a lot easier!
This me, you'll learn how to:
Create and publish a project in Cocos IDE
Code an enre game in Lua
Use sprites, parcles, labels, menus, and acons, but this me with the
Lua bindings
Build a match-three game
So what is Lua like?
At the heart of Lua (which means moon in Portuguese), you have the table. You may think
of it as being similar to a JavaScript object, only it's much more than that. It plays the part
of arrays, diconaries, enumeraons, structures, and classes, among other things. It makes
Lua the perfect language to manage large sets of data. You write a script that handles the
data, and then keep feeding it dierent "stu." An inventory or shop system, an interacve
children's book—these types of projects could all benet from Lua's table-centric power,
as they can be built around a xed template with a data table at its core.
10
www.it-ebooks.info
Introducing Lua!
[ 198 ]
Its syntax, for those not used to a scripng language, can be a lile odd, with its dos,
thens, and ends. But once you get past this inial hurdle, you'll nd Lua quite user-friendly.
Here are some of the "oddies" in its syntax:
-- a comment
--[[
a
multiline
comment
]]
-- a table declared as a local variable
local myTable = {}
-- the length of a table
local len = #myTable
-- looping the table (starting with index 1!)
for i = 1, #myTable do
local element = myTable[i]
-- an if elseif else statement
if (element ~= true ) then
-- do something
elseif (element == true) then
-- do something else
else
-- we'll never get here!
end
end
Semicolons are oponal.
A table can be turned into a template to generate instances of it, in other words, a class.
Methods of the instance of the table must be accessed with a : notaon:
myTableClassObject:myMethod()
From inside the method, you refer to the instance of the class as self:
self.myProperty = 1
self:myOtherMethod()
Alternavely, you can call the template's method with a dot notaon, passing the instance of
that template to it as the rst parameter:
myTableClassObject.myMethod(myTableClassObject)
www.it-ebooks.info
Chapter 10
[ 199 ]
I admit, it's weird, but it can be useful at mes as prey much every method you write in
Lua can be made available for other parts of your code—sort of the way stac methods are
used in tradional OOP languages.
Debugging in Lua – the knights who say nil
Debugging your Lua code can be frustrang at mes. But you soon learn to disnguish
between the minute subtlees in Lua's runme errors. The compiler will say something is
nil (Lua's null) in about 99.9 percent of cases. It's up to you to gure out why. Here are
the main culprits:
You are referencing an object's own property without prepending self. or self:.
You are calling an instance method with a dot notaon, and not passing the
instance as the rst parameter; something like myObject.myMethod() instead of
myObject.myMethod(myObject). Use myObject:myMethod() instead.
You are referencing a variable from a place outside its scope. For example, a local
variable declared inside an if statement is being referenced outside the condional.
You forgot to return the class object at the end of your class or
module/table declaraon.
You tried to access the zero index of an array.
You forgot to add a few dos and thens or ends.
And nally, maybe you're just having one of those days. A nil sort of day.
The Cocos IDE will show errors in bold; the same bold it uses for global variables, which is
confusing at mes. But it helps nonetheless. Just make a habit of scanning your code for
bold text!
You might need to increase the heap memory inside the IDE. The quickest
way to accomplish this is to nd the le called eclipse.ini inside the
Cocos IDE applicaon folder. On a Mac, this means inside the Cocos IDE
app package: right-click on the app icon, select Show Package Contents,
and then navigate to Contents/MacOS/eclipse.ini.
Then nd the line where you read -Xmx256m or -Xmx512m and change
it to -Xmx1024m.
This might help in slower computers. My laptop crashed a lot while
running the IDE.
www.it-ebooks.info
Introducing Lua!
[ 200 ]
The game – Stone Age
This is a match-three game. You know, the kind of game that is making a couple of companies
a gazillion dollars and making a gazillion other companies clone those games in order to earn
a couple of dollars. Yes, that game!
You must match three or more gems. If you match more than three, a random gem bursts
and turns into a diamond, which you collect for more points.
The game has a mer, and when me runs out, it's game over.
I used prey much the same structure as in the previous games in this book. But I broke it
into separate modules so it's easier for you to use the code as a reference.
We have a MenuScene and a GameScene item. I have prey much all Cocos2d-x acons in
one module called GridAnimations and most of the interacvity inside another module
called GridController. And all object pools are kept inside a class called ObjectPools.
This is a grid game, so it's perfect to illustrate working with table arrays in Lua, and its main
advantages over C++: it's much easier to create and memory manage dynamic lists (arrays) in
Lua. This exibility, aligned with Cocos2d-x's awesomeness, make for very rapid prototyping
and development. The actual game will look like this:
But before you import the starter project, let me show you how to create a new project
inside the Cocos IDE.
www.it-ebooks.info
Chapter 10
[ 201 ]
Time for action – creating or importing a project
Nothing could be simpler; since the IDE is based on Eclipse, you know most of its main
funconalies already:
1. First let's set up the IDE to use the Lua bindings. Go to Preferences | Cocos | Lua,
and in the drop-down menu for Lua Frameworks, nd the Cocos2d-x framework
folder you downloaded:
2. Select File | New | Cocos Lua Project, if that opon is already available, or select
File | New | Other | Cocos Lua | Cocos Lua Project.
3. In the New Cocos Project wizard, give your project a name and click Next.
4. In the next dialogue, you can choose your project's orientaon and design size.
And that's it. Click Finish.
5. In order to import a project, click File | Import then Cocos | Import Cocos
Project, and navigate to the project start folder for this chapter. The game is called
StoneAge. (Download this chapter's source les from this book's website if you
haven't done so already. There is a starter project and a nal project that you can
run and test.)
What just happened?
You learned to create and import a project into the Cocos IDE. Since the IDE is an
Eclipse-based program, the steps should be familiar to you by now.
You may also wish to change the sengs for the simulator. For that, all you need to do
is right-click on your project and select either Run As... or Debug As..., and then Run or
Debug Conguraons.
www.it-ebooks.info
Introducing Lua!
[ 202 ]
It's best to leave the default for the Mac OSX runme (if you're on a Mac of course), as this is
the fastest opon. But if you wish to change the simulator, here is where you do it:
On my machine, version 3.4 of the framework threw compile errors. I
had to add two xes in order to run Stone Age. In cocos-cocos2d-
Cocos2dConstants.lua, just before the last table is declared, I added
this line:
cc.AsyncTaskPool = {}
Similarly, in cocos-ui-GuiConstants.lua, I added ccui.
LayoutComponent = {} before new tables are added to
LayoutComponent, also near the end of the le.
If you run into problems, switch to version 3.3, which was much more stable
for Lua development.
www.it-ebooks.info
Chapter 10
[ 203 ]
Time for action – setting up our screen resolution
The old AppDelegate class logic now exists inside a le called main.lua:
1. In the IDE, open the main.lua le inside the src folder.
2. Aer the line where we set the animaon interval, type the following:
cc.Director:getInstance():getOpenGLView():
setDesignResolutionSize(640, 960,
cc.ResolutionPolicy.SHOW_ALL)
local screenSize =
cc.Director:getInstance():getVisibleSize()
local designSize = cc.size(640, 960)
if (screenSize.width > 320) then
cc.Director:getInstance():setContentScaleFactor(640/
designSize.width)
cc.FileUtils:getInstance():addSearchPath("res/hd/")
else
cc.Director:getInstance():setContentScaleFactor(320/
designSize.width)
cc.FileUtils:getInstance():addSearchPath("res/sd/")
end
3. I designed the game for iPhone rena, and here we set the appropriate scale
and asset folder for both rena and non-rena phones. Next, let's preload the
sound les:
local bgMusicPath =
cc.FileUtils:getInstance():fullPathForFilename("background.mp3")
cc.SimpleAudioEngine:getInstance():preloadMusic(bgMusicPa
th)
local effectPath =
cc.FileUtils:getInstance():fullPathForFilename("match.wav")
cc.SimpleAudioEngine:getInstance():preloadEffect(effectPa
th)
effectPath =
cc.FileUtils:getInstance():fullPathForFilename("diamond.wav")
cc.SimpleAudioEngine:getInstance():preloadEffect(effectPa
th)
effectPath =
cc.FileUtils:getInstance():fullPathForFilename("diamond2.wav")
cc.SimpleAudioEngine:getInstance():preloadEffect(effectPa
th)
effectPath =
cc.FileUtils:getInstance():fullPathForFilename("wrong.wav")
cc.SimpleAudioEngine:getInstance():preloadEffect(effectPath)
www.it-ebooks.info
Introducing Lua!
[ 204 ]
4. And nally, let's set the ball rolling by creang and running our rst scene:
--create scene
local scene = require("MenuScene")
local menuScene = scene.create()
if cc.Director:getInstance():getRunningScene() then
cc.Director:getInstance():replaceScene(menuScene)
else
cc.Director:getInstance():runWithScene(menuScene)
end
What just happened?
Like we've done in prey much every game so far, we set the resoluon policy and scale
factor for our applicaon and preloaded the sounds we'll be using.
The game was designed only for phones this me, and it was designed with the iPhone 4
screen in mind, and it resizes to older phones.
But don't run the game just yet. Let's create our menu scene. It has a lile of everything in it
and it will be a perfect introducon to the Cocos2d-x API in Lua.
Time for action – creating a menu scene
Let's create a new le and add a menu scene to our game:
1. Right-click on the src folder and select New | Lua File; call the new le
MenuScene.lua.
2. Let's create a class that extends a scene. We rst load our own module of all the
game's constants (this le already exists in the starter project):
local constants = require ("constants")
3. Then we build our class:
local MenuScene = class("MenuScene", function()
return cc.Scene:create()
end)
function MenuScene.create()
local scene = MenuScene.new()
return scene
end
function MenuScene:ctor()
www.it-ebooks.info
Chapter 10
[ 205 ]
self.visibleSize =
cc.Director:getInstance():getVisibleSize()
self.middle = {x = self.visibleSize.width * 0.5,
y = self.visibleSize.height * 0.5}
self.origin = cc.Director:getInstance():getVisibleOrigin()
self:init()
end
return MenuScene
We'll add the methods next, including the init method we called in the class
constructor (always called ctor), but I wanted to stress the importance of returning
the class at the end of its declaraon.
4. So moving just below the constructor, let's connue building up our scene:
function MenuScene:init ()
local bg = cc.Sprite:create("introbg.jpg")
bg:setPosition(self.middle.x, self.middle.y)
self:addChild(bg)
--create pterodactyl animation
local pterodactyl = cc.Sprite:create("ptero_frame1.png")
pterodactyl:setPosition(cc.p(self.visibleSize.width + 100,
self.visibleSize.height * 0.8))
self:addChild(pterodactyl)
local animation = cc.Animation:create()
local number, name
for i = 1, 3 do
number = i
name = "ptero_frame"..number..".png"
animation:addSpriteFrameWithFile(name)
end
animation:setDelayPerUnit(0.5 / 3.0)
animation:setRestoreOriginalFrame(true)
animation:setLoops(-1)
local animate = cc.Animate:create(animation)
pterodactyl:runAction( animate )
local moveOut = cc.MoveTo:create(0, cc.p(self.visibleSize.width
+ 100, self.visibleSize.height *
0.8))
local moveIn = cc.MoveTo:create(4.0, cc.p(-100,
self.visibleSize.height * 0.8))
local delay = cc.DelayTime:create(2.5)
pterodactyl:runAction(cc.RepeatForever:create
(cc.Sequence:create(moveOut, moveIn, delay) ) )
www.it-ebooks.info
Introducing Lua!
[ 206 ]
local character = cc.Sprite:create("introCharacter.png")
character:setPosition(self.middle.x, self.middle.y + 110)
self:addChild(character)
local frame = cc.Sprite:create("frame.png")
frame:setPosition(self.middle.x, self.middle.y)
self:addChild(frame)
end
With this, we added a background and two other sprites, plus an animaon of a
pterodactyl ying in the background. Once again, the calls are remarkably similar
to the ones in C++.
5. Now let's add a menu with a play buon (all this sll inside the init method):
--create play button
local function playGame()
local bgMusicPath = cc.FileUtils:getInstance():fullPathForFi
lename("background.mp3")
cc.SimpleAudioEngine:getInstance():playMusic(bgMusicPath, true)
local scene = require("GameScene")
local gameScene = scene.create()
cc.Director:getInstance():replaceScene(gameScene)
end
local btnPlay = cc.MenuItemImage:create("playBtn.png",
"playBtnOver.png")
btnPlay:setPosition(0,0)
btnPlay:registerScriptTapHandler(playGame)
local menu = cc.Menu:create(btnPlay)
menu:setPosition(self.middle.x, 80)
self:addChild(menu)
Typing the buon's callback inside the same method where the callback is referenced is
similar to wring a block or even a lambda funcon in C++.
What just happened?
You created a scene in Lua with Cocos2d-x using a menu, a few sprites, and an animaon.
It's easy to see how similar the calls are in the Lua bindings to the original C++ ones.
And with code compleon inside the IDE, nding the correct methods is a breeze.
Now let's tackle the GameScene class.
www.it-ebooks.info
Chapter 10
[ 207 ]
One of the nicest features of Lua is something called live coding, and it's
switched on by default in the Cocos IDE. To see what I mean by live coding,
do this: while the game is running in the simulator, change the posion of the
character sprite in your code and save it. You should see the change taking
eect in the simulator. This is a great way to build UI and game scenes.
Time for action – creating our game scene
The GameScene class is already added to the start project and some of the code is already in
place. We'll focus rst on building the game's interface and listening to touches:
1. Let's work on the addTouchEvents method:
function GameScene:addTouchEvents()
local bg = cc.Sprite:create("background.jpg")
bg:setPosition(self.middle.x, self.middle.y)
self:addChild(bg)
local function onTouchBegan(touch, event)
self.gridController:onTouchDown(touch:getLocation())
return true
end
local function onTouchMoved(touch, event)
self.gridController:onTouchMove(touch:getLocation())
end
local function onTouchEnded(touch, event)
self.gridController:onTouchUp(touch:getLocation())
end
local listener = cc.EventListenerTouchOneByOne:create()
listener:registerScriptHandler
(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN )
listener:registerScriptHandler
(onTouchMoved,cc.Handler.EVENT_TOUCH_MOVED )
listener:registerScriptHandler
(onTouchEnded,cc.Handler.EVENT_TOUCH_ENDED )
local eventDispatcher = bg:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority
(listener, bg)
end
www.it-ebooks.info
Introducing Lua!
[ 208 ]
2. Once again, we register the events with the node's instance of the event dispatcher.
The actual touches are handled by our GridController object. We'll go over
those later; rst, let's build the UI. Time to work on the init method:
function GameScene:init ()
self.gridController = GridController:create()
self.gridAnimations = GridAnimations:create()
self.objectPools = ObjectPools.create()
self.gridAnimations:setGameLayer(self)
self.gridController:setGameLayer(self)
self.objectPools:createPools(self)
Create our special objects, one to handle user interacvity, another for animaons,
and our good old object pool.
3. Next, we add a couple of nodes and our score labels:
self:addChild( self.gemsContainer )
self.gemsContainer:setPosition( 25, 80)
--build interface
local frame = cc.Sprite:create("frame.png")
frame:setPosition(self.middle.x, self.middle.y)
self:addChild(frame)
local diamondScoreBg = cc.Sprite:create("diamondScore.png")
diamondScoreBg:setPosition(100, constants.SCREEN_HEIGHT - 30)
self:addChild(diamondScoreBg)
local scoreBg = cc.Sprite:create("gemsScore.png")
scoreBg:setPosition(280, constants.SCREEN_HEIGHT - 30)
self:addChild(scoreBg)
local ttfConfig = {}
ttfConfig.fontFilePath="fonts/myriad-pro.ttf"
ttfConfig.fontSize=20
self.diamondScoreLabel = cc.Label:createWithTTF(ttfConfig,
"0", cc.TEXT_ALIGNMENT_RIGHT , 150)
self.diamondScoreLabel:setPosition
(140, constants.SCREEN_HEIGHT - 30)
self:addChild(self.diamondScoreLabel)
self.scoreLabel = cc.Label:createWithTTF(ttfConfig,
"0", cc.TEXT_ALIGNMENT_RIGHT , 150)
self.scoreLabel:setPosition (330, constants.SCREEN_HEIGHT - 30)
self:addChild(self.scoreLabel)
end
The main dierence when compared to the C++ implementaon of Label:createWithTTF
is that, in Lua, we have a conguraon table for the font.
www.it-ebooks.info
Chapter 10
[ 209 ]
What just happened?
This me, we saw how to register for touch events and how to create true type font labels.
Next, we'll go over creang a typical grid for a match-three game.
Time for action – building the gems
There are basically two types of match-three games, those in which the selecon of
matches takes place automacally and those in which the matches are selected by the
player. Candy Crush is a good example of the former, and Diamond Dash of the laer.
When building the rst type of game, you must add extra logic to ensure you start the
game with a grid that contains no matches. This is what we'll do now:
1. We start with the buildGrid method:
function GameScene:buildGrid ()
math.randomseed(os.clock())
self.enabled = false
local g
for c = 1, constants.GRID_SIZE_X do
self.grid[c] = {}
self.gridGemsColumnMap[c] = {}
for r = 1, constants.GRID_SIZE_Y do
if (c < 3) then
self.grid[c][r] =
constants.TYPES[ self:getVerticalUnique(c,r) ]
else
self.grid[c][r] =
constants.TYPES[ self:getVerticalHorizontalUnique(c,r) ]
end
g = Gem:create()
g:setType( self.grid[c][r] )
g:setPosition ( c * (constants.TILE_SIZE +
constants.GRID_SPACE),
r * (constants.TILE_SIZE +
constants.GRID_SPACE))
self.gemsContainer:addChild(g)
self.gridGemsColumnMap[c][r] = g
table.insert(self.allGems, g)
end
end
self.gridAnimations:animateIntro()
end
www.it-ebooks.info
Introducing Lua!
[ 210 ]
Ensure that we generate a dierent random series of gems each me we run
the game by changing the randomseed value.
The enabled property will stop user interacons while the grid is being altered
or animated.
The grid is a two-dimensional array of columns of gems. The magic happens in
the getVerticalUnique and getVerticalHorizontalUnique methods.
2. To ensure that none of the gems will form a three-gem-match on the rst two
columns, we check them vercally:
function GameScene:getVerticalUnique (col, row)
local type = math.floor (math.random () *
#constants.TYPES + 1 )
if (self.grid[col][row-1] == constants.TYPES[type] and
self.grid[col][row-2] ~= nil and self.grid[col][row-2] ==
constants.TYPES[type]) then
type = type + 1;
if (type == #constants.TYPES + 1) then type = 1 end
end
return type
end
All this code is doing is checking a column to see if any gem is forming a string of
three connected gems of the same type.
3. Then, we check both vercally and horizontally, starng with column 3:
function GameScene:getVerticalHorizontalUnique (col, row)
local type = self:getVerticalUnique (col, row)
if (self.grid[col - 1][row] == constants.TYPES[type] and
self.grid[col - 2][row] ~= nil and self.grid[col - 2][row] ==
constants.TYPES[type]) then
local unique = false
while unique == false do
type = self:getVerticalUnique (col, row)
if (self.grid[col-1][row] == constants.TYPES[type] and
self.grid[col - 2 ][row] ~= nil and self.grid[col -
2 ][row] == constants.TYPES[type]) then
--do nothing
else
unique = true
end
end
end
return type
end
This algorithm is doing the same thing we did previously with the columns, but it's also
checking on individual rows.
www.it-ebooks.info
Chapter 10
[ 211 ]
What just happened?
We created a grid of gems, free of any three-gem matches. Again, if we had built the
sort of match-three game where the user must select clusters of matched gems to have
these removed from the grid (like Diamond Dash), we would not have to bother with this
logic at all.
Next, let's manipulate the grid with gem swaps, idencaon of matches, and grid collapse.
Time for action – changing the grid with GridController
The GridController object iniates all grid changes since it's where we handle touches.
In the game, the user can drag a gem to swap places with another, or rst select the gem
they want to move and then select the gem they want to swap places with in a two-touch
process. Let's add the touch handling for that:
1. In GridController, let's add the logic to onTouchDown:
function GridController:onTouchDown (touch)
if (self.gameLayer.running == false) then
local scene = require("GameScene")
local gameScene = scene.create()
cc.Director:getInstance():replaceScene(gameScene)
local bgMusicPath =
cc.FileUtils:getInstance():fullPathForFilename("background.mp3")
cc.SimpleAudioEngine:getInstance():playMusic(bgMusicPath,
true)
return
end
If we are displaying the game over screen, restart the scene.
2. Next, we nd the gem the user is trying to select:
self.touchDown = true
if (self.enabled == false) then return end
local touchedGem = self:findGemAtPosition (touch)
if (touchedGem.gem ~= nil ) then
if (self.gameLayer.selectedGem == nil) then
self:selectStartGem(touchedGem)
else
if (self:isValidTarget(touchedGem.x,
touchedGem.y, touch) == true) then
self:selectTargetGem(touchedGem)
else
www.it-ebooks.info
Introducing Lua!
[ 212 ]
if (self.gameLayer.selectedGem ~= nil)
then self.gameLayer.selectedGem:deselect() end
self.gameLayer.selectedGem = nil
self:selectStartGem (touchedGem)
end
end
end
end
We nd the gem closest to the touch posion. If the user has not selected a gem
yet (selectedGem = nil), we set the one just touched as the rst gem selected.
Otherwise, we determine whether the second gem selected can be used for a swap.
Only gems above and below the rst selected gem, or the ones to the le and right
of it, can be swapped with. If that is valid, we use the second gem as the target gem.
3. Before moving on to onTouchMove and onTouchUp, let's see how we determine
which gem is being selected and which gem is a valid target gem. So let's work on
the findGemAtPosition value. Begin by determining where in the grid container
the touch landed:
function GridController:findGemAtPosition (position)
local mx = position.x
local my = position.y
local gridWidth = constants.GRID_SIZE_X *
(constants.TILE_SIZE + constants.GRID_SPACE)
local gridHeight = constants.GRID_SIZE_Y *
(constants.TILE_SIZE + constants.GRID_SPACE)
mx = mx - self.gameLayer.gemsContainer:getPositionX()
my = my - self.gameLayer.gemsContainer:getPositionY()
if (mx < 0) then mx = 0 end
if (my < 0) then my = 0 end
if (mx > gridWidth) then mx = gridWidth end
if (my > gridHeight) then my = gridHeight end
4. Here is where the magic happens. We use the x and y posion of the touch inside
the grid to determine the index of the gem inside the array:
local x = math.ceil ((mx - constants.TILE_SIZE * 0.5) /
(constants.TILE_SIZE + constants.GRID_SPACE))
local y = math.ceil ((my - constants.TILE_SIZE * 0.5) /
(constants.TILE_SIZE + constants.GRID_SPACE))
if (x < 1) then x = 1 end
if (y < 1) then y = 1 end
if (x > constants.GRID_SIZE_X) then x =
constants.GRID_SIZE_X end
www.it-ebooks.info
Chapter 10
[ 213 ]
if (y > constants.GRID_SIZE_Y) then y =
constants.GRID_SIZE_Y end
return {x = x, y = y, gem =
self.gameLayer.gridGemsColumnMap[x][y]}
end
We nish by checking whether the touch is out of array bounds.
5. And now let's see the logic to determine whether the target gem is a valid target:
function GridController:isValidTarget (px, py, touch)
local offbounds = false
if (px > self.gameLayer.selectedIndex.x + 1) then
offbounds = true end
if (px < self.gameLayer.selectedIndex.x - 1) then
offbounds = true end
if (py > self.gameLayer.selectedIndex.y + 1) then
offbounds = true end
if (py < self.gameLayer.selectedIndex.y - 1) then
offbounds = true end
We rst check to see whether the target gem is at the top, boom, le, or right of
the selected gem:
local cell = math.sin (math.atan2
(math.pow( self.gameLayer.selectedIndex.x - px, 2),
math.pow( self.gameLayer.selectedIndex.y- py, 2) ) )
if (cell ~= 0 and cell ~= 1) then
offbounds = true
end
if (offbounds == true) then
return false
end
We next use a bit of trig magic to determine whether the selected target gem is
diagonal to the selected gem:
local touchedGem = self.gameLayer.gridGemsColumnMap[px][py]
if (touchedGem.gem == self.gameLayer.selectedGem or
(px == self.gameLayer.selectedIndex.x and
py == self.gameLayer.selectedIndex.y)) then
self.gameLayer.targetGem = nil
return false
end
return true
end
We nish by checking whether the target gem is not the same as the previously
selected gem.
www.it-ebooks.info
Introducing Lua!
[ 214 ]
6. Now, let's move on to the onTouchUp event handling:
function GridController:onTouchUp (touch)
if (self.gameLayer.running == false) then return end
self.touchDown = false
if (self.enabled == false) then return end
if (self.gameLayer.selectedGem ~= nil) then
self.gameLayer:dropSelectedGem() end
end
Prey simple! We just change the z layering of the selected gem, as we want to
make sure that the gem is shown above the others when the swap takes place.
So when we release the gem, we push it back to its original z level (which is what
the dropSelectedGem method does, and we'll see how it does this soon).
7. The onTouchMove event handles the opon of dragging the selected gem unl it
swaps places with another gem:
function GridController:onTouchMove (touch)
if (self.gameLayer.running == false) then return end
if (self.enabled == false) then return end
--track to see if we have a valid target
if (self.gameLayer.selectedGem ~= nil and
self.touchDown == true) then
self.gameLayer.selectedGem:setPosition(
touch.x - self.gameLayer.gemsContainer:getPositionX(),
touch.y - self.gameLayer.gemsContainer:getPositionY())
local touchedGem = self:findGemAtPosition (touch)
if (touchedGem.gem ~= nil and self:isValidTarget(touchedGe
m.x, touchedGem.y, touch) == true ) then
self:selectTargetGem(touchedGem)
end
end
end
We run most of the same logic as we did with onTouchDown. We move the
selectedGem object unl a suitable target gem is idened, and then we pick
the second one as the target. This is when the swap happens. Let's do that now.
www.it-ebooks.info
Chapter 10
[ 215 ]
8. First, the logic that sets our selected gem:
function GridController:selectStartGem (touchedGem)
if (self.gameLayer.selectedGem == nil) then
self.gameLayer.selectedGem = touchedGem.gem
self.gameLayer.targetGem = nil
self.gameLayer.targetIndex = nil
touchedGem.gem:setLocalZOrder(constants.Z_SWAP_2)
self.gameLayer.selectedIndex = {x = touchedGem.x,
y = touchedGem.y}
self.gameLayer.selectedGemPosition =
{x = touchedGem.gem:getPositionX(),
y =
touchedGem.gem:getPositionY()}
self.gameLayer.gridAnimations:animateSelected
(touchedGem.gem)
end
end
We start the swapping process; we have a selected gem but no target gem.
We change the layering of the selected gem through setLocalZOrder.
We also make the selected gem rotate 360 degrees.
9. Then, we're ready to select the target gem:
function GridController:selectTargetGem (touchedGem)
if (self.gameLayer.targetGem ~= nil) then return end
self.enabled = false
self.gameLayer.targetIndex = {x = touchedGem.x,
y = touchedGem.y}
self.gameLayer.targetGem = touchedGem.gem
self.gameLayer.targetGem:setLocalZOrder(constants.Z_SWAP_1)
self.gameLayer:swapGemsToNewPosition()
end
It is now that we nally call our GameScene class and ask it to swap the gems.
What just happened?
We just added the logic to handle all the user interacvity. Now, all that's le for us to do is
handle the swaps, checking for matches and collapsing the grid. Let's do it!
www.it-ebooks.info
Introducing Lua!
[ 216 ]
Time for action – swapping the gems and looking for matches
The swapping logic is found in GameScene in the swapGemsToNewPosition method:
1. The swapGemsToNewPosition method makes one call to GridAnimations to
animate the swap between the selected and target gem. Once this animaon is
complete, we re a onNewSwapComplete method. The majority of the logic takes
place in there:
function GameScene:swapGemsToNewPosition ()
local function onMatchedAnimatedOut (sender)
self:collapseGrid()
end
local function onReturnSwapComplete (sender)
self.gridController.enabled = true
end
local function onNewSwapComplete (sender)
self.gridGemsColumnMap[self.targetIndex.x][self.
targetIndex.y]
= self.selectedGem
self.gridGemsColumnMap[self.selectedIndex.x][self.
selectedIndex.y] =
self.targetGem
self.grid[self.targetIndex.x][self.targetIndex.y] =
self.selectedGem.type
self.grid[self.selectedIndex.x][self.selectedIndex.y] =
self.targetGem.type
The call back switches the gems around inside the grid array.
self.combos = 0
self.addingCombos = true
Combos are used to track if we have more than 3 gems matched
after the player's move.
--check for new matches
if (self.gridController:checkGridMatches() == true) then
2. If we have a match, we run animaons on the matched gems, otherwise we
play a swap back animaon and play a sound eect to represent a wrong move
by the player:
--animate matched gems
if (#self.gridController.matchArray > 3) then
self.combos = self.combos + (#self.gridController.matchArray -
3) end
www.it-ebooks.info
Chapter 10
[ 217 ]
self.gridAnimations:animateMatches
(self.gridController.matchArray, onMatchedAnimatedOut)
self:showMatchParticle
(self.gridController.matchArray)
self:setGemsScore(#self.gridController.matchArray *
constants.POINTS)
self:playFX("match2.wav")
else
--no matches, swap gems back
self.gridAnimations:swapGems (self.targetGem,
self.selectedGem, onReturnSwapComplete)
self.gridGemsColumnMap[self.targetIndex.x][self.
targetIndex.y]
= self.targetGem
self.gridGemsColumnMap[self.selectedIndex.x][self.
selectedIndex.y]
= self.selectedGem
self.grid[self.targetIndex.x][self.targetIndex.y] =
self.targetGem.type
self.grid[self.selectedIndex.x][self.selectedIndex.y]
=
self.selectedGem.type
self:playFX("wrong.wav")
end
At the end of each new animaon, be it the match one or the swap back one,
we once again run callbacks listed at the top of the method. The most important
thing these do is the call to collapseGrid done when the matched gems nish
animang inside the onMatchedAnimatedOut callback:
self.selectedGem = nil
self.targetGem = nil
end
We end the callback by clearing the selected gems and start with a clean slate.
3. And here, at the end of the funcon, we call the swap gems animaon with
onNewSwapComplete as its callback:
self.gridAnimations:swapGems (self.selectedGem, self.targetGem,
onNewSwapComplete)
end
www.it-ebooks.info
Introducing Lua!
[ 218 ]
4. Let's move back to GridController and add the checkGridMatches method.
This is broken into three parts:
function GridController:checkGridMatches ()
self.matchArray = {}
for c = 1, constants.GRID_SIZE_X do
for r = 1, constants.GRID_SIZE_Y do
self:checkTypeMatch(c,r)
end
end
if (#self.matchArray >= 2) then
self.gameLayer:addToScore()
return true
end
print("no matches")
return false
end
This method starts the check by running checkTypeMatch on each cell.
5. The checkTypeMatch method searches around the current index and looks for
matches at the top, boom, le, and right of the index:
function GridController:checkTypeMatch (c, r)
local type = self.gameLayer.grid[c][r]
local stepC = c
local stepR = r
local temp_matches = {}
--check top
while stepR -1 >= 1 and self.gameLayer.grid[c][stepR-1] ==
type do
stepR = stepR - 1
table.insert (temp_matches, {x = c, y = stepR})
end
if (#temp_matches >= 2) then self:addMatches (temp_matches)
end
temp_matches = {}
--check bottom
stepR = r
while stepR + 1 <= constants.GRID_SIZE_Y
and self.gameLayer.grid[c][stepR + 1] == type do
stepR = stepR + 1
table.insert (temp_matches, {x = c, y= stepR})
end
www.it-ebooks.info
Chapter 10
[ 219 ]
if (#temp_matches >= 2) then self:addMatches (temp_matches)
end
temp_matches = {}
--check left
while stepC - 1 >= 1 and self.gameLayer.grid[stepC - 1][r]
== type do
stepC = stepC - 1
table.insert (temp_matches, {x = stepC, y= r})
end
if (#temp_matches >= 2) then self:addMatches (temp_matches)
end
temp_matches = {}
--check right
stepC = c;
while stepC + 1 <= constants.GRID_SIZE_X and
self.gameLayer.grid[stepC + 1][r] == type do
stepC = stepC + 1
table.insert (temp_matches, {x = stepC, y = r})
end
if (#temp_matches >= 2) then self:addMatches (temp_matches)
end
end
If any matches are found, they are added to the matches array.
6. But rst we need to make sure there are no duplicates listed there, so when we
add a gem to the matches array, we check whether it has not been added already:
function GridController:addMatches (matches)
for key, value in pairs(matches) do
if (self:find(value, self.matchArray) == false) then
table.insert(self.matchArray, value)
end
end
end
7. And the simple method to look for duplicates:
function GridController:find (np, array)
for key, value in pairs(array) do
if (value.x == np.x and value.y == np.y) then return true
end
end
return false
end
www.it-ebooks.info
Introducing Lua!
[ 220 ]
What just happened?
Finding matches is more than half the necessary logic for any match-three game. All you
need to do is traverse the grid as eecvely as you can and look for repeated paerns.
The rest of the logic concerns the grid collapse. We'll do that next and then we're ready to
publish the game.
Time for action – collapsing the grid and repeating
So the ow of the game is move pieces around, look for matches, remove those,
collapse the grid and add new gems, look for matches again, and if necessary, do the
whole process in a loop:
1. This is the longest method in the game, and again, most of the logic happens inside
callbacks. First we tag the gems being removed by seng their type data to -1.
All the gems inside matchArray will be removed:
function GameScene:collapseGrid ()
for i = 1, #self.gridController.matchArray do
self.grid[self.gridController.matchArray[i].x]
[self.gridController.matchArray[i].y] = -1
end
local column = nil
local newColumn = nil
local i
2. Next, we traverse the grid's columns and rearrange the gems whose type is not
equal to -1 inside the column arrays. Essenally, we update the data here so that
gems above the ones removed "fall down". The actual change will take place in the
animateCollapse method:
for c = 1, constants.GRID_SIZE_X do
column = self.grid[c]
newColumn = {}
i = 1
while #newColumn < #column do
if (#column > i) then
if (column[i] ~= -1) then
--move gem
table.insert(newColumn, column[i])
end
else
www.it-ebooks.info
Chapter 10
[ 221 ]
--create new gem
table.insert(newColumn, 1, column[i])
end
i = i+1
end
self.grid[c] = newColumn
end
self.gridAnimations:animateCollapse
(onGridCollapseComplete)
end
3. But now, let's code the callback of that animaon called
onGridCollapseComplete. So above the code we've entered already inside
collapseGrid, we add the local funcon:
local function onGridCollapseComplete (sender)
local function onMatchedAnimatedOut (sender)
self:collapseGrid()
end
for i = 1, #self.allGems do
local gem = self.allGems[i]
local xIndex = math.ceil ((gem:getPositionX() -
constants.TILE_SIZE * 0.5) / (constants.TILE_SIZE +
constants.GRID_SPACE))
local yIndex = math.ceil ((gem:getPositionY() -
constants.TILE_SIZE * 0.5) / (constants.TILE_SIZE +
constants.GRID_SPACE))
self.gridGemsColumnMap[xIndex][yIndex] = gem
self.grid[xIndex][yIndex] = gem.type
end
First, we update the array of sprites, sorng them by the new x and y indexes
of the grid.
4. Then, we check for matches again. Remember that this callback runs aer the grid
collapse animaon has nished, which means new gems have been added already
and these may create new matches (we'll look at the logic soon):
if (self.gridController:checkGridMatches () == true) then
--animate matched games
if (self.addingCombos == true) then
if (#self.gridController.matchArray > 3) then
self.combos = self.combos + (#self.gridController.matchArray -
3) end
end
www.it-ebooks.info
Introducing Lua!
[ 222 ]
self.gridAnimations:animateMatches
(self.gridController.matchArray, onMatchedAnimatedOut)
self:showMatchParticle (self.gridController.matchArray)
self:setGemsScore(#self.gridController.matchArray *
constants.POINTS)
self:playFX("match.wav")
5. Then, if we nd no more matches, we replace some random gems with diamonds
if the value for combos is above 0 (meaning we had more than a 3 gem match in
the last player's move):
else
--no more matches, check for combos
if (self.combos > 0) then
--now turn random gems into diamonds
local diamonds = {}
local removeGems = {}
local i = 0
math.randomseed(os.clock())
while i < self.combos do
i = i + 1
local randomGem = nil
local randomX,randomY = 0
while randomGem == nil do
randomX = math.random(1, constants.GRID_SIZE_X)
randomY = math.random(1, constants.GRID_SIZE_Y)
randomGem = self.gridGemsColumnMap[randomX][randomY]
if (randomGem.type == constants.TYPE_GEM_WHITE)
then randomGem = nil end
end
6. And we pick random gems for the diamonds:
local diamond = self.objectPools:getDiamond()
diamond:setPosition(randomGem:getPositionX(),
randomGem:getPositionY())
local diamondParticle =
self.objectPools:getDiamondParticle()
diamondParticle:setPosition(randomGem:getPositionX(),
randomGem:getPositionY())
table.insert(diamonds, diamond)
table.insert(removeGems, {x=randomX, y=randomY})
end
self:setDiamondScore(#diamonds *
constants.DIAMOND_POINTS)
www.it-ebooks.info
Chapter 10
[ 223 ]
Animate the diamonds being collected, and at the end of that animaon, call back
onMatchedAnimatedOut, which will collapse the grid once more now that we had
gems "burst" into diamonds:
self.gridAnimations:animateMatches(removeGems,
onMatchedAnimatedOut)
self.gridAnimations:collectDiamonds(diamonds)
self.combos = 0
self:playFX("diamond2.wav")
else
self.gridController.enabled = true
end
self.addingCombos = false
end
end
7. Here's the whole collapseGrid method:
function GameScene:collapseGrid ()
local function onGridCollapseComplete (sender)
local function onMatchedAnimatedOut (sender)
self:collapseGrid()
end
for i = 1, #self.allGems do
local gem = self.allGems[i]
local xIndex = math.ceil ((gem:getPositionX() -
constants.TILE_SIZE * 0.5) / (constants.TILE_SIZE +
constants.GRID_SPACE))
local yIndex = math.ceil ((gem:getPositionY() -
constants.TILE_SIZE * 0.5) / (constants.TILE_SIZE +
constants.GRID_SPACE))
self.gridGemsColumnMap[xIndex][yIndex] = gem
self.grid[xIndex][yIndex] = gem.type
end
if (self.gridController:checkGridMatches () == true) then
--animate matched games
if (self.addingCombos == true) then
if (#self.gridController.matchArray > 3) then
self.combos = self.combos + (#self.gridController.matchArray -
3) end
end
self.gridAnimations:animateMatches
(self.gridController.matchArray, onMatchedAnimatedOut)
self:showMatchParticle
(self.gridController.matchArray)
www.it-ebooks.info
Introducing Lua!
[ 224 ]
self:setGemsScore(#self.gridController.matchArray *
constants.POINTS)
self:playFX("match.wav")
else
--no more matches, check for combos
if (self.combos > 0) then
--now turn random gems into diamonds
local diamonds = {}
local removeGems = {}
local i = 0
math.randomseed(os.clock())
while i < self.combos do
i = i + 1
local randomGem = nil
local randomX,randomY = 0
while randomGem == nil do
randomX =
math.random(1, constants.GRID_SIZE_X)
randomY =
math.random(1, constants.GRID_SIZE_Y)
randomGem =
self.gridGemsColumnMap[randomX][randomY]
if (randomGem.type ==
constants.TYPE_GEM_WHITE) then randomGem = nil end
end
local diamond =
self.objectPools:getDiamond()
diamond:setPosition(randomGem:getPositionX(),
randomGem:getPositionY())
local diamondParticle =
self.objectPools:getDiamondParticle()
diamondParticle:setPosition(randomGem:getPosit
ionX(),
randomGem:getPositionY())
table.insert(diamonds, diamond)
table.insert(removeGems, {x=randomX,
y=randomY})
end
self:setDiamondScore(#diamonds *
constants.DIAMOND_POINTS)
self.gridAnimations:animateMatches(removeGems,
onMatchedAnimatedOut)
self.gridAnimations:collectDiamonds(diamonds)
self.combos = 0
www.it-ebooks.info
Chapter 10
[ 225 ]
self:playFX("diamond2.wav")
else
self.gridController.enabled = true
end
self.addingCombos = false
end
end
for i = 1, #self.gridController.matchArray do
self.grid[self.gridController.matchArray[i].x]
[self.gridController.matchArray[i].y] = -1
end
local column = nil
local newColumn = nil
local i
for c = 1, constants.GRID_SIZE_X do
column = self.grid[c]
newColumn = {}
i = 1
while #newColumn < #column do
if (#column > i) then
if (column[i] ~= -1) then
--move gem
table.insert(newColumn, column[i])
end
else
--create new gem
table.insert(newColumn, 1, column[i])
end
i = i+1
end
self.grid[c] = newColumn
end
self.gridAnimations:animateCollapse
(onGridCollapseComplete)
end
What just happened?
The collapseGrid method collects all the gems aected by matches or gems which
exploded into diamonds. The resulng array is sent to GridAnimations for the proper
animaons to be performed.
We'll work on those next and nish our game.
www.it-ebooks.info
Introducing Lua!
[ 226 ]
Time for action – animating matches and collapses
Now for the last bit of logic: the nal animaons:
1. We'll start with the easy ones:
function GridAnimations:animateSelected (gem)
gem:select()
gem:stopAllActions()
local rotate = cc.EaseBounceOut:create (
cc.RotateBy:create(0.5, 360) )
gem:runAction(rotate)
end
This rotates a gem; we use this animaon when a gem is rst selected.
2. Next is the swap animaon:
function GridAnimations:swapGems
(gemOrigin, gemTarget, onComplete)
gemOrigin:deselect()
local origin = self.gameLayer.selectedGemPosition
local target = cc.p(gemTarget:getPositionX(),
gemTarget:getPositionY())
local moveSelected =
cc.EaseBackOut:create(cc.MoveTo:create(0.8, target) )
local moveTarget =
cc.EaseBackOut:create(cc.MoveTo:create(0.8, origin) )
local callback = cc.CallFunc:create(onComplete)
gemOrigin:runAction(moveSelected)
gemTarget:runAction (cc.Sequence:create(moveTarget, callback))
end
All this does is swap the places of the rst selected gem and the target gem.
3. Then, we add the animaons we run for matched gems:
function GridAnimations:animateMatches (matches, onComplete)
local function onCompleteMe (sender)
self.animatedMatchedGems = self.animatedMatchedGems - 1;
if (self.animatedMatchedGems == 0) then
if (onComplete ~= nil) then onComplete() end
end
end
self.animatedMatchedGems = #matches
local gem = nil
for i, point in ipairs(matches) do
gem = self.gameLayer.gridGemsColumnMap[point.x]
[point.y]
gem:stopAllActions()
www.it-ebooks.info
Chapter 10
[ 227 ]
local scale = cc.EaseBackOut:create
( cc.ScaleTo:create(0.3, 0))
local callback = cc.CallFunc:create(onCompleteMe)
local action = cc.Sequence:create (scale, callback)
gem.gemContainer:runAction(action)
end
end
This will scale down a gem to nothing, and only re the nal callback when all gems
have nish scaling.
4. Next is the collect diamonds animaon:
function GridAnimations:collectDiamonds(diamonds)
local function removeDiamond (sender)
sender:setVisible(false)
end
for i = 1, #diamonds do
local delay = cc.DelayTime:create(i * 0.05)
local moveTo = cc.EaseBackIn:create( cc.MoveTo:create
( 0.8, cc.p(50, constants.SCREEN_HEIGHT - 50) ) )
local action = cc.Sequence:create
(delay, moveTo, cc.CallFunc:create(removeDiamond))
diamonds[i]:runAction(action)
end
end
This moves the diamonds to where the diamond score label is.
5. And now, nally, add the grid collapse:
function GridAnimations:animateCollapse ( onComplete )
self.animatedCollapsedGems = 0
local gem = nil
local drop = 1
for c = 1, constants.GRID_SIZE_X do
drop = 1
for r = 1, constants.GRID_SIZE_Y do
gem = self.gameLayer.gridGemsColumnMap[c][r]
--if this gem has been resized, move it to the top
if (gem.gemContainer:getScaleX() ~= 1) then
gem:setPositionY((constants.GRID_SIZE_Y +
(drop)) * (constants.TILE_SIZE + constants.GRID_SPACE))
self.animatedCollapsedGems =
self.animatedCollapsedGems + 1
gem:setType ( self.gameLayer:getNewGem() )
gem:setVisible(true)
local newY = (constants.GRID_SIZE_Y -
(drop - 1)) * (constants.TILE_SIZE + constants.GRID_SPACE)
www.it-ebooks.info
Introducing Lua!
[ 228 ]
self:dropGemTo (gem, newY, 0.2, onComplete)
drop = drop + 1
else
if (drop > 1) then
self.animatedCollapsedGems =
self.animatedCollapsedGems + 1
local newY = gem:getPositionY() -
(drop - 1) * (constants.TILE_SIZE + constants.GRID_SPACE)
self:dropGemTo (gem, newY, 0, onComplete)
end
end
end
end
end
We loop through all the gems and idenfy the ones that have been scaled down,
meaning the ones which were removed. We move these above the column,
so they will fall down as new gems, and we pick a new type for them:
gem:setType ( self.gameLayer:getNewGem() )
The ones which were not removed will drop to their new posions. The way we
do this is simple. We count how many gems were removed unl we reached a
gem which has not been removed. That count is stored in the local variable drop,
which is reset to 0 with every column.
That way, we know how many gems were removed below other gems. We use that
to nd the new y posion.
6. The dropGemTo new posion looks like this:
function GridAnimations:dropGemTo (gem, y, delay, onComplete)
gem:stopAllActions()
gem:reset()
local function onCompleteMe (sender)
self.animatedCollapsedGems =
self.animatedCollapsedGems - 1;
if ( self.animatedCollapsedGems == 0 ) then
if (onComplete ~= nil) then onComplete() end
end
end
local move = cc.EaseBounceOut:create
(cc.MoveTo:create (0.6, cc.p(gem:getPositionX(), y) ) )
local action = cc.Sequence:create
(cc.DelayTime:create(delay), move,
cc.CallFunc:create(onCompleteMe))
gem:runAction(action)
end
Again, we only re the nal callback once all gems have collapsed. This nal callback will run
another check for matches, as we've seen earlier, starng the whole process again.
www.it-ebooks.info
Chapter 10
[ 229 ]
What just happened?
That's it; we have the three main parts of a match-three game: the swap, the matches,
and the collapse.
There is only one animaon we haven't covered, which is already included in the code
for this chapter, and that is the column drop for the intro animaon when the grid is rst
created. But there's nothing new with that one. Feel free to review it, though.
Now, it's me to publish the game.
Time for action – publishing the game with the Cocos IDE
In order to build and publish the game, we'll need to tell the IDE a few things. I'll show you how
to publish the game for Android, but the steps are very similar for any of the other targets:
1. First, let's tell the IDE where to nd the Android SDK, NDK, and ANT, just as we did
when we installed the Cocos2d-x console. In the IDE, open the Preferences panel.
Then, under Cocos, enter the three paths just like we did before (remember that for
ANT, you need to navigate to its bin folder).
www.it-ebooks.info
Introducing Lua!
[ 230 ]
2. Now, in order to build the project, you need to select the fourth buon at the top
of the IDE (from the le-hand side), or right-click on your project and select Cocos
Tools. You'll have dierent opons available depending on which stage you are at in
the deployment process.
First, the IDE needs to add the nave code support, and then it builds the
project inside a folder called frameworks (it will contain an iOS, Mac OS, Windows,
Android, and Linux version of your project just as if you had created it through the
Cocos console).
3. You can then choose to package the applicaon into an APK or IPA, which you can
transfer to your phone. Or, you can use the generated project inside Eclipse or Xcode.
What just happened?
You just built your Lua game to Android, or iOS, or Windows, or Linux, or Mac OS, or all of
them! Well done.
Summary
That's it. You can now choose between C++ or Lua to build your games. The whole API can
be accessed either way. So, every game created in this book can be done in either language
(and yes, that includes the Box2D API.)
And this is it for the book. I hope you're not too red to start working on your own ideas.
And I hope to see your game someme soon in an App Store near me!
www.it-ebooks.info
[ 231 ]
Vector Calculations with Cocos2d-x
This appendix will cover some of the math concepts used in Chapter 5, On the Line – Rocket
Through, in a lile more detail.
What are vectors?
First, let's do a quick refresh on vectors and the way you can use Cocos2d-x to deal
with them.
So what is the dierence between a vector and a point? At rst, they seem to be
the same. Consider the following point and vector:
Point (2, 3.5)
Vec2 (2, 3.5)
The following gure illustrates a point and a vector:
A
www.it-ebooks.info
Vector Calculaons with Cocos2d-x
[ 232 ]
In this gure, they each have the same value for x and y. So what's the dierence?
With a vector, you always have extra informaon. It is as if, besides those two values for x
and y, we also have the x and y of the vector's origin, which in the previous gure we can
assume to be point (0, 0). So the vector is moving in the direcon described from point
(0, 0) to point (2, 3.5). The extra informaon we can derive then from vectors is direcon
and length (usually referred to as magnitude).
It's as if a vector is a person's stride. We know how long each step is, and we know the
direcon in which the person is walking.
In game development, vectors can be used, among other things, to describe movement
(speed, direcon, acceleraon, fricon, and so on) or the combining forces acng upon a body.
The vector methods
There is a lot you can do with vectors, and there are many ways to create them and
manipulate them. And Cocos2d-x comes bundled with helper methods that will take
care of most of the calculaons for you. Here are some examples:
You have a vector, and you want to get its angle—use getAngle()
You want the length of a vector—use getLength()
You want to subtract two vectors; for example, to reduce the amount of movement
of a sprite by another vector—use vector1 - vector2
You want to add two vectors; for example, to increase the amount of movement
of a sprite by another vector—use vector1 + vector2
You want to mulply a vector; for example, applying a fricon value to the amount
of movement of a sprite—use vector1 * vector2
You want the vector that is perpendicular to another (also known as a vector's
normal)—use getPerp() or getRPerp()
And, most importantly for our game example, you want the dot product of two
vectors—use dot(vector1, vector2)
Now let me show you how to use these methods in our game example.
www.it-ebooks.info
Appendix
[ 233 ]
Using ccp helper methods
In the example of Rocket Through, the game we developed in Chapter 5, On the Line – Rocket
Through, we used vectors to describe movement, and now I want to show you the logic
behind some of the methods we used to handle vector operaons and what they mean.
Rotating the rocket around a point
Let's start, as an example, with the rocket sprite moving with a vector of (5, 0):
We then draw a line from the rocket, say from point A to point B:
Now we want the rocket to rotate around point B. So how can we change the rocket's vector
to accomplish that? With Cocos2d-x, we can use the helper point method rotateByAngle
to rotate a point around any other point. In this case, we rotate the rocket's posion point
around point B by a certain angle.
www.it-ebooks.info
Vector Calculaons with Cocos2d-x
[ 234 ]
But here's a queson – in which direcon should the rocket rotate?
By looking at this gure, you know that the rocket should rotate clockwise, since it's moving
towards the right. But programmacally, how could we determine that, and in the easiest
way possible? We can determine this by using vectors and another property derived from
them: the dot product.
Using the dot product of vectors
The dot product of two vectors describes their angular relaonship. If their dot product
is greater than zero, the two vectors form an angle smaller than 90 degrees. If it is less
than zero, the angle is greater than 90 degrees. And if it is equal to zero, the vectors are
perpendicular. Have a look at this descripve gure:
www.it-ebooks.info
Appendix
[ 235 ]
But one other way to think about this is that if the dot product is a posive value, then the
vectors will "point" in the same direcon. If it is a negave value, they point in opposite
direcons. How can we use that to help us?
A vector will always have two perpendiculars, as shown in the following gure:
These perpendiculars are oen called right and le, or clockwise and counterclockwise
perpendiculars, and they are themselves vectors, known as normals.
Now, if we calculate the dot product between the rocket's vector and each of the
perpendiculars on line AB, you can see that we can determine the direcon the rocket
should rotate in. If the dot product of the rocket and the vector's right perpendicular is a
posive value, it means the rocket is moving towards the right (clockwise). If not, it means
the rocket is moving towards the le (counterclockwise).
www.it-ebooks.info
Vector Calculaons with Cocos2d-x
[ 236 ]
The dot product is very easy to calculate. We don't even need to bother with the formula
(though it's a simple one), because we can use the dot(vector1, vector2) method.
So we have the vector for the rocket already. How do we get the vector for the normals?
First, we get the vector for the AB line. We use another method for this – point1 - point2.
This will subtract points A and B and return a vector represenng that line.
Next, we can get the le and right perpendiculars of that line vector with the getPerp()
and getRPerp() methods respecvely. However, we only need to check one of these.
Then we get the dot product with dot(rocketVector, lineNormal).
If this is the correct normal, meaning the value for the dot product is a posive one, we can
rotate the rocket to point to this normal's direcon; so the rocket will be at a 90-degree
angle with the line at all mes as it rotates. This is easy, because we can convert the normal
vector to an angle with the getAngle() method. All we need to do is apply that angle to
the rocket.
But how fast should the rocket rotate? We'll see how to calculate that next.
Moving from pixel-based speed to angular-based speed
When rotang the rocket, we sll want to show it moving at the same speed as it was when
moving in a straight line, or as close to it as possible. How do we do that?
www.it-ebooks.info
Appendix
[ 237 ]
Remember that the vector is being used to update the rocket's posion in every iteraon.
In the example I gave you, the (5, 0) vector is currently adding 5 pixels to the x posion of
the rocket in every iteraon.
Now let's consider an angular speed. If the angular speed were 15 degrees, and we kept
rotang the rocket's posion by that angle, it would mean the rocket would complete a full
circle in 24 iteraons. Because 360 degrees of a full circle divided by 15 degrees equals 24.
But we don't have the correct angle yet; we only have the amount in pixels the rocket moves
in every iteraon. But math can tell us a lot here.
Math says that the length of a circle is twice the value of Pi, mulplied by the radius of the
circle, usually wrien as 2πr.
We know the radius of the circle we want the rocket to describe. It is the length of the line
we drew.
With that formula, we can get the length in pixels of that circle, also known as its
circumference. Let's say the line has a length of 100 pixels; this would mean the circle about
to be described by the rocket has a length (or circumference) of 628.3 pixels (2 * π * 100).
www.it-ebooks.info
Vector Calculaons with Cocos2d-x
[ 238 ]
With the speed described in the vector (5, 0), we can determine how long it would take the
rocket to complete that pixel length. We don't need this to be absolutely precise; the last
iteraon will most likely move beyond that total length, but it's good enough for our purposes.
When we have the total number of iteraons to complete the length, we can convert that
to an angle. So, if the iteraon value is 125, the angle would be 360 degrees divided by 125;
that is, 2.88. That would be the angle required to describe a circle in 125 iteraons.
Now the rocket can change from pixel-based movement to angular-based movement
without much visual change.
www.it-ebooks.info
[ 239 ]
Pop Quiz Answers
Chapter 4, Fun with Sprites – Sky Defense
Pop quiz – sprites and actions
Q1 2
Q2 1
Q3 3
Q4 4
Chapter 8, Getting Physical – Box2D
Pop quiz
Q1 3
Q2 2
Q3 1
Q4 3
B
www.it-ebooks.info
www.it-ebooks.info
[ 241 ]
Index
A
acons
about 64
creang, with Cocos2d-x 64, 65
ADT bundle
URL, for obtaining from Google 2
ADT plugin
URL, for installaon instrucons 2
Android NDK
URL, for downloading latest version 2
Android SDK
URL, for downloading 2
animaons
creang 66, 67
Apache Ant
URL, for downloading 2
AppDelegate class
about 9, 10
Apple
URL 2
Automac Reference Counng (ARC) 22
B
b2Body object 154
batch drawing sprites
about 55
SpriteBatchNode, creang 56, 57
bitmap fonts, Cocos2d-x
about 58
nal screen sprites, adding 60, 61
labels, creang 59
object pools, creang 63
Block object 106
Box2D project
about 151
building, with Cocos2d-x 147
debug draw, using 148-151
C
C++ 18, 23, 24
ccp helper methods
dot product, using of vectors 234-236
pixel-based speed, moving to angular-based
speed 236-238
rocket, rotang around point 233
using 233
CDT plugins, Eclipse
URL, for installaon instrucons 6
class implementaon
about 20
creang 20-22
class interface
about 18
creang 18-20
Cocos2d-x
about 15, 16, 24
acons 17
www.it-ebooks.info
[ 242 ]
acons, creang with 64, 65
batch drawing sprites 55
Box2D project, building with 147
cache classes 17
containers 16, 17
Director object 17
downloading 1-3
helper methods 17
installing 3, 4
macros 17
parcles 17
primives, drawing 87
references 12
scenes, using 181
scene transion, creang 181, 182
specialized nodes 17
sprite, animang 66
sprite sheets, using 54
structures 17
test samples, running 11
transions, creang with singleton
Layer class 183, 184
URL, for latest stable version 2
Cocos2d-x objects, instanang
C++ 24
stac methods, using 22, 23
CocosBuilder 12
CocosStudio 12
CollisionListener class
methods 163
containers
about 16
Layer 16
Node 17
Scene 16
Sprite 17
controls, Victorian Rush Hour
adding 124
main loop, coding 126, 127
touches, handling 124, 125
D
damping 160
E
Eclipse ADT plugin
URL, for downloading 2
Eskimo, game
.plist le, creang 185, 186
about 178
accelerometer data, reading 192, 193
accelerometer, using 192
b2Bodies, reusing 194
b2Body xture, changing 194
completed levels, storing 189, 190
data, retrieving from .plist le 187-189
event dispatcher, using 191, 192
events, using 190
external data, loading from .plist le 184
level data, loading 186, 187
organizing 180
running, in Android 195, 196
saving 189
sengs 179
G
game
reseng 99-101
game conguraons
about 28
game project, creang 28, 29
rules, laying down 30
GameLayer interface
coding 37, 38
game, running in Android 48, 49
init(), implemenng 38-40
main loop, adding 43-46
mul-touches, handling 41, 42
scores, updang 47, 48
game loop
about 96
main loop, adding 96-99
Glyph Designer
URL 12
www.it-ebooks.info
[ 243 ]
H
hello world example
about 4
AppDelegate class 9
applicaon, creang 4, 5
folder structure 8
HelloWorldScene class 10
iOS linkage classes 9
project, opening in Eclipse 6, 7
HelloWorldScene class
about 10
deleng 36
I
iOS linkage classes 9
L
leerbox 179
LineContainer node
drawing in 87-89
live coding 207
Lua
about 197, 198
code, debugging 199
M
Mac port cfxr
URL 12
main loop, MiniPool game
about 170
adding 170-172
memory
managing 22
MiniPool game
about 155
ball objects, creang 159, 160
collision lters, creang 161
contact listener, creang 163-165
cue, creang 161
edges, creang 158
pool table, creang 157, 158
sengs 156
with b2Body objects, combining with
sprites 156
MiniPool game, controls
about 165
touch events, adding 166-170
N
Node 16
O
objects
instanang 22
Observer Paern 190
P
parallax eect
creang 137, 138
Parcle Designer
URL 12
parcles
about 84
parcle systems, creang 84-86
physics engine
about 151
b2Body object 154
b2World object, creang 152
Box2D 151
Ref objects, avoiding in Box2D 153
simulaon, running 153
pixel to meter (PTM) 152
plaorm collision
about 121
collision detecon, adding 122-124
player
killing 99-101
Player object 106
primives
drawing, in Cocos2d-x 87
Python
URL 3
www.it-ebooks.info
[ 244 ]
R
Ref 25
rena displays
image les, adding 30
rena support, adding 31, 32
supporng 30
rocket sprite
about 89
touches, handling 92-95
updang 90, 91
Rocket Through, game
about 81
game sengs 81
grid, creang 86, 87
parcles 85
project, starng 83
running, in Android 101
screen sengs 83
Start Project opon, using 82
S
sfxr, for Windows
URL 12
sky defense, game
about 51
background music, adding 53
game sengs 52
inializing 54
objects, retrieving from pool 74, 75
playing 76, 77
restarng 71, 72
running 68
running, in Android 77
screen support, adding for universal app 52, 53
starng 71, 72
start project 52
touches, handling 68-70
updang 73
sound eects
adding 32
sound les, adding 32, 33
Sprite
animaons, creang 66, 67
animang, in Cocos2d-x 66
extending 34
GameSprite.cpp, adding 34, 35
GameSprite, implemenng 35, 36
SpriteBuilder 12
sprite sheets
used, in Cocos2d-x 54
stac methods
using 22, 23
Stone Age game
about 200
collapses, animang 226-228
game scene, creang 207, 208
gems, building 209, 210
gems, swapping 216-219
grid, changing with GridController 211-215
grid collapse process, repeang 220- 225
grid, collapsing 220-225
matches, animang 226-228
menu scene, creang 204-206
project, creang 201, 202
project, imporng 201, 202
publishing, with Cocos IDE 229, 230
screen resoluon, seng up 203, 204
T
terrain object
about 106
Block object, coding 110, 111
Blocks object, inializing 115-119
building 113
moving 119, 120
player, coding 107-109
reseng 119, 120
Terrain class, inializing 113-115
Terrain class, planning 111, 112
TexturePacker
URL 12
mer, MiniPool game
adding 172
creang 172
running, in Android 173
www.it-ebooks.info
[ 245 ]
X
Xcode
about 2
URL, for downloading 2
Z
zoom-in eect 179
Zwoptex
URL 12
V
vectors
about 231, 232
vector methods 232
Victorian Rush Hour, game
about 103, 104, 130
animaons 131
buildings, texturing 132-135
buildings, texturing with sprites 131
containers, within containers 135
menu, adding to 138
menu, creang 139-141
MenuItem, creang 139-141
parallax eect, creang 136-138
placeholder sprites, creang 105
rectangles, creang 104
running, in Android 144
sengs 104
sprites, adding 130
tutorial, adding 141-144
www.it-ebooks.info
www.it-ebooks.info
Thank you for buying
Cocos2d-x by Example Beginner's Guide
Second Edition
About Packt Publishing
Packt, pronounced 'packed', published its rst book, Mastering phpMyAdmin for Eecve
MySQL Management, in April 2004, and subsequently connued to specialize in publishing
highly focused books on specic technologies and soluons.
Our books and publicaons share the experiences of your fellow IT professionals in adapng
and customizing today's systems, applicaons, and frameworks. Our soluon-based books
give you the knowledge and power to customize the soware and technologies you're
using to get the job done. Packt books are more specic and less general than the IT books
you have seen in the past. Our unique business model allows us to bring you more focused
informaon, giving you more of what you need to know, and less of what you don't.
Packt is a modern yet unique publishing company that focuses on producing quality,
cung-edge books for communies of developers, administrators, and newbies alike.
For more informaon, please visit our website at www.packtpub.com.
About Packt Open Source
In 2010, Packt launched two new brands, Packt Open Source and Packt Enterprise, in order
to connue its focus on specializaon. This book is part of the Packt Open Source brand,
home to books published on soware built around open source licenses, and oering
informaon to anybody from advanced developers to budding web designers. The Open
Source brand also runs Packt's Open Source Royalty Scheme, by which Packt gives a royalty
to each open source project about whose soware a book is sold.
Writing for Packt
We welcome all inquiries from people who are interested in authoring. Book proposals
should be sent to author@packtpub.com. If your book idea is sll at an early stage and
you would like to discuss it rst before wring a formal book proposal, then please contact
us; one of our commissioning editors will get in touch with you.
We're not just looking for published authors; if you have strong technical skills but no wring
experience, our experienced editors can help you develop a wring career, or simply get
some addional reward for your experse.
www.it-ebooks.info
Cocos2d-X by Example Beginner's Guide
ISBN: 978-1-78216-734-1 Paperback: 246 pages
Make fun games for any plaorm using C++,
combined with one of the most popular open
source frameworks in the world
1. Learn to build mul-device games in simple, easy
steps, leng the framework do all the heavy liing.
2. Spice things up in your games with easy to apply
animaons, parcle eects, and physics simulaon.
3. Quickly implement and test your own gameplay
ideas, with an eye for opmizaon and portability.
Learning Cocos2d-x Game Development
ISBN: 978-1-78398-826-6 Paperback: 266 pages
Learn cross-plaorm game development
with Cocos2d-x
1. Create a Windows Store account and upload
your game for distribuon.
2. Develop a game using Cocos2d-x by going
through each stage of game development
process step by step.
Please check www.PacktPub.com for information on our titles
www.it-ebooks.info
Cocos2d-x Game Development Essentials
ISBN: 978-1-78398-786-3 Paperback: 136 pages
Create iOS and Android games from scratch
using Cocos2d-x
1. Create and run Cocos2d-x projects on iOS and
Android plaorms.
2. Find praccal soluons to many real-world game
development problems.
3. Learn the essenals of Cocos2d-x by wring code
and following step-by-step instrucons.
Learning Cocos2d-JS Game Development
ISBN: 978-1-78439-007-5 Paperback: 188 pages
Learn to create robust and engaging cross-plaorm
HTML5 games using Cocos2D-JS
1. Create HTML5 games running both on desktop
and mobile devices, played with both mouse and
touch controls.
2. Add advanced features such as realisc physics,
parcle eects, scrolling, tweaking, sound eects,
background music, and more to your games.
3. Build excing cross-plaorm games and build
a memory game, an endless runner and a
physics-driven game.
Please check www.PacktPub.com for information on our titles
www.it-ebooks.info

Navigation menu