Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials - 2D Game Development

64 Articles
article-image-parallax-scrolling
Packt
13 Mar 2014
16 min read
Save for later

Parallax scrolling

Packt
13 Mar 2014
16 min read
(For more resources related to this topic, see here.) As a developer, we have been asked numerous times about how to implement parallax scrolling in a 2D game. Cerulean Games, my game studio, has even had the elements of parallax scrolling as the "do or die" requirement to close a project deal with a client. In reality, this is incredibly easy to accomplish, and there are a number of ways to do this. In Power Rangers Samurai SMASH! (developed by Cerulean Games for Curious Brain; you can find it in the iOS App Store), we implemented a simple check that would see what the linear velocity of the player is and then move the background objects in the opposite direction. The sprites were layered on the Z plane, and each was given a speed multiplier based on its distance from the camera. So, as the player moved to the right, all parallax scrolling objects would move to the left based on their multiplier value. That technique worked, and it fared us and our client quite well for the production of the game. This is also a common and quick way to manage parallax scrolling, and it's also pretty much how we're going to manage it in this game as well. OK, enough talk! Have at you! Well, have at the code: Create a new script called ParallaxController and make it look like the following code: -using UnityEngine; using System.Collections; public class ParallaxController : MonoBehaviour {     public GameObject[] clouds;     public GameObject[] nearHills;     public GameObject[] farHills;     public float cloudLayerSpeedModifier;     public float nearHillLayerSpeedModifier;     public float farHillLayerSpeedModifier;     public Camera myCamera;     private Vector3 lastCamPos;     void Start()     {         lastCamPos = myCamera.transform.position;     }     void Update()     {         Vector3 currCamPos = myCamera.transform.position;         float xPosDiff = lastCamPos.x - currCamPos.x;         adjustParallaxPositionsForArray(clouds,           cloudLayerSpeedModifier, xPosDiff);         adjustParallaxPositionsForArray(nearHills,           nearHillLayerSpeedModifier, xPosDiff);         adjustParallaxPositionsForArray(farHills,           farHillLayerSpeedModifier, xPosDiff);         lastCamPos = myCamera.transform.position;     }     void adjustParallaxPositionsForArray(GameObject[]       layerArray, float layerSpeedModifier, float xPosDiff)     {         for(int i = 0; i < layerArray.Length; i++)         {             Vector3 objPos =               layerArray[i].transform.position;             objPos.x += xPosDiff * layerSpeedModifier;             layerArray[i].transform.position = objPos;         }     } } Create a new GameObject in your scene and call it _ParallaxLayers. This will act as the container for all the parallax layers. Create three more GameObjects and call them _CloudLayer, _NearHillsLayer, and _FarHillsLayer, respectively. Place these three objects inside the _ParallaxLayers object, and place the Parallax Controller component onto the _ParallaxLayers object. Done? Good. Now we can move some sprites. Import the sprites from SpritesParallaxScenery. Start placing sprites in the three layer containers you created earlier. For the hills you want to be closer, place the sprites in the _NearHillsLayer container; for the hills you want to be further away, place the sprites in _FarHillsLayer; and place the clouds in the _CloudLayer container. The following screenshot shows an example of what the layers will now look like in the scene: Pro tip Is this the absolute, most efficient way of doing parallax? Somewhat; however, it's a bit hardcoded to only really fit the needs of this game. Challenge yourself to extend it to be flexible and work for any scenario! Parallax layer ordering Wait, you say that the objects are layered in the wrong order? Your hills are all mixed up with your platforms and your platforms are all mixed up with your hills? OK, don't panic, we've got this. What you need to do here is change the Order in Layer option for each of the parallax sprites. You can find this property in the Sprite Renderer component. Click on one of the sprites in your scene, such as one of the clouds, and you can see it in the Inspector panel. Here's a screenshot to show you where to look: Rather than changing each sprite individually, we can easily adjust the sprites in bulk by performing the following steps: Select all of your cloud layer sprites, and under their Sprite Renderer components, set their Order in Layer to 0. Set the Order in Layer property of the _NearHillsLayer sprites to 1 and that of the _FarHillsLayer sprites to 0. Select the Prefab named Platform and set its Order in Layer to 2; you should see all of your Platform sprites instantly update in the scene. Set the Order in Layer values of the Prefabs for Enemy and Player Bullet to 2. Set the sprite on the Player object in the scene to 2 as well. Finally, set the Wall objects to 3 and you're good to go. With the layers all set up, let's finish setting up the parallax layers. First, finish placing any additional parallax sprites; I'll wait. Brilliant! Now, go to the _ParallaxLayers object and let's play around with that Parallax Controller component. We're going to want to add all of those sprites to Parallax Controller. To make this easy, look at the top-right corner of the Inspector panel. See the little lock icon? Click on it. Now, regardless of what you do, the Parallax Controller component will not be deselected. Since it can't be deselected, you can now easily drag-and-drop all of the Cloud sprites into the Clouds array in the ParallaxController component, and all of the _FarHillsLayer child objects into the Far Hills array—you see where this is going. Set the My Camera field to use the Main Camera object. Finally, let's set some values in those Layer Speed Modifier fields. The higher the number, the faster the object will move as the camera moves. As an example, we set the Cloud layer to 0.05, the Near layer to 0.2, and the Far layer to 0.1. Feel free though to play with the values and see what you like! Go ahead and play the game. Click on the play button and watch those layers move! But, what's this? The particles that burst when an enemy is defeated render behind the sprites in the background—actually, they render behind all the sprites! To fix this, we need to tell Unity to render the particles on a layer in front of the sprites. By default, the sprites render after the particles. Let's change that. First, we need to create a new sorting layer. These are special types of layers that tell Unity the order to render things in. Go to the Tags & Layers window and look out for the drop-down menu called Sorting Layers. Add a new layer called ParticleLayer on Layer 1, as shown in the following screenshot: With this in place, it means anything with the Sorting Layers menu of ParticleLayer will render after the Default layer. Now, we need a way to assign this Sorting Layer to the particle system used when enemies are defeated. Create a new script called ParticleLayering and make it look like the following code: using UnityEngine; using System.Collections; public class ParticleLayering : MonoBehaviour {     public string sortLayerString = "";     void Start ()     {         particleSystem.renderer.sortingLayerName = sortLayerString;     } } Add this script to the EnemyDeathFX Prefab and set the Sort Layer String field to ParticleLayer. Go ahead and play the game again now to watch those particles fly in front of the other objects. Finally, if you want a solid color background to your scene, you don't need to worry about adding a colored plane or anything. Simply select the Main Camera object, and in the Inspector panel, look for the Background field in the Camera component. Adjust the color there as per your need. For the example game, we made this color a nice sky blue with the following values: R: 128, G: 197, B: 232, and A: 0. The one thing you may notice we're missing is something at the bottom of the scene. Here's a nice little challenge for you. We've given you a Lava sprite. Now, add in a lava layer of parallax sprites in the foreground using all the info you've read in this article. You can do this! Let's score! One of the most important elements to a game is being able to track progress. A quick and simple way to do this is to implement a score system. In our case, we will have a score that increases whenever you defeat an enemy. Now, Unity does have a built-in GUI system. However, it has some drawbacks. With this in mind, we won't be relying on Unity's built-in system. Instead, we are going to create objects and attach them to the camera, which in turn will allow us to have a 3D GUI. Pro tip If you want to use what this author believes is the best UI system for Unity, purchase a license for NGUI from the Unity Asset Store. I'm not the only one to think it's the best; Unity hired the NGUI developer to build the new official UI system for Unity itself. Let's build out some GUI elements: Create a 3D Text object by navigating to the menu item GameObject | Create Other; name it Score. Make it a child of the Main Camera GameObject and align it such that it sits in the top-left corner of the screen. Set its position to X: -6.91, Y: 4.99, Z: 10 to get this effect. Make the text color solid black and adjust the scaling so that it looks the way you want it to. Set the Anchor field to Upper Left and Alignment to Left. Adjust the scene to your taste, but it should look a little something like the following screenshot: Pro tip Unity's default 3D text font looks rather low quality in most situations. Try importing your own font and then set the font size to something much higher than you would usually need; often around 25 to 40. Then, when you place it in the world, it will look crisp and clean. Let's make it so that the Score visual element can actually track the player's score. Create a new script called ScoreWatcher and write the following code in it: using UnityEngine; using System.Collections; public class ScoreWatcher : MonoBehaviour {     public int currScore = 0;     private TextMesh scoreMesh = null;         void Start()     {         scoreMesh = gameObject.GetComponent<TextMesh>(); scoreMesh.text = "0";     }         void OnEnable()     {         EnemyControllerScript.enemyDied += addScore;     }         void OnDisable()     {         EnemyControllerScript.enemyDied -= addScore;     }         void addScore(int scoreToAdd)     {         currScore += scoreToAdd;         scoreMesh.text = currScore.ToString();     } } You may notice that in the preceding script, we are listening to the enemyDied event on the EnemyControllerScript. What we did here was we allowed other objects to easily create scoring events that the Score object can optionally listen to. There is lots of power to this! Let's add that event and delegate to the enemy. Open up EnemyControllerScript, and in the beginning, add the following code:     // States to allow objects to know when an enemy dies     public delegate void enemyEventHandler(int scoreMod);         public static event enemyEventHandler enemyDied; Then, down in the hitByPlayerBullet function, add the following code just above Destroy(gameObject,0.1f);, right around line 95: // Call the EnemyDied event and give it a score of 25. if(enemyDied != null)     enemyDied(25); Add the ScoreWatcher component to the Score object. Now, when you play the game and defeat the enemies, you can watch the score increase by 25 points each time! Yeeee-haw! Sorry 'bout that... shootin' things for points always makes me feel a bit Texan. Enemies – forever! So, you defeated all your enemies and now find yourself without enemies to defeat. This gets boring fast; so, let's find a way to get more enemies to whack. To do this, we are going to create enemy spawn points in the form of nifty rotating vortexes and have them spit out enemies whenever we kill other enemies. It shall be glorious, and we'll never be without friends to give gifts—and by gifts, we mean bullets. First things first. We need to make a cool-looking vortex. This vortex will be a stacked, animated visual FX object that is built for a 2D world. Don't worry, we've got you covered on textures, so please go through the following steps: Import the ones in the assets folder under SpritesVortex. Create a new GameObject called Vortex and add all three of the Vortex sprites in it, with each of their Position values set to X:0, Y:0, and Z:0. Adjust their Order in Layer values so that the Vortex_Back child is set to 10, Vortex_Center is set to 11, and Vortex_Front is set to 12. You should now have an object that looks something like the following screenshot: Go ahead and give it a nice spinning animation by rotating the Z axis from 0 to 356. Once you're happy with it, create a new script called EnemyRespawner and code it up as shown in the following code snippet: using UnityEngine; using System.Collections; public class EnemyRespawner : MonoBehaviour {     public GameObject spawnEnemy = null;     float respawnTime = 0.0f;         void OnEnable()     {         EnemyControllerScript.enemyDied += scheduleRespawn;     }         void OnDisable()     {         EnemyControllerScript.enemyDied -= scheduleRespawn;     }         // Note: Even though we don't need the enemyScore, we still need to accept it because the event passes it     void scheduleRespawn(int enemyScore)     {         // Randomly decide if we will respawn or not         if(Random.Range(0,10) < 5)             return;                 respawnTime = Time.time + 4.0f;     }         void Update()     {         if(respawnTime > 0.0f)         {             if(respawnTime < Time.time)             {                 respawnTime = 0.0f;                 GameObject newEnemy = Instantiate(spawnEnemy) as GameObject;                 newEnemy.transform.position = transform.position;             }         }     } } Now attach the preceding script to your Vortex object, populate the Spawn Enemy field with the Enemy Prefab, and save the Vortex object as a Prefab. Scatter a bunch of Vortex Prefabs around the level and you can get the hydra effect, where killing one enemy will create two more enemies or even more than two! Also, if you haven't already done so, you may want to go to the Physics Manager option and adjust the settings so that enemies won't collide with other enemies. One more thing—those enemies sort of glide out of their portals very awkwardly. Let's boost the gravity so they fall faster. Click on the main Enemy Prefab and change the Gravity Scale value of the RigidBody 2D component to 30. Now, they'll fall properly! Pro tip There are so many things you can do with enemy spawners that go far, far outside the context of this article. Take a shot at adding some features yourself! Here are a few ideas: Make the spawn vortexes play a special visual effect when an enemy is spawned Give vortexes a range so that they only spawn an enemy if another enemy was killed in their range Make vortexes move around the level Make vortexes have multiple purposes so that enemies can walk into one and come out another Have a special gold enemy worth bonus points spawn after every 100 kills Make an enemy that, when defeated, spawns other enemies or even collectable objects that earn the player bonus points! Summary So, what have we learned here today aside from the fact that shooting enemies with bullets earns you points? Well, check this out. You now know how to use parallax scrolling, 2D layers, and generate objects; and how to use a scoring system. Enemies dying, enemies spawning, freakin' vortexes? I know, you're sitting there going, "Dude, OK, I'm ready to get started on my first 2D game... the next side scrolling MMO Halo meets Candy Crush with bits of Mass Effect and a little Super Mario Bros!" Resources for Article: Further resources on this subject: Unity Game Development: Interactions (Part 1) [Article] Unity Game Development: Interactions (Part 2) [Article] Unity Game Development: Welcome to the 3D world [Article]
Read more
  • 0
  • 0
  • 9088

article-image-cocos2d-x-installation
Packt
05 Sep 2013
10 min read
Save for later

Cocos2d-x: Installation

Packt
05 Sep 2013
10 min read
(For more resources related to this topic, see here.) Download and installation All the examples in this article were developed on a Mac using Xcode. Although you can use Cocos2d-x to develop your games for other platforms, using different systems, the examples will focus on iOS and Mac. 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 USD 99 a year. You can find more information on their website: https://developer.apple.com/ So, assuming you have an internet connection, and that Xcode is ready to rock, let's begin! Time for action – downloading and installing Cocos2d-x We start by downloading the framework: Go to http://download.cocos2d-x.org/ and download the latest stable version of Cocos2d-x. For this article I'll be using version Cocos2d-2.0-x-2.0.4, which means the 2.0.4 C++ port of version 2.0 of Cocos2d. Uncompress the files somewhere on your machine. Open Terminal and type cd (that is cd and a space). Drag the uncompressed folder you just downloaded to the Terminal window. You should see the path to the folder added to the command line. Hit returnto go to that folder in Terminal. Now type: sudo ./install-templates-xcode.sh -u Hit return again and you're done. What just happened? You have successfully installed the Cocos2d-x templates in your machine. With these in place, you can select the type of Cocos2d-x application you wish to build inside Xcode, and the templates will take care of copying all the necessary files into your application. Next, open Xcode and select Create a new Xcode Project.You should see something like this: So let's build our first application. Hello-x World-x Let's create that old chestnut in computer programming: the hello world example. Time for action – creating an application Open Xcode and select File | New | Project... and follow these steps: In the dialogue box select cocos2d-x under the iOS menu and choose the cocos2dx template. Hit Next . Give the application a name, but not HelloWorld. I'll show you why in a second. You will be then asked to select a place to save the project and you are done. Once your application is ready, click Run to build it. After that, this is what you should see in the simulator: When you run a cocos2d-x application 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 first Cocos2d-x application using the cocos2dx template, sometimes referred to as the basic template. The other template options include one with Box2D, one with Chipmunk (both related to physics simulation), one with JavaScript, and one with Lua. The last two options allow you to code some or all of your game using those script languages instead of the native C++; and they work just as you would expect a scripting language to work, meaning the commands written in either Javascript or Lua are actually replaced and interpreted as C++ commands by the compiler. Now if you look at the files created by the basic template you will see a HelloWorldScene class file. That's the reason I didn't want you to call your application HelloWorld, because I didn't want you to have the impression that the file name was based on your project name. It isn't. You will always get a HelloWorldScene file unless you change the template itself. Now let's go over the sample application and its files: The folder structure First you have the Resources folder, where you find the images used by the application. The ios folder has the necessary underlying connections between your app and iOS. For other platforms, you will have their necessary linkage files in separate folders targeting their respective platform (like an android folder the Android platform, for instance.) In the libs folder you have all the cocos2dx files, plus CocosDenshion files (for sound support) and a bunch of other extensions. Using a different template for your projects will result in a different folder structure here, based on what needs to be added to your project. So you will see a Box2D folder, for example, if you choose the Box2D template. In the Classes folder you have your application. In here, everything is written in C++ and this is the home for the part of your code that will hopefully not need to change, however many platforms you target with your application. Now let us go over the main classes of the basic application. The iOS linkage classes AppController and RootViewController are responsible for setting up OpenGL in iOS as well as telling the underlying operating system that your application is about to say Hello... To the World. These classes are written with a mix of Objective-C and C++, as all the nice brackets and the .mm extensions show. You will change very little if anything in these classes; and again that will reflect in changes to the way iOS handles your application. So other targets would require the same instructions or none at all depending on the target. In AppController for instance, I could add support for multitouch. And in RootViewController, I could limit the screen orientations supported by my application. The AppDelegate class This class marks the first time your C++ app will talk to the underlying OS. It attempts to map the main events that mobile devices wants to dispatch and listen to. From here on, all your application will be written in C++ (unless you need something else). In AppDelegate you should setup CCDirector (the cocos2d-x all powerful singleton manager object) to run your application just the way you want. You can: Get rid of the application status information Change the frame rate of your application Tell CCDirector where your high definition images are, and where your standard definition images are, as well as which to use You can change the overall scale of your application to suit different screens The AppDelegate class is also the best place to start any preloading process And, most importantly, it is here you tell the CCDirector object what CCScene to begin your application with Here too you will handle what happens to your application 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. The HelloWorldScene class When you run the application you get a screen with the words Hello World and a bunch of numbers in one corner. These are the display stats 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 class is, or a Scene class, you will soon enough). When it initializes, HelloWorldScene puts a button on screen that you can press to exit the application. The button is actually a Menu item, part of a Menu group consisting of one button, two image states for that button, and one callback event, triggered when the said button is pressed. The Menu group automatically handles touch events targeting its members, so you don't get to see any of that code floating about. There is also the necessary Label object to show the Hello World message and the background image. Who begets whom If you never worked with either Cocos2d or Cocos2d-x before, the way the initial scene() method is instantiated may lead to dizziness. To recap, in AppDelegate you have: CCScene *pScene = HelloWorld::scene(); pDirector->runWithScene(pScene); CCDirector needs a CCScene object to run, which you can think of as being your application, basically. CCScene needs something to show, which in this case is a CCLayer class. CCScene is then said to contain a CCLayer class. Here a CCScene object is created through a static method scene inside a CCLayer derived class. So the layer creates the scene, and the scene immediately adds the layer to itself. Huh? Relax. This incestuous-like instantiation will most likely only happen once, and you have 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 instantiations will be much easier after this first one. Further information Follow these steps to access one of the best sources for reference material on Cocos2d-x: its Test project. Time for action – running the test samples You open the test project just like you would do for any other Xcode project: Go inside the folder you downloaded for the framework, and navigate to samples/TestCpp/proj.ios/TestCpp.xcodeproj. Open that project in Xcode. When you run the project, you will see inside the simulator a long list of tests, all nicely organized by topic. Pick any one to review. Better yet, navigate to samples/TestCpp/Classes and if you have a program like TextWrangler or some equivalent, you can open that entire directory inside a Disk Browser window and have all that information ready for referencing right at your desktop. What just happened? With the test samples you can visualize most features in Cocos2d-x and see what they do, as well as some of the ways you can initialize and customize them. I will refer to the code found in the tests quite often. As usual with programming, there is always a different way to accomplish a given task, so sometimes after showing you one way, I'll refer to another one that you can find (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. In this articles examples I use four of them: A tool to help build sprite sheets: I'll use Texture Packer (http://www.codeandweb.com/texturepacker). There are other alternatives, like Zwoptex (http://zwopple.com/zwoptex/). And they usually offer some features for free. A tool to help build particle effects: I'll use Particle Designer (http://www.71squared.com/en/particledesigner). Depending on your operating system you may find free tools online for this. Cocos2d-x comes bundled with some common particle effects that you can customize. But to do it blindly is a process I do not recommend. 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), FontBuilder (which is free). It is not extremely hard to build a Bitmap font by hand, not nearly as hard as building a particle effect from scratch, but doing it once is enough to convince you to get one of these tools fast. A tool to produce sound effects: No contest. cfxr for Mac or the original sfxr for Windows. Both are free (http://www.drpetter.se/project_sfxr.html and http://thirdcog.eu/apps/cfxr respectively). Summary You just learned how to install Cocos2d-x templates and create a basic application. You also learned enough of the structure of a basic Cocos2d-x application to get started to build your first game. Resources for Article: Further resources on this subject: Getting Started With Cocos2d [Article] Cocos2d: Working with Sprites [Article] Cocos2d for iPhone: Surfing Through Scenes [Article]
Read more
  • 0
  • 0
  • 8926

article-image-introducing-gamemaker
Packt
24 Mar 2015
5 min read
Save for later

Introducing GameMaker

Packt
24 Mar 2015
5 min read
In this article by Nathan Auckett, author of the book GameMaker Essentials, you will learn what GameMaker is all about, who made it, what it is used for, and more. You will then also be learning how to install GameMaker on your computer that is ready for use. (For more resources related to this topic, see here.) In this article, we will cover the following topics: Understanding GameMaker Installing GameMaker: Studio What is this article about? Understanding GameMaker Before getting started with GameMaker, it is best to know exactly what it is and what it's designed to do. GameMaker is a 2D game creation software by YoYo Games. It was designed to allow anyone to easily develop games without having to learn complex programming languages such as C++ through the use of its drag and drop functionality. The drag and drop functionality allows the user to create games by visually organizing icons on screen, which represent actions and statements that will occur during the game. GameMaker also has a built-in programming language called GameMaker Language, or GML for short. GML allows users to type out code to be run during their game. All drag and drop actions are actually made up of this GML code. GameMaker is primarily designed for 2D games, and most of its features and functions are designed for 2D game creation. However, GameMaker does have the ability to create 3D games and has a number of functions dedicated to this. GameMaker: Studio There are a number of different versions of GameMaker available, most of which are unsupported because they are outdated; however, support can still be found in the GameMaker Community Forums. GameMaker: Studio is the first version of GameMaker after GameMaker HTML5 to allow users to create games and export them for use on multiple devices and operating systems including PC, Mac, Linux, and Android, on both mobile and desktop versions. GameMaker: Studio is designed to allow one code base (GML) to run on any device with minimal changes to the base code. Users are able to export their games to run on any supported device or system such as HTML5 without changing any code to make things work. GameMaker: Studio was also the first version available for download and use through the Steam marketplace. YoYo Games took advantage of the Steam workshop and allowed Steam-based users to post and share their creations through the service. GameMaker: Studio is sold in a number of different versions, which include several enhanced features and capabilities as the price gets higher. The standard version is free to download and use. However, it lacks some advanced features included in higher versions and only allows for exporting to the Windows desktop. The professional version is the second cheapest from the standard version. It includes all features, but only has the Windows desktop and Windows app exports. Other exports can be purchased at an extra cost ranging from $99.99 to $300. The master version is the most expensive of all the options. It comes with every feature and every export, including all future export modules in version 1.x. If you already own exports in the professional version, you can get the prices of those exports taken off the price of the master version. Installing GameMaker: Studio Installing GameMaker is performed much like any other program. In this case, we will be installing GameMaker: Studio as this is the most up-to-date version at this point. You can find the download at the YoYo Games website, https://www.yoyogames.com/. From the site, you can pick the free version or purchase one of the others. All the installations are basically the same. Once the installer is downloaded, we are ready to install GameMaker: Studio. This is just like installing any other program. Just run the file, and then follow the on-screen instructions to accept the license agreement, choose an install location, and install the software. On the first run, you may see a progress bar appear at the top left of your screen. This is just GameMaker running its first time setup. It will also do this during the update process as YoYo Games releases new features. Once it is done, you should see a welcome screen and will be prompted to enter your registration code. The key should be e-mailed to you when you make an account during the purchase/download process. Enter this key and your copy of GameMaker: Studio should be registered. You may be prompted to restart GameMaker at this time. Close GameMaker and re-open it and you should see the welcome screen and be able to choose from a number of options on it: What is this article about? We now have GameMaker: Studio installed and are ready to get started with it. In this article, we will be covering the essential things to know about GameMaker: Studio. This includes everything from drag and drop actions to programming in GameMaker using GameMaker Language (GML). You will learn about how things in GameMaker are structured, and how to organize resources to keep things as clean as possible. Summary In this article, we looked into what GameMaker actually is and learned that there are different versions available. We also looked at the different types of GameMaker: Studio available for download and purchase. We then learned how to install GameMaker: Studio, which is the final step in getting ready to learn the essential skills and starting to make our very own games. Resources for Article: Further resources on this subject: Getting Started – An Introduction to GML [Article] Animating a Game Character [Article] Why should I make cross-platform games? [Article]
Read more
  • 0
  • 0
  • 8915

article-image-adding-animations
Packt
19 Mar 2014
10 min read
Save for later

Adding Animations

Packt
19 Mar 2014
10 min read
(For more resources related to this topic, see here.) Exploring 3D hierarchies The ability to parent objects among one another is a versatile feature in Unity3D. So far, in this article, we have seen how hierarchies can be used as a means of associating objects with one another and organizing them into hierarchies. One example of this is the character Prefabs with their child hats we developed for the player Prefab and the racers. In this example, by dragging-and-dropping the hat object onto the player's body, we associated the hat with the object body by putting the child into the parent's coordinate system. After doing this, we saw that the rotations, translations, and scales of the body's transform component were propagated to the hat. In practice, this is how we attach objects to one another in 3D games, that is, by parenting them to different parents and thereby changing their coordinate systems or frames of reference. Another example of using hierarchies as a data structure was for collections. Examples of this are the splineData collections we developed for the NPCs. These objects had a single game object at the head and a collection of data as child objects. We could then perform operations on the head, which lets us process all of the child objects in the collection (in our case, installing the way points). A third use of hierarchies was for animation. Since rotations, translations, and scales that are applied to a parent transform are propagated to all of the children, we have a way of developing fine-tuned motion for a hierarchy of objects. It turns out that characters in games and animated objects alike use this hierarchy of transforms technique to implement their motion. Skinned meshes in Unity3D A skinned mesh is a set of polygons whose positions and rotations are computed based on the positions of the various transforms in a hierarchy. Instead of each GameObject in the hierarchy have its own mesh, a single set of polygons is shared across a number of transforms. This results in a single mesh that we call a skin because it envelops the transforms in a skin-like structure. It turns out that this type of mesh is great for in-game characters because it moves like skin. We will now use www.mixamo.com to download some free models and animations for our game. Acquiring and importing models Let's download a character model for the hero of our game. Open your favorite Internet browser and go to www.mixamo.com. Click on Characters and scroll through the list of skinned models. From the models that are listed free, choose the one that you want to use. At the time of writing this article, we chose Justin from the free models available, as shown in the following screenshot: Click on Justin and you will be presented with a preview window of the character on the next page. Click on Download to prepare the model for download. Note, you may have to sign up for an account on the website to continue. Once you have clicked on Download, the small downloads pop-up window at the bottom-right corner of the screen will contain your model. Open the tab and select the model. Make sure to set the download format to FBX for Unity (.fbx) and then click on Download again. FBX (Filmbox)—originally created by a company of the same name and now owned by AutoDesk—is an industry standard format for models and animations. Congratulations! You have downloaded the model we will use for the main player character. While this model will be downloaded and saved to the Downloads folder of your browser, go back to the character select page and choose two more models to use for other NPCs in the game. At the time of writing this article, we chose Alexis and Zombie from the selection of free models, as shown in the following screenshot: Go to Unity, create a folder in your Project tab named Chapter8, and inside this folder, create a folder named models. Right-click on this folder and select Open In Explorer. Once you have the folder opened, drag-and-drop the two character models from your Download folder into the models folder. This will trigger the process of importing a model into Unity, and you should then see your models in your Project view as shown in the following screenshot: Click on each model in the models folder, and note that the Preview tab shows a t-pose of the model as well as some import options. In the Inspector pane, under the Rig tab, ensure that Animation Type is set to Humanoid instead of Generic. The various rig options tell Unity how to animate the skinned mesh on the model. While Generic would work, choosing Humanoid will give us more options when animating under Mechanim. Let Unity create the avatar definition file for you and you can simply click on Apply to change the Rig settings, as shown in the following screenshot: We can now drag-and-drop your characters to the game from the Project tab into the Scene view of the level, as shown in the following screenshot: Congratulations! You have successfully downloaded, imported, and instantiated skinned mesh models. Now, let's learn how to animate them, and we will do this starting with the main player's character. Exploring the Mechanim animation system The Mechanim animation system allows the Unity3D user to integrate animations into complex state machines in an intuitive and visual way! It also has advanced features that allow the user to fine-tune the motion of their characters, right from the use of blend trees to mix, combine, and switch between animations as well as Inverse Kinematics (IK) to adjust the hands and feet of the character after animating. To develop an FSM for our main player, we need to consider the gameplay, moves, and features that the player represents in the game. Choosing appropriate animations In level one, our character needs to be able to walk around the world collecting flags and returning them to the flag monument. Let's go back to www.mixamo.com, click on Animations, and download the free Idle, Gangnam Style, and Zombie Running animations, as shown in the following screenshot: Building a simple character animation FSM Let's build an FSM that the main character will use. To start, let's develop the locomotion system. Import the downloaded animations to a new folder named anims, in Chapter8. If you downloaded the animations attached to a skin, don't worry. We can remove it from the model, when it is imported, and apply it to the animation FSM that you will build. Open the scene file from the first gameplay level TESTBED1. Drag-and-drop the Justin model from the Projects tab into the Scene view and rename it Justin. Click on Justin and add an Animator component. This is the component that drives a character with the Mechanim system. Once you have added this component, you will be able to animate the character with the Mechanim system. Create a new animator controller and call it JustinController. Drag-and-drop JustinController into the controller reference on the Animator component of the Justin instance. The animator controller is the file that will store the specific Mechanim FSM that the Animator component will use to drive the character. Think of it as a container for the FSM. Click on the Justin@t-pose model from the Project tab, and drag-and-drop the avatar definition file from Model to the Avatar reference on the Animator component on the Justin instance. Go to the Window drop-down menu and select Animator. You will see a new tab open up beside the Scene view. With your Justin model selected, you should see an empty Animator panel inside the tab, as shown in the following screenshot: Right now our Justin model has no animations in his FSM. Let's add the idle animation (named Idle_1) from the Adam model we downloaded. You can drag-and-drop it from the Project view to any location inside this panel. That's all there is to it! Now, we have a single anim FSM attached to our character. When you play the game now, it should show Justin playing the Idle animation. You may notice that the loop doesn't repeat or cycle repeatedly. To fix this, you need to duplicate the animation and then select the Loop Pose checkbox. Highlight the animation child object idle_1 and press the Ctrl + D shortcut to duplicate it. The duplicate will appear outside of the hierarchy. You can then rename it to a name of your choice. Let's choose Idle as shown in the following screenshot: Now, click on Idle, and in the Inspector window, make sure that Loop Pose is selected. Congratulations! Using this Idle animation now results in a character who idles in a loop. Let's take a look at adding the walk animation. Click on the Zombie Running animation, which is a child asset of the Zombie model, and duplicate it such that a new copy appears in the Project window. Rename this copy Run. Click on this animation and make sure to check the Loop Pose checkbox so that the animation runs in cycles. Drag-and-drop the Run animation into the Animator tab. You should now have two animations in your FSM, with the default animation still as Idle; if you run the game, Justin should still just be idle. To make him switch animations, we need to do the following: Add some transitions to the Run animation from the Idle animation and vice versa. Trigger the transitions from a script. You will want to switch from the Idle to the Run animation when the player's speed (as determined from the script) is greater than a small number (let's say 0.1 f). Since the variable for speed only lives in the script, we will need a way for the script to communicate with the animation, and we will do this with parameters. In your Animator tab, note that the FSM we are developing lives in the Base Layer screen. While it is possible to add multiple animation layers by clicking on the + sign under Base Layer, this would allow the programmer to design multiple concurrent animation FSMs that could be used to develop varying degrees/levels of complex animation. Add a new parameter by clicking on the + sign beside the Parameters panel. Select float from the list of datatypes. You should now see a new parameter. Name this speed as shown in the following screenshot: Right-click on the Idle Animation and select Make Transition. You will now see that a white arrow is visible, which extends from Idle, that tracks the position of your mouse pointer. If you now click on the Run animation, the transition from Idle to Run will lock into place. Left-click on the little white triangle of the transition and observe the inspector for the transition itself. Scroll down in the Inspector window to the very bottom and set the condition for the transition to speed greater than 0.1, as shown in the following screenshot: Make the transition from the Run animation cycle back to the Idle cycle by following the same procedure. Right-click on the Run animation to start the transition. Then, left-click on Idle. After this, left-click on the transition once it is locked into place. Then, when the transition is activated in Conditions, set its speed to less than 0.09. Congratulations! Our character will now transition from Idle to Run when the speed crosses the 0.1 threshold. The transition is a nice blend from the first animation to the second over a brief period of time, and this is indicated in the transition graph.
Read more
  • 0
  • 0
  • 8591

article-image-writing-simple-behaviors
Packt
27 Apr 2015
18 min read
Save for later

Writing Simple Behaviors

Packt
27 Apr 2015
18 min read
In this article by Richard Sneyd, the author of Stencyl Essentials, we will learn about Stencyl's signature visual programming interface to create logic and interaction in our game. We create this logic using a WYSIWYG (What You See Is What You Get) block snapping interface. By the end of this article, you will have the Player Character whizzing down the screen, in pursuit of a zigzagging air balloon! Some of the things we will learn to do in this article are as follows: Create Actor Behaviors, and attach them to Actor Types. Add Events to our Behaviors. Use If blocks to create branching, conditional logic to handle various states within our game. Accept and react to input from the player. Apply physical forces to Actors in real-time. One of the great things about this visual approach to programming is that it largely removes the unpleasantness of dealing with syntax (the rules of the programming language), and the inevitable errors that come with it, when we're creating logic for our game. That frees us to focus on the things that matter most in our games: smooth, well wrought game mechanics and enjoyable, well crafted game-play. (For more resources related to this topic, see here.) The Player Handler The first behavior we are going to create is the Player Handler. This behavior will be attached to the Player Character (PC), which exists in the form of the Cowboy Actor Type. This behavior will be used to handle much of the game logic, and will process the lion's share of player input. Creating a new Actor Behavior It's time to create our very first behavior! Go to the Dashboard, under the LOGIC heading, select Actor Behaviors: Click on This game contains no Logic. Click here to create one. to add your first behavior. You should see the Create New... window appear: Enter the Name Player Handler, as shown in the previous screenshot, then click Create. You will be taken to the Behavior Designer: Let's take a moment to examine the various areas within the Behavior Designer. From left to right, as demonstrated in the previous screenshot, we have: The Events Pane: Here we can add, remove, and move between events in our Behavior. The Canvas: To the center of the screen, the Canvas is where we drag blocks around to click our game logic together. The blocks Palette: This is where we can find any and all of the various logic blocks that Stencyl has on offer. Simply browse to your category of choice, then click and drag the block onto the Canvas to configure it. Follow these steps: Click the Add Event button, which can be found at the very top of the Events Pane. In the menu that ensues, browse to Basics and click When Updating: You will notice that we now have an Event in our Events Pane, called Updated, along with a block called always on our Canvas. In Stencyl events lingo, always is synonymous with When Updating: Since this is the only event in our Behavior at this time, it will be selected by default. The always block (yellow with a red flag) is where we put the game logic that needs to be checked on a constant basis, for every update of the game loop (this will be commensurate with the framerate at runtime, around 60fps, depending on the game performance and system specs). Before we proceed with the creation of our conditional logic, we must first create a few attributes. If you have a programming background, it is easiest to understand attributes as being synonymous to local variables. Just like variables, they have a set data type, and you can retrieve or change the value of an attribute in real-time. Creating Attributes Switch to the Attributes context in the blocks palette: There are currently no attributes associated with this behavior. Let's add some, as we'll need them to store important information of various types which we'll be using later on to craft the game mechanics. Click on the Create an Attribute button: In the Create an Attribute… window that appears, enter the Name Target Actor, set Type to Actor, check Hidden?, and press OK: Congratulations! If you look at the lower half of the blocks palette, you will see that you have added your first attribute, Target Actor, of type Actors, and it is now available for use in our code. Next, let's add five Boolean attributes. A Boolean is a special kind of attribute that can be set to either true, or false. Those are the only two values it can accept. First, let's create the Can Be Hurt Boolean: Click Create an Attribute…. Enter the Name Can Be Hurt. Change the Type to Boolean. Check Hidden?. Press OK to commit and add the attribute to the behavior. Repeat steps 1 through 5 for the remaining four Boolean attributes to be added, each time substituting the appropriate name:     Can Switch Anim     Draw Lasso     Lasso Back     Mouse Over If you have done this correctly, you should now see six attributes in your attributes list - one under Actor, and five under Boolean - as shown in the following screenshot: Now let's follow the same process to further create seven attributes; only this time, we'll set the Type for all of them to Number. The Name for each one will be: Health (Set to Hidden?). Impact Force (Set to Hidden?). Lasso Distance (Set to Hidden?). Max Health (Don't set to Hidden?). Turn Force (Don't set to Hidden?). X Point (Set to Hidden?). Y Point (Set to Hidden?). If all goes well, you should see your list of attributes update accordingly: We will add just one additional attribute. Click the Create an Attribute… button again: Name it Mouse State. Change its Type to Text. Do not hide this attribute. Click OK to commit and add the attribute to your behavior. Excellent work, at this point, you have created all of the attributes you will need for the Player Handler behavior! Custom events We need to create a few custom events in order to complete the code for this game prototype. For programmers, custom events are like functions that don't accept parameters. You simply trigger them at will to execute a reusable bunch of code. To accept parameters, you must create a custom block: Click Add Event. Browse to Advanced.. Select Custom Event: You will see that a second event, simply called Custom Event, has been added to our list of events: Now, double-click on the Custom Event in the events stack to change its label to Obj Click Check (For readability purposes, this does not affect the event's name in code, and is completely ignored by Stencyl): Now, let's set the name as it will be used in code. Click between When and happens, and insert the name ObjectClickCheck: From now on, whenever we want to call this custom event in our code, we will refer to it as ObjectClickCheck. Go back to the When Updating event by selecting it from the events stack on the left. We are going to add a special block to this event, which calls the custom event we created just a moment ago: In the blocks palette, go to Behaviour | Triggers | For Actor, then click and drag the highlighted block onto the canvas: Drop the selected block inside of the Always block, and fill in the fields as shown (please note that I have deliberately excluded the space between Player and Handler in the behavior name, so as to demonstrate the debugging workflow. This will be corrected in a later part of the article): Now, ObjectClickCheck will be executed for every iteration of the game loop! It is usually a good practice to split up your code like this, rather than having it all in one really long event. That would be confusing, and terribly hard to sift through when behaviors become more complex! Here is a chance to assess what you have learnt from this article thus far. We will create a second custom event; see if you can achieve this goal using only the skeleton guide mentioned next. If you struggle, simply refer back to the detailed steps we followed for the ObjectClickCheck event: Click Add Event | Advanced | Custom Event. A new event will appear at the bottom of the events pane. Double Click on the event in the events pane to change its label to Handle Dir Clicks for readability purposes. Between When and happens, enter the name HandleDirectionClicks. This is the handle we will use to refer to this event in code. Go back to the Updated event, right click on the Trigger event in behavior for self block that is already in the always block, and select copy from the menu. Right-click anywhere on the canvas and select paste from the menu to create an exact duplicate of the block. Change the event being triggered from ObjectClickCheck to HandleDirectionClicks. Keep the value PlayerHandler for the behavior field. Drag and drop the new block so that it sits immediately under the original. Holding Alt on the keyboard, and clicking and dragging on a block, creates a duplicate of that block. Were you successful? If so, you should see these changes and additions in your behavior (note that the order of the events in the events pane does not affect the game logic, or the order in which code is executed). Learning to create and utilize custom events in Stencyl is a huge step towards mastering the tool, so congratulations on having come this far! Testing and debugging As with all fields of programming and software development, it is important to periodically and iteratively test your code. It's much easier to catch and repair mistakes this way. On that note, let's test the code we've written so far, using print blocks. Browse to and select Flow | Debug | print from the blocks palette: Now, drag a copy of this block into both of your custom events, snapping it neatly into the when happens blocks as you do so. For the ObjectClickCheck event, type Object Click Detected into the print block For the HandleDirectionClicks event, type Directional Click Detected into the print block. We are almost ready to test our code. Since this is an Actor Behavior, however, and we have not yet attached it to our Cowboy actor, nothing would happen yet if we ran the code. We must also add an instance of the Cowboy actor to our scene: Click the Attach to Actor Type button to the top right of the blocks palette: Choose the Cowboy Actor from the ensuing list, and click OK to commit. Go back to the Dashboard, and open up the Level 1 scene. In the Palette to the right, switch from Tiles to Actors, and select the Cowboy actor: Ensure Layer 0 is selected (as actors cannot be placed on background layers). Click on the canvas to place an instance of the actor in the scene, then click on the Inspector, and change the x and y Scale of the actor to 0.8: Well done! You've just added your first behavior to an Actor Type, and added your first Actor Instance to a scene! We are now ready to test our code. First, Click the Log Viewer button on the toolbar: This will launch the Log Viewer. The Log Viewer will open up, at which point we need only set Platform to Flash (Player), and click the Test Game Button to compile and execute our code: After a few moments, if you have followed all of the steps correctly, you will see that the game windows opens on the screen and a number of events appear on the Log Viewer. However, none of these events have anything to do with the print blocks we added to our custom events. Hence, something has gone wrong, and must be debugged. What could it be? Well, since the blocks simply are not executing, it's likely a typo of some kind. Let's look at the Player Handler again, and you'll see that within the Updated event, we've referred to the behavior name as PlayerHandler in both trigger event blocks, with no space inserted between the words Player and Handler: Update both of these fields to Player Handler, and be sure to include the space this time, so that it looks like the following (To avoid a recurrence of this error, you may wish to use the dropdown menu by clicking the downwards pointing grey arrow, then selecting Behavior Names to choose your behavior from a comprehensive list): Great work! You have successfully completed your first bit of debugging in Stencyl. Click the Test Game button again. After the game window has opened, if you scroll down to the bottom of the Log Viewer, you should see the following events piling up: These INFO events are being triggered by the print blocks we inserted into our custom events, and prove that our code is now working. Excellent job! Let's move on to a new Actor; prepare to meet Dastardly Dan! Adding the Balloon Let's add the balloon actor to our game, and insert it into Level 1: Go to the Dashboard, and select Actor Types from the RESOURCES menu. Press Click here to create a new Actor Type. Name it Balloon, and click Create. Click on This Actor Type contains no animations. Click here to add an animation. Change the text in the Name field to Default. Un-check looping?. Press the Click here to add a frame. button. The Import Frame from Image Strip window appears. Change the Scale to 4x. Click Choose Image... then browse to Game AssetsGraphicsActor Animations and select Balloon.png. Keep Columns and Rows set to 1, and click Add to commit this frame to the animation. All animations are created with a box collision shape by default. In actuality, the Balloon actor requires no collisions at all, so let's remove it. Go to the Collision context, select the Default box, and press Delete on the keyboard: The Balloon Actor Type is now free of collision shapes, and hence will not interact physically with other elements of our game levels. Next, switch to the Physics context: Set the following attributes: Set What Kind of Actor Type? to Normal. Set Can Rotate? To No. This will disable all rotational physical forces and interactions. We can still rotate the actor by setting its rotation directly in the code, however. Set Affected by Gravity? to No. We will be handling the downward trajectory of this actor ourselves, without using the gravity implemented by the physics engine. Just before we add this new actor to Level 1, let's add a behavior or two. Switch to the Behaviors context: Then, follow these steps: This Actor Type currently has no attached behaviors. Click Add Behavior, at the bottom left hand corner of the screen: Under FROM YOUR LIBRARY, go to the Motion category, and select Always Simulate. The Always Simulate behavior will make this actor operational, even if it is not on the screen, which is a desirable result in this case. It also prevents Stencyl from deleting the actor when it leaves the scene, which it would automatically do in an effort to conserve memory, if we did not explicitly dictate otherwise. Click Choose to add it to the behaviors list for this Actor Type. You should see it appear in the list: Click Add Behavior again. This time, under FROM YOUR LIBRARY, go the Motion category once more, and this time select Wave Motion (you'll have to scroll down the list to see it). Click Choose to add it to the behavior stack. You should see it sitting under the Always Simulate behavior: Configuring prefab behaviors Prefab behaviors (also called shipped behaviors) enable us to implement some common functionality, without reinventing the wheel, so to speak. The great thing about these prefab behaviors, which can be found in the behavior library, is that they can be used as templates, and modified at will. Let's learn how to add and modify a couple of these prefab behaviors now. Some prefab behaviors have exposed attributes which can be configured to suit the needs of the project. The Wave Motion behavior is one such example. Select it from the stack, and configure the attributes as follows: Set Direction to Horizontal from the dropdown menu. Set Start Speed to 5. Set Amplitude to 64. Set Wavelength to 128. Fantastic! Now let's add an Instance of the Balloon actor to Level 1: Click the Add to Scene button at the top right corner of your view. Select the Level 1 scene. Select the Balloon. Click on the canvas, below the Cowboy actor, to place an instance of the Balloon in the scene: Modifying prefab behaviors Before we test the game one last time, we must quickly add a prefab behavior to the Cowboy Actor Type, modifying it slightly to suit the needs of this game (for instance, we will need to create an offset value for the y axis, so the PC is not always at the centre of the screen): Go to the Dashboard, and double click on the Cowboy from the Actor Types list. Switch to the Behavior Context. Click Add Behavior, as you did previously when adding prefab behaviors to the Balloon Actor Type. This time, under FROM YOUR LIBRARY, go to the Game category, and select Camera Follow. As the name suggests, this is a simple behavior that makes the camera follow the actor it is attached to. Click Choose to commit this behavior to the stack, and you should see this: Click the Edit Behavior button, and it will open up in the Behavior Designer: In the Behavior Designer, towards the bottom right corner of the screen, click on the Attributes tab: Once clicked, you will see a list of all the attributes in this behavior appear in the previous window. Click the Add Attribute button: Perform the following steps: Set the Name to Y Offset. Change the Type to Number. Leave the attribute unhidden. Click OK to commit new attribute to the attribute stack: We must modify the set IntendedCameraY block in both the Created and the Updated events: Holding Shift, click and drag the set IntendedCameraY block out onto the canvas by itself: Drag the y-center of Self block out like the following: Click the little downward pointing grey arrow at the right of the empty field in the set intendedCameraY block , and browse to Math | Arithmetic | Addition block: Drag the y-center of Self block into the left hand field of the Add block: Next, click the small downward pointing grey arrow to the right of the right hand field of the Addition block to bring up the same menu as before. This time, browse to Attributes, and select Y Offset: Now, right click on the whole block, and select Copy (this will copy it to the clipboard), then simply drag it back into its original position, just underneath set intendedCameraX: Switch to the Updated Event from the events pane on the left, hold Shift, then click and drag set intendedCameraY out of the Always block and drop it in the trash can, as you won't need it anymore. Right-click and select Paste to place a copy of the new block configuration you copied to the clipboard earlier: Click and drag the pasted block so that it appears just underneath the set intendedCameraX block, and save your changes: Testing the changes Go back to the Cowboy Actor Type, and open the Behavior context; click File | Reload Document (Ctrl-R or Cmd-R) to update all the changes. You should see a new configurable attribute for the Camera Follow Behavior, called Y Offset. Set its value to 70: Excellent! Now go back to the Dashboard and perform the following: Open up Level 1 again. Under Physics, set Gravity (Vertical) to 8.0. Click Test Game, and after a few moments, a new game window should appear. At this stage, what you should see is the Cowboy shooting down the hill with the camera following him, and the Balloon floating around above him. Summary In this article, we learned the basics of creating behaviors, adding and setting Attributes of various types, adding and modifying prefab behaviors, and even some rudimentary testing and debugging. Give yourself a pat on the back; you've learned a lot so far! Resources for Article: Further resources on this subject: Form Handling [article] Managing and Displaying Information [article] Background Animation [article]
Read more
  • 0
  • 0
  • 8067

article-image-article-playing-particles
Packt
06 Jun 2013
19 min read
Save for later

Playing with Particles

Packt
06 Jun 2013
19 min read
(For more resources related to this topic, see here.) Introducing particle effects Particle effects are the decorative flourishes used in games to represent dynamic and complex phenomena, such as fire, smoke, and rain. To create a particle effect, it requires three elements: a System, Emitters, and the Particles themselves. Understanding particle systems Particle systems are the universe in which the particles and emitters live. Much like the universe, we cannot define the size but we can define a point of origin which all emitters and particles will be placed relative to. We can also have multiple particle systems in existence at any given time, which can be set to draw the particles at different depths. While we can have as many particle systems as we want, it is best to have as few as possible in order to prevent possible memory leaks. The reason for this is that once a particle system is created, it will remain in existence forever unless it is manually destroyed. Destroying the instance that spawned it or changing rooms will not remove the system, so make sure it is removed when it is no longer needed. By destroying a particle system, it will remove all the emitters and particles in that system along with it. Utilizing particle emitters Particle emitters are defined areas within a system from which particles will spawn. There are two types of emitters to choose from: Burst emitters that spawn particles a single time, and Stream emitters that spew particles continuously over time. We can define the size and shape of the region in space for each emitter, as well as how the particles should be distributed within the region. Image When defining the region in space, there are four Shape options: DIAMOND, ELLIPSE, LINE, and RECTANGLE. An example of each can be seen in the preceding diagram, all using exactly the same dimensions, amount of particles, and distribution. While there is no functional difference between using any one of these shapes, the effect itself can benefit from a properly chosen shape. For example, only a LINE can make an effect appear to be angled 30 degrees. Image The distribution of the particles can also affect how the particles are expelled from the emitter. As can be seen in the preceding diagram, there are three different distributions. LINEAR will spawn particles with an equal random distribution throughout the emitter region. GAUSSIAN will spawn particles more towards the center of the region. INVGAUSSIAN is the inverse of GAUSSIAN, wherein the particles will spawn closer to the edges of the emitter. Applying particles Particles are the graphic resources that are spawned from the emitters. There are two types of particles that can be created: Shapes and Sprites. Shapes are the collection of 64 x 64 pixel sprites that comes built-in with GameMaker: Studio for use as particles. The shapes, as seen in the next diagram, are suitable for the majority of the most common effects, such as fireworks and flames. When wanting to create something more specialized for a game, we can use any Sprite in the Resource tree. Image There are a lot of things we can do with particles by adjusting the many attributes available. We can define ranges for how long it lives, the color it should be, and how it moves. We can even spawn more particles at the point of death for each particle. There are, however, some things that we cannot do. In order to keep the graphics processing costs low, there is no ability to manipulate individual particles within an effect. Also, particles cannot interact with objects in any way, so there is no way to know if a particle has collided with an instance in the world. If we need this kind of control, we need to build objects instead. Designing the look of a particle event is generally a trial and error process that can take a very long time. To speed things up, try using one of the many particle effect generators available on the Internet, such as Particle Designer 2.5 by Alert Games found here: http://alertgames.net/index.php?page=s/pd2. HTML5 limitations Using particle effects can really improve the visual quality of a game, but when developing a game intended to be played in a browser we need to be careful. Before implementing a particle effect, it is important to understand potential problems we may encounter. The biggest issue surrounding particles is that in order for them to be rendered smoothly without any lag, they need to be rendered with the graphics processor instead of the main CPU. Most browsers allow this to happen through a JavaScript API called WebGL. It is not, however, an HTML5 standard and Microsoft has stated that they have no plans for Internet Explorer to support it for the foreseeable future. This means a potentially significant portion of the game's potential audience could suffer poor gameplay if particles are used. Additionally, even with WebGL enabled, the functionality for particles to have additive blending and advanced color blending cannot be used, as none of the browsers currently support this feature. Now that we know this we are ready to make some effects! Adding particle effects to the game We are going to build a few different particle effects to demonstrate the various ways effects can be implemented in a game, and to look into some of the issues that might arise. To keep things straightforward, all of the effects we create will be a part of a single, global particle system. We will use both types of emitters, and utilize both shape and sprite-based particles. We will start with a Dust Cloud that will be seen anytime a Pillar is broken or destroyed. We will then add a system to create a unique shrapnel effect for each Pillar type. Finally, we will create some fire and smoke effects for the TNT explosion to demonstrate moving emitters. Creating a Dust Cloud The first effect we are going to create is a simple Dust Cloud. It will burst outwards upon the destruction of each Pillar and dissolve away over time. As this effect will be used in every level of the game, we will make all of its elements global, so they only need to be declared once. Open the Tower Toppling project we were previously working on if it is not already open. We need to make sure that WebGL is enabled when we build the game. Navigate to Resources | Change Global Game Settings and click on the HTML5 tab. On the left-hand side, click on the tab for Graphics. As seen in the following screenshot, there are three options under WebGL in Options. If WebGL is Disabled, the game will not be able to use the GPU and all browsers will suffer from any potential lag. If WebGL is Required, any browser that does not have this capability will be prevented from running the game. The final option is Auto-Detect which will use WebGL if the browser supports it, but will allow all browsers to play the game no matter what. Select Auto-Detect and then click on OK. Image Now that we have WebGL activated we can build our effects. We will start by defining our particle system as a global variable by creating a new script called scr_Global_Particles. code The first effect we are going to make is the Dust Cloud which will be attached to the Pillars. For this we only need a single emitter which we will move to the appropriate position when it is needed. Create a global variable for the emitter and add it to the particle system with the following code at the end of the script: code For this particle, we are going to use one of the built-in shapes, pt_shape_explosion, which looks like a little thick cloud of dust. Add the following code to the end of the script: code Once again we have made this a global variable, so that we have to create this Dust Cloud particle only once. We have declared only the shape attribute of this particle at this time. We will add more to this later once we can see what the effect looks like in the game. We need to initialize the particle system with the other global variables. Reopen scr_Global_GameStart and call the particles script. code With everything initialized, we can now create a new script, scr_Particles_DustCloud, which we can use to set the region of the emitter and have it activate a burst of particles. code We start by defining a small area for the emitter based on the position of instance that calls this script. The region itself will be circular with a Gaussian distribution so that the particles shoot out from the center. We then activate a single burst of 10 dust particles from the emitter. All we need to do now is execute this script from the destruction of a Pillar. Reopen scr_Pillar_Destroyand insert the following line of code on the line before the instance is destroyed: code We need to add this effect to the breaking of the Pillars as well. Reopen scr_ Pillar_BreakApart and insert the same code in the same spot. Save the game and then play it. When the glass Pillars are destroyed, we should see thick white clouds appearing as shown in the following screenshot: Image The particles are boring and static at this point, because we have not told the particles to do anything other than to look like the shape of a cloud. Let's fix this by adding some attributes to the particle. Reopen scr_Global_Particles and add the following code at the end of the script: code The first attribute we add is how long we want the particle to live for, which is a range between 15 and 30 steps, or at the speed of our rooms, a half to a whole second. Next, we want the particles to explode outwards, so we set the angle and add some velocity. Both functions that we are using have similar parameters. The first value is the particle type for which this is to be applied. The next two parameters are the minimum and maximum values from which a number will be randomly chosen. The fourth parameter sets an incremental value every step. Finally, the last parameter is a wiggle value that will randomly be applied throughout the particle's lifetime. For the Dust Cloud, we are setting the direction to be in any angle and the speed is fairly slow, ranging only a few pixels per step. We also want to change the size of the particles and their transparency, so that the dust appears to dissipate. Save the game and run it again. This time the effect appears much more natural, with the clouds exploding outwards, growing slightly larger, and fading out. It should look something like the next screenshot. The Dust Cloud is now complete. Image Adding in Shrapnel The Dust Cloud effect helps the Pillar destruction appear more believable, but it lacks the bigger chunks of material one would expect to see. We want some Shrapnel of various shapes and sizes to explode outwards for each of the different types of Pillars. We will start with the Glass particles. Create a new Sprite, spr_Particle_Glass, and with Remove Background checked, load Chapter 8/Sprites/Particle_Glass.gif.mhanaje This sprite is not meant to be animated, though it does have several frames within it. Each frame represents a different shape of particle that will be randomly chosen when the particle is spawned. We will want the particles to rotate as they move outwards, so we need to center the origin. Click on OK. Reopen scr_Global_Particles and initialize the Glass particle at the end of the script. code Once we have created the global variable and the particle, we set the particle type to be a Sprite. When assigning Sprites there are a few extra parameters beyond which resources should be used. The third and fourth parameters are for whether it should be animated, and if so, should the animation stretch for the duration of the particle's life. In our case we are not using animation, so it has been set to false. The last parameter is for whether we want it to choose a random subimage of the Sprite, which is what we do want it to do. We also need to add some attributes to this particle for life and movement. Add the following code at the end of the script: code When compared with the Dust Cloud, this particle will have a shorter lifespan but will move at a much higher velocity. This will make this effect more intense while keeping the general area small. We have also added some rotational movement through part_type_orientation. The particles can be set to any angle and will rotate 20 degrees per frame with a variance of up to four degrees. This will give us a nice variety in the spin of each particle. There is one additional parameter for orientation, which is whether the angle should be relative to its movement. We have set it to false as we just want the particles to spin freely. To test this effect out, open up scr_Particles_DustCloud and insert a burst emitter before the Dust Cloud is emitted, so that the Glass particles appear behind the other effect. code Save the game and then play it. When the Pillars break apart, there should be shards of Glass exploding out along with the Dust Cloud. The effect should look something like the following screenshot: Image Next we need to create Shrapnel for the Wood and Steel particles. Create new Sprites for spr_Particle_Wood and spr_Particle_Steel with the supplied images in Chapter 8/Sprites/ in the same manner as we did for Glass. As these particles are global, we cannot just swap the Sprite out dynamically. We need to create new particles for each type. In scr_Global_Particles, add particles for both Wood and Steel with the same attributes as Glass. Currently the effect is set to Always create Glass particles, something we do not want to do. To fix this we are going to add a variable, myParticle, to each of the different Pillars to allow us to spawn the appropriate particle. Open scr_Pillar_Glass_Create and add the following code at the end of the script: code Repeat the last step for Wood and Steel with the appropriate particle assigned. In order to have the proper particle spawn, all we need to do is reopen scr_Particles_DustCloud and change the variable particle_Glass to myParticle as in the following code: code Save the game and play the game until you can destroy all the three types of Pillars to see the effect. It should look something similar to the following screenshot, where each Pillar spawns its own Shrapnel: Image Making the TNT explosion When the TNT explodes, it shoots out some TNT Fragments which are currently bland looking Sprites. We want the Fragments to be on fire as they streak across the scene. We also want a cloud of smoke to rise up from the explosion to indicate that the explosion we see is actually on fire. This is going to cause some complications. In order to make something appear to be on fire, it will need to change color, say from white to yellow to orange. As we have already mentioned, due to the fact that WebGL is not supported by all browsers, we cannot utilize any of the functions that allow us to blend colors together. This means that we need to work around this issue. The solution is to use several particles instead of one. We will start by creating some custom colors so that we can achieve the look of fire and smoke that we want. Open scr_Global_Colors and add the following colors: code We already have a nice yellow color, so we add an orange, a slightly yellow tinted white, and a partially orange black color. In order to achieve the fake blending effect we will need to spawn one particle type, and upon its death, have it spawn the next particle type. For this to work properly, we need to construct the creation of the particles in the opposite order that they will be seen. In this case, we need to start by building the smoke particle. In scr_Global_Particles add a new particle for the smoke with the following attributes: code We start by adding the particle and using the built-in smoke shape. We want the smoke to linger for a while, so we set its life to range between a minimum of a second to almost two full seconds. We then set the direction and speed to be more or less upwards so that the smoke rises. Next, we set the size and have it grow over time. With the alpha values, we don't want the smoke to be completely opaque, so we set it to start at half transparent and fade away over time. Next, we are using part_type_color1 which allows us to tint the particle without affecting the performance very much. Finally, we apply some gravity to the particles so that any angled particles float slowly upwards. The smoke is the final step of our effect and it will be spawned from an orange flame that precedes it. code Once again we set up the particle using the built-in smoke shape, this time with a much shorter lifespan. The general direction is still mainly upwards, though there is more spread than the smoke. These particles are slightly smaller, tinted orange and will be partially transparent for its entire life. We have added a little bit of upward gravity, as this particle is in between fire and smoke. Finally, we are using a function that will spawn a single particle of smoke upon the death of each orange particle. The next particle in the chain for this effect is a yellow particle. This time we are going to use the FLARE shape, which will give a better appearance of fire. It will also be a bit smaller, live slightly longer than the orange particle, and move faster, spreading in all directions. We will not add any transparency to this particle so that it appears to burn bright. code We have only one more particle to create this effect for, which is the hottest and brightest white particle. Its construction is the same as the yellow particle, except it is smaller and faster. code We now have all the particles we need for this particle effect; we just need to add an emitter to spawn them. This time we are going to use a stream emitter, so that the fire continuously flows out of each Fragment. Since the Fragments are moving, we will need to have a unique emitter for each Fragment we create. This means it cannot be a global emitter, but rather a local one. Open scr_TNT_Fragment_Create and add the following code at the end of the script: code We create an emitter with a fairly small area for spawning with balanced distribution. At every step, the emitter will create five new Fire particles as long as the emitter exists. The emitter is now created at the same time as the Fragment, but we need the emitter to move along with it. Open scr_TNT_Fragment_Step and add the following code: code As already mentioned we need to destroy the emitter, otherwise it will never stop streaming particles. For this we will need to open obj_TNT_Fragment and add a destroy event with a new Script, scr_TNT_Fragment_Destroy, which removes the emitter attached. code This function will remove the emitter from the system without removing any of the particles that had been spawned. One last thing we need to do is to uncheck the Visible checkbox, as we don't want to see the Fragment sprite, but just the particles. Save the game and detonate the TNT. Instead of just seeing a few Fragments, there are now streaks of fire jetting out of the explosion that turn into dark clouds of smoke that float up. It should look something like the following screenshot: Image Cleaning up the particles At this point, we have built a good variety of effects using various particles and emitters. The effects have added a lot of polish to the game, but there is a flaw with the particles. If the player decides to restart the room or go to the SHOP immediately after the explosion has occurred, the emitters will not be destroyed. This means that they will continue to spawn particles forever, and we will lose all references to those emitters. The game will end up looking like the following screenshot: Image The first thing we need to do is to destroy the emitters when we leave the room. Luckily, we have already written a script that does exactly this. Open obj_TNT_Fragment and add a Room End event and attach scr_TNT_Fragment_Destroy to it. Even if we destroy the emitters before changing rooms, any particles remaining in the game will still appear in the next room, if only briefly. What we need to do is clear all the particles from the system. While this might sound like it could be a lot of work, it is actually quite simple. As Overlord is in every level, but not in any other room, we can use it to clean up the scene. Open obj_Overlord, add a Room End event and attach a new Script, scr_Overlord_RoomEnd, with the following line of code: part_particles_clear(system); This function will remove any particle that exists within the system, but will not remove the particle type from memory. It is important that we do not destroy the particle type, as we would not be able to use a particle again if its type no longer exists. Save the game, explode some TNT, and restart the room immediately. You should no longer see any particles in the scene. Summary In this article, we were provided with the details to add some spit and polish to the game to really make it shine. We delved into the world of particles and created a variety of effects that add impact to the TNT and Pillar destruction. Resources for Article : Further resources on this subject: HTML5: Generic Containers [Article] HTML5 Presentations - creating our initial presentation [Article] Deploying HTML5 Applications with GNOME [Article]
Read more
  • 0
  • 0
  • 7866
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
article-image-article-anatomy-sprite-kit-project
Packt
16 Jan 2014
12 min read
Save for later

Anatomy of a Sprite Kit project

Packt
16 Jan 2014
12 min read
(For more resources related to this topic, see here.) A Sprite Kit project consists of things usual to any iOS project. It has the AppDelegate, Storyboard, and ViewController classes. It has the usual structure of any iOS application. However, there are differences in ViewController.view, which has the SKView class in Storyboard. You will handle everything that is related to Sprite Kit in SKView. This class will render your gameplay elements such as sprites, nodes, backgrounds, and everything else. You can't draw Sprite Kit elements on other views. It's important to understand that Sprite Kit introduces its own coordinate system. In UIkit, the origin (0,0) is located at the top-left corner, whereas Sprite Kit locates the origin at the bottom-left corner. The reason why this is important to understand is because of the fact that all elements will be positioned relative to the new coordinate system. This system originates from OpenGL, which Sprite Kit uses in implementation. Scenes An object where you place all of your other objects is the SKScene object. It represents a single collection of objects such as a level in your game. It is like a canvas where you position your Sprite Kit elements. Only one scene at a time is present on SKView. A view knows how to transition between scenes and you can have nice animated transitions. You may have one scene for menus, one for the gameplay scene, and another for the scene that features after the game ends. If you open your ViewController.m file, you will see how the SKScene object is created in the viewDidLoad method. Each SKView should have a scene, as all other objects are added to it. The scene and its object form the node tree, which is a hierarchy of all nodes present in the scene. Open the ERGMyScene.m file. Here, you can find the method where scene initialization and setup take place: - (id)initWithSize:(CGSize)size The scene holds the view hierarchy of its nodes and draws all of them. Nodes are very much like UIViews; you can add nodes as children to other nodes, and the parent node will apply its effects to all of its children, effects such as rotation or scaling, which makes working with complex nodes so much easier. Each node has a position property that is represented by CGPoint in scene coordinates, which is used to set coordinates of the node. Changing this position property also changes the node's position on the screen. After you have created a node and set its position, you need to add it to your scene node hierarchy. You can add it either to the scene or to any existing node by calling the addChild: method. You can see this in your test project with the following line: [self addChild:myLabel]; After the node has been added to a visible node or scene, it will be drawn by the scene in the next iteration of the run loop. Nodes The methods that create SKLabelNode are self-explanatory and it is used to represent text in a scene. The main building block of every scene is SKNode. Most things you can see on the screen of any given Sprite Kit game is probably a result of SKNode. Node types There are different node types that serve different purposes: SKNode: This is a parent class for different types of nodes SKSpriteNode: This is a node that draws textured sprites SKLabelNode: This is a node that displays text SKShapeNode: This is a node that renders a shape based on a Core Graphics path SKEmitterNode: This is a node that creates and renders particles SKEffectNode: This is a node that applies a Core Image filter to its children Each of them has their own initializer methods—you create one, add it to your scene, and it does the job it was assigned to do. Some node properties and methods that you might find useful are: position: This is a CGPoint representing the position of a node in its parent coordinate system. zPosition: This is a CGFloat that represents the position of a node on an imaginary Z axis. Nodes with higher zPosition will be over the nodes that have lower zPosition. If nodes have the same zPosition, the ones that were created later will appear on top of those that were created before. xScale and yScale: This is a CGFloat that allows you to change the size of any node. The default is 1.0 and setting it to any other value will change the sprite size. It is not recommended, but if you have an image of a certain resolution, scaling it will upscale the image and it will look distorted. Making nodes smaller can lead to other visual artifacts. But if you need quick and easy ways to change the size of your nodes, this property is there. name: This is the name of the node that is used to locate it in the node hierarchy. This allows you to be flexible in your scenes as you no longer need to store pointers to your nodes, and also saves you a lot of headache. childNodeWithName:(NSString *)name: This finds a child node with the specified name. If there are many nodes with the same name, the first one is returned. enumerateChildNodesWithName:usingBlock:: This allows you torun custom code on your nodes. This method is heavily used throughout any Sprite Kit game and can be used for movement, state changing, and other tasks. Actions Actions are one of the ways to add life to your game and make things interactive. Actions allow you to perform different things such as moving, rotating, or scaling nodes, playing sounds, and even running your custom code. When the scene processes its nodes, actions that are linked to these nodes are executed. To create a node, you run a class method on an action that you need, set its properties, and call the runAction: method on your node with action as a parameter. You may find some actions in the touchesBegan: method in ERGMyScene.m. In this method, you can see that a new node (of the type SKSpriteNode) is created, and then a new action is created and attached to it. This action is embedded into another action that makes it repeat forever, and then a sprite runs the action and you see a rotating sprite on the screen. To complete the preceding process, it took only five lines, and it is very intuitive. This is one of the Sprite Kit strengths—simplicity and self-documenting code. As you might have noticed, Apple names methods in a simpler way so that you can understand what it does just by reading the method. Try to adhere to the same practice and name your variables and methods so that their function can be understood immediately. Avoid naming objects a or b, use characterSprite or enemyEmitter instead. There are different action types; here we will list some that you may need in your first project: Move actions (moveTo:duration:, moveBy, followPath) are actions that move the node by a specified distance in points Rotate actions are actions that rotate your nodes by a certain angle (rotateByAngle:duration:) Actions that change node scale over time (scaleBy:duration) Actions that combine other actions (sequence: to play actions one after another, and repeatAction: to play an action a certain amount of times or forever) There are many other actions and you might look up the SKAction class reference if you want to learn more about actions. Game loop Unlike UIKit, which is based on events and waits for user input before performing any drawing or interactions, Sprite Kit evaluates all nodes, their interactions, and physics as fast as it can (capped at 60 times per second) and produces results on screen. In the following figure, you can see the way a game loop operates: First, the scene calls the update:(CFTimeInterval)currentTime method and sends it the time at which this method was invoked. The usual practice is to save the time of the last update and calculate the time that it took from the last update (delta) to the current update to move sprites by a given number of points, by multiplying the velocity of a sprite by delta, so you will get the same movement regardless of FPS. For example, if you want a sprite to move 100 pixels every second, regardless of your game performance, you multiply delta by 100. This way, if it took long to process the scene, your sprite will move slightly further for this frame; if it is processed fast, it will move just a short distance. Either way you get expected results without complex calculations. After the update is done, the scene evaluates actions, simulates physics, and renders itself on screen. This process repeats itself as soon as it's finished. This allows for smooth movement and interactions. You will write the most essential code in the update: method, since it is getting called many times per second and everything on screen happens with the code we write in this method. You will usually iterate over all objects in your scene and dispatch some job for each to do, such as character moving and bullets disappearing off screen. The update: method is not used in a template project, but it is there if you want to customize it. Let's see how we can use it to move the Hello, World! label off the screen. First, find where the label is created in the scene init method, and find this line: myLabel.text = @"Hello, World!"; Add this code right after it: myLabel.name = @"theLabel"; Find the update: method; it looks like this: - (void)update:(CFTimeInterval)currentTime Insert the following code into it: [self enumerateChildNodesWithName:@"theLabel" usingBlock:^(SKNode *node, BOOL *stop) { node.position = CGPointMake(node.position.x - 2, node.position.y); if (node.position.x < - node.frame.size.width) { node.position = CGPointMake(self.frame.size.width, node.position.y); } }]; This method first finds the child node with the name "theLabel", and as we named our label the same, it finds it and gives control to the block inside. The child that it found is a node. If it finds other nodes with the name "theLabel", it will call the same block on all of them in the order they were found. Inside the block, we change the label position by 2 pixels to the left, keeping the vertical position the same. Then, we do a check, if the position of the label from the left border of the screen is further than the length of the label, we move the label to the right-hand side of the screen. This way, we create a seamless movement that should appear to be coming out of the right-hand side as soon as the label moves off screen. But if you run the project again, you will notice that the label does not disappear. The label takes a bit longer to disappear and blinks on screen instead of moving gracefully. There are two problems with our code. The first issue is that the frame is not changing when you rotate the screen, it stays the same even if you rotate the screen. This happens because the scene size is incorrectly calculated at startup. But we will fix that using the following steps: Locate the Endless Runner project root label in the left pane with our files. It says Endless Runner, 2 targets, iOS SDK 7.0 . Select it and select the General pane on the main screen. There, find the device orientation and the checkboxes near it. Remove everything but Landscape Left and Landscape Right . We will be making our game in landscape and we don't need the Portrait mode. Next, locate your ERGViewController.m file. Find the viewDidLoad method. Copy everything after the [super viewDidLoad] call. Make a new method and add the following code: - (void) viewWillLayoutSubviews { // Configure the view. [super viewWillLayoutSubviews]; SKView * skView = (SKView *)self.view; skView.showsFPS = YES; skView.showsNodeCount = YES; // Create and configure the scene. SKScene * scene = [ERGMyScene sceneWithSize:skView.bounds.size]; scene.scaleMode = SKSceneScaleModeAspectFill; // Present the scene. [skView presentScene:scene]; } Let's see why calculations of frame size are incorrect by default. When the view has finished its load, the viewDidLoad method is getting called, but the view still doesn't have the correct frame. It is only set to the correct dimensions sometime later and it returns a portrait frame before that time. We fix this issue by setting up the scene after we get the correct frame. The second problem is the anchoring of the nodes. Unlike UIViews, which are placed on screen using their top-left corner coordinates, SKNodes are getting placed on the screen based on their anchorPoint property. The following figure explains what anchor points are. By default, the anchor point is set at (0.5, 0.5), which means that the sprite position is its center point. This comes in handy when you need to rotate the sprite, as this way it rotates around its center axis. Imagine that the square in the preceding figure is your sprite. Different anchor points mean that you use these points as the position of the sprite. The anchor point at (0, 0) means that the left-bottom corner of our sprite will be on the position of the sprite itself. If it is at (0.5, 0.5), the center of the sprite will be on the position point. Anchor points go from 0 to 1 and represent the size of the sprite. So, if you make your anchor point (0.5, 0.5), it will be exactly on sprite center. We might want to use the (0,0) anchor point for our text label. Summary In this article, we saw the different parts of the Sprite Kit project, got an idea about various objects such as scenes, nodes, and so on. Resources for Article : Further resources on this subject: Interface Designing for Games in iOS [Article] Linking OpenCV to an iOS project [Article] Creating a New iOS Social Project [Article]
Read more
  • 0
  • 0
  • 7657

article-image-adding-graphical-user-interface
Packt
03 Jun 2015
12 min read
Save for later

Adding a Graphical User Interface

Packt
03 Jun 2015
12 min read
In this article by Dr. Edward Lavieri, the author of Getting Started with Unity 5, you will learn how to use Unity 5's new User Interface (UI) system. (For more resources related to this topic, see here.) An overview of graphical user interface Graphical User Interfaces or GUI (pronounced gooey) is a collection of visual components such as text, buttons, and images that facilitates a user's interaction with software. GUIs are also used to provide feedback to players. In the case of our game, the GUI allows players to interact with our game. Without a GUI, the user would have no visual indication of how to use the game. Imagine software without any on-screen indicators of how to use the software. The following image shows how early user interfaces were anything but intuitive: We use GUIs all the time and might not pay too close attention to them, unless they are poorly designed. If you've ever tried to figure out how to use an app on your Smartphone or could not figure out how to perform a specific action with desktop software, you've most likely encountered a poorly designed GUI. Functions of a GUI Our goal is to create a GUI for our game that both informs the user and allows for interaction between the game and the user. To that end, GUIs have two primary purposes: feedback and control. Feedback is generated by the game to the user and control is given to the user and managed by user input. Let's look at each of these more closely. Feedback Feedback can come in many forms. The most common forms of game feedback are visual and audio. Visual feedback can be something as simple as a text on a game screen. An example would be a game player's current score ever-present on the game screen. Games that include dialog systems where the player interacts with non-player characters (NPC) usually have text feedback on the screen that informs what the NPC's responses are. Visual feedback can also be non-textual, such as smoke, fire, explosions, or other graphic effect. Audio feedback can be as simple as a click sound when the user clicks or taps on a button or as complex as a radar ping when an enemy submarine is detected on long-distance sonar scans. You can probably think of all the audio feedback your favorite game provides. When you run your cart over a coin, an audio sound effect is played so there is no question that you earned the coin. If you take a moment to consider all of the audio feedback you are exposed to in games, you'll begin to appreciate the significance of them. Less typical feedback includes device vibration, which is sometimes used with smartphone applications and console games. Some attractions have taken feedback to another level through seat movement and vibration, dispensing liquid and vapor, and introducing chemicals that provide olfactory input. Control Giving players control of the game is the second function of GUIs. There is a wide gambit of types of control. The most simple is using buttons or menus in a game. A game might have a graphical icon of a backpack that, when clicked, gives the user access to the inventory management system of a game. Control seems like an easy concept and it is. Interestingly, most popular console games lack good GUI interfaces, especially when it comes to control. If you play console games, think about how many times you have to refer to the printed or in-game manual. Do you intuitively know all of the controller key mappings? How do you jump, switch weapons, crotch, throw a grenade, or go into stealth mode? In the defense of the game studios that publish these games, there is a lot of control and it can be difficult to make them intuitive. By extension, control is often physical in addition to graphical. Physical components of control include keyboards, mice, trackballs, console controllers, microphones, and other devices. Feedback and control Feedback and control GUI elements are often paired. When you click or tap a button, it usually has both visual and audio effects as well as executing the user's action. When you click (control) on a treasure chest, it opens (visual feedback) and you hear the creak of the old wooden hinges (audio feedback). This example shows the power of using adding feedback to control actions. Game Layers At a primitive level, there are three layers to every game. The core or base level is the Game Layer. The top layer is the User Layer; this is the actual person playing your game. So, it is the layer in between, the GUI Layer that serves as an intermediary between a game and its player. It becomes clear that designing and developing intuitive and well-functioning GUIs is important to a game's functionality, the user experience, and a game's success. Unity 5's UI system Unity's UI system has recently been re-engineered and is now more powerful than ever. Perhaps the most important concept to grasp is the Canvas object. All UI elements are contained in a canvas. Project and scenes can have more than one canvas. You can think of a canvas as a container for UI elements. Canvas To create a canvas, you simply navigate and select GameObject | UI | Canvas from the drop-down menu. You can see from the GameObject | UI menu pop-up that there are 11 different UI elements. Alternatively, you can create your first UI element, such as a button and Unity will automatically create a canvas for you and add it to your Hierarchy view. When you create subsequent UI elements, simply highlight the canvas in the Hierarchy view and then navigate to the GameObject | UI menu to select a new UI element. Here is a brief description of each of the UI elements: UI element Description Panel A frame object Button Standard button that can be clicked Text Text with standard text formatting Image Images can be simple, sliced, tiled, and filled Raw Image Texture file Slider Slider with min and max values Scrollbar Scrollbar with values between 0 and 1 Toggle Standard checkbox; can also be grouped Input Field Text input field Canvas The game object container for UI elements Event System Allows us to trigger scripts from UI elements. An Event System is automatically created when you create a canvas. You can have multiple canvases in your game. As you start building larger games, you'll likely find a use for more than one canvas. Render mode There are a few settings in the Inspector view that you should be aware of regarding your canvas game object. The first setting is the render mode. There are three settings: Screen Space – Overlay, Screen Space – Camera, and World Space: In this render mode, the canvas is automatically resized when the user changes the size or resolution of the game screen. The second render mode, Screen Space – Camera, has a plane distance property that determines how far the canvas is rendered from the camera. The third render mode is World Space. This mode gives you the most control and can be manipulated much like any other game object. I recommend experimenting with different render modes so you know which one you like best and when to use each one. Creating a GUI Creating a GUI in Unity is a relatively easy task. We first create a canvas, or have Unity create it for us when we create our first UI element. Next, we simply add the desired UI elements to our canvas. Once all the necessary elements are in our canvas, you can arrange and format them. It is often best to switch to 2D mode in the Scene view when placing the UI elements on the canvas. This simply makes the task a bit easier. If you have used earlier versions of Unity, you'll note that several things have changed regarding creating and referencing GUI elements. For example, you'll need to include the using UnityEngine.UI; statement before referencing UI components. Also, instead of referencing GUI text as public GUIText waterHeld; you now use public Text waterHeld;. Heads-up displays A game's heads-up display (HUD) is graphical and textual information available to the user at all times. No action should be required of the user other than to look at a specific region of the screen to read the displays. For example, if you are playing car-racing game, you might have an odometer, speedometer, compass, fuel tank level, air pressure, and other visual indicators always on the screen. Creating a HUD Here are the basic steps to create a HUD: Open the game project and load the scene. Navigate and select the GameObject | UI | Text option from the drop-down menu. This will result in a Canvas game object being added to the Hierarchy view, along with a text child item. Select the child item in the Hierarchy view. Then, in the Inspector view, change the text to what you want displayed on the screen. In the Inspector view, you can change the font size. If necessary, you can change the Horizontal Overflow option from Wrap to Overflow: Zoom out in the Scene view until you can see the GUI Canvas. Use the transform tools to place your new GUI element in the top-left corner of the screen. Depending on how you are viewing the scene in the Scene view, you might need to use the hand tool to rotate the scene. So, if your GUI text appears backwards, just rotate the scene until it is correct. Repeat steps 2 through 5 until you've created all the HUD elements you need for your game. Mini-maps Miniature-maps or mini-maps provide game players with a small visual aid that helps them maintain perspective and direction in a game. These mini-maps can be used for many different purposes, depending on the game. Some examples include the ability to view a mini-map that overlooks an enemy encampment; a zoomed out view of the game map with friendly and enemy force indicators; and a mini-map that has the overall tunnel map while the main game screen views the current section of tunnel. Creating a Mini-Map Here are the steps used to create a mini-map for our game: Navigate and select GameObject | Camera from the top menu. In the Hierarchy view, change the name from Camera to Mini-Map. With the mini-map camera selected, go to the Inspector view and click on the Layer button, then Add Layer in the pop-up menu. In the next available User Layer, add the name Mini-Map: Select the Mini-Map option in the Hierarchy view, and then select Layer | Mini-Map. Now the new mini-map camera is assigned to the Mini-Map layer: Next, we'll ensure the main camera is not rendering the Mini-Map camera. Select the Main Camera option in the Hierarchy view. In the Inspector view, select Culling Mask, and then deselect Mini-Map from the pop-up menu: Now we are ready to finish the configuration of our mini-map camera. Select the Mini-Map in the Hierarchy view. Using the transform tools in the Scene view, adjust the camera object so that it shows the area of the game environment you want visible via the mini-map. In the Inspector view, under Camera, make the settings match the following values: Setting Value Clear Flags Depth only Culling Mask Everything Projection Orthographic Size 25 Clipping Planes Near 0.3; Far 1000 Viewpoint Rect X 0.75; Y 0.75; W 1; H 1 Depth 1 Rendering Path User Player Settings Target Texture None Occlusion Culling Selected HDR Not Selected With the Mini-Map camera still selected, right-click on each of the Flare Layer, GUI Layer, and Audio Listener components in the Inspector view and select Remove Component. Save your scene and your project. You are ready to test your mini-map. Mini-maps can be very powerful game components. There are a couple of things to keep in mind if you are going to use mini-maps in your games: Make sure the mini-map size does not obstruct too much of the game environment. There is nothing worse than getting shot by an enemy that you could not see because a mini-map was in the way. The mini-map should have a purpose—we do not include them in games because they are cool. They take up screen real estate and should only be used if needed, such as helping the player make informed decisions. In our game, the player is able to keep an eye on Colt's farm animals while he is out gathering water and corn. Items should be clearly visible on the mini-map. Many games use red dots for enemies, yellow for neutral forces, and blue for friendlies. This type of color-coding provides users with a lot of information at a very quick glance. Ideally, the user should have the flexibility to move the mini-map to a corner of their choosing and toggle it on and off. In our game, we placed the mini-map in the top-right corner of the game screen so that the HUD objects would not be in the way. Summary In this article, you learned about the UI system in Unity 5. You gained an appreciation for the importance of GUIs in games we create. Resources for Article: Further resources on this subject: Bringing Your Game to Life with AI and Animations [article] Looking Back, Looking Forward [article] Introducing the Building Blocks for Unity Scripts [article]
Read more
  • 0
  • 0
  • 7654

article-image-run-xcode-run
Packt
05 Feb 2015
9 min read
Save for later

Run Xcode Run

Packt
05 Feb 2015
9 min read
In this article by Jorge Jordán, author of the book Cocos2d Game Development Blueprints, we will see how to run the newly created project in Xcode. (For more resources related to this topic, see here.) Click on Run at the top-left of the Xcode window and it will run the project in the iOS Simulator, which defaults to an iOS 6.1 iPhone: Voilà! You've just built your first Hello World example with Cocos2d v3, but before going further, let's take a look at the code to understand how it works. We will be using iOS Simulator to run the game unless otherwise specified. Understanding the default project We are going to take an overview of the classes available in a new project, but don't worry if you don't understand everything; the objective of this section is just to get familiar with the look of a Cocos2d game. If you open the main.m class under the Supporting Files group, you will see: int main(int argc, char *argv[]) {    @autoreleasepool {        int retVal = UIApplicationMain(argc, argv, nil,         @"AppDelegate");        return retVal;    } } As you can see, the @autorelease block means that ARC is enabled by default on new Cocos2d projects so we don't have to worry about releasing objects or enabling ARC. ARC is the acronym for Automatic Reference Counting and it's a compiler iOS feature to provide automatic memory management of objects. It works by adding code at compile time, ensuring every object lives as long as necessary, but not longer. On the other hand, the block calls AppDelegate, a class that inherits from CCAppDelegate which implements the UIApplicationDelegate protocol. In other words, the starting point of our game and the place to set up our app is located in AppDelegate, like a typical iOS application. If you open AppDelegate.m, you will see the following method, which is called when the game has been launched: -(BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    [self setupCocos2dWithOptions:@{          CCSetupShowDebugStats: @(YES),    }];    return YES; } Here, the only initial configuration specified is to enable the debug stats, specifying the option CCSetupShowDebugStats: @(YES), that you can see in the previous block of code. The number on the top indicates the amount of draw calls and the two labels below are the time needed to update the frame and the frame rate respectively. The maximum frame rate an iOS device can have is 60 and it's a measure of the smoothness a game can attain: the higher the frame rate, the smoother the game. You will need to have the top and the bottom values in mind as the number of draw calls and the frame rate will let you know how efficient your game will be. The next thing to take care of is the startScene method: -(CCScene *)startScene {    // The initial scene will be GameScene    return [IntroScene scene]; } This method should be overriden to indicate the first scene we want to display in our game. In this case, it points to IntroScene where the init method looks like the following code: - (id)init {    // Apple recommends assigning self with super's return value    self = [super init];    if (!self) {        return(nil);      }    // Create a colored background (Dark Gray)    CCNodeColor *background = [CCNodeColor nodeWithColor:[CCColorcolorWithRed:0.2f green:0.2f blue:0.2f alpha:1.0f]];    [self addChild:background];    // Hello world    CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World"fontName:@"Chalkduster" fontSize:36.0f];    label.positionType = CCPositionTypeNormalized;    label.color = [CCColor redColor];    label.position = ccp(0.5f, 0.5f); // Middle of screen    [self addChild:label];    // Helloworld scene button    CCButton *helloWorldButton = [CCButton buttonWithTitle:@"[Start ]" fontName:@"Verdana-Bold" fontSize:18.0f];    helloWorldButton.positionType = CCPositionTypeNormalized;    helloWorldButton.position = ccp(0.5f, 0.35f);    [helloWorldButton setTarget:self     selector:@selector(onSpinningClicked:)];    [self addChild:helloWorldButton];    // done    return self; } This code first calls the initialization method for the superclass IntroScene by sending the [super init] message. Then it creates a gray-colored background with a CCNodeColor class, which is basically a solid color node, but this background won't be shown until it's added to the scene, which is exactly what [self addChild:background] does. The red "Hello World" label you can see in the previous screenshot is an instance of the CCLabelTTF class, whose position will be centered on the screen thanks to label.position = ccp(0.5f, 0.5f). Cocos2d provides the cpp(coord_x, coord_y) method, which is a precompiler macro for CGPointMake and both can be used interchangeably. The last code block creates CCButton that will call onSpinningClicked once we click on it. This source code isn't hard at all, but what will happen when we click on the Start button? Don't be shy, go back to the iOS Simulator and find out! If you take a look at the onSpinningClicked method in IntroScene.m, you will understand what happened: - (void)onSpinningClicked:(id)sender {    // start spinning scene with transition    [[CCDirector sharedDirector] replaceScene:[HelloWorldScene     scene]        withTransition:[CCTransitiontransitionPushWithDirection:CCTransitionDirectionLeftduration:1.0f]]; } This code presents the HelloWorldScene scene replacing the current one (InitScene) and it's being done by pushing HelloWorldScene to the top of the scene stack and using a horizontal scroll transition that will last for 1.0 second. Let's take a look at the HelloWorldScene.m to understand the behavior we just experienced: @implementation HelloWorldScene {    CCSprite *_sprite; } - (id)init {    // Apple recommends assigning self with super's return value    self = [super init];    if (!self) {        return(nil);    }    // Enable touch handling on scene node    self.userInteractionEnabled = YES;    // Create a colored background (Dark Gray)    CCNodeColor *background = [CCNodeColor nodeWithColor:[CCColorcolorWithRed:0.2f green:0.2f blue:0.2f alpha:1.0f]];    [self addChild:background];    // Add a sprite    _sprite = [CCSprite spriteWithImageNamed:@"Icon-72.png"];    _sprite.position =     ccp(self.contentSize.width/2,self.contentSize.height/2);    [self addChild:_sprite];    // Animate sprite with action    CCActionRotateBy* actionSpin = [CCActionRotateByactionWithDuration:1.5f angle:360];    [_sprite runAction:[CCActionRepeatForeveractionWithAction:actionSpin]];    // Create a back button    CCButton *backButton = [CCButton buttonWithTitle:@"[ Menu ]"fontName:@"Verdana-Bold" fontSize:18.0f];    backButton.positionType = CCPositionTypeNormalized;    backButton.position = ccp(0.85f, 0.95f); // Top Right ofscreen    [backButton setTarget:self     selector:@selector(onBackClicked:)];    [self addChild:backButton];    // done    return self; } This piece of code is very similar to the one we saw in IntroScene.m, which is why we just need to focus on the differences. If you look at the top of the class, you can see how we are declaring a private instance for a CCSprite class, which is also a subclass of CCNode, and its main role is to render 2D images on the screen. The CCSprite class is one of the most-used classes in Cocos2d game development, as it provides a visual representation and a physical shape to the objects in view. Then, in the init method, you will see the instruction self.userInteractionEnabled = YES, which is used to enable the current scene to detect and manage touches by implementing the touchBegan method. The next thing to highlight is how we initialize a CCSprite class using an image, positioning it in the center of the screen. If you read a couple more lines, you will understand why the icon rotates as soon as the scene is loaded. We create a 360-degree rotation action thanks to CCRotateBy that will last for 1.5 seconds. But why is this rotation repeated over and over? This happens thanks to CCActionRepeatForever, which will execute the rotate action as long as the scene is running. The last piece of code in the init method doesn't need explanation as it creates a CCButton that will execute onBackClicked once clicked. This method replaces the scene HelloWorldScene with IntroScene in a similar way as we saw before, with only one difference: the transition happens from left to right. Did you try to touch the screen? Try it and you will understand why touchBegan has the following code: -(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {    CGPoint touchLoc = [touch locationInNode:self];    // Move our sprite to touch location    CCActionMoveTo *actionMove = [CCActionMoveToactionWithDuration:1.0f position:touchLoc];    [_sprite runAction:actionMove]; } This is one of the methods you need to implement to manage touch. The others are touchMoved, touchEnded, and touchCancelled. When the user begins touching the screen, the sprite will move to the registered coordinates thanks to a commonly used action: CCActionMoveto. This action just needs to know the position that we want to move our sprite to and the duration of the movement. Now that we have had an overview of the initial project code, it is time to go deeper into some of the classes we have shown. Did you realize that CCNode is the parent class of several classes we have seen? You will understand why if you keep reading. Summary In this article, we had our first contact with a Cocos2d project. We executed a new project and took an overview of it, understanding some of the classes that are part of this framework. Resources for Article: Further resources on this subject: Dragging a CCNode in Cocos2D-Swift [Article] Animations in Cocos2d-x [Article] Why should I make cross-platform games? [Article]
Read more
  • 0
  • 0
  • 7593

article-image-construct-game-development-platformer-revisited-2d-shooter
Packt
05 Jun 2012
11 min read
Save for later

Construct Game Development: Platformer Revisited, a 2D Shooter

Packt
05 Jun 2012
11 min read
(For more resources on Game Development, see here.) Before we start As there is a large amount of ground to cover in this chapter, we'll be moving quickly through steps similar to those we've done before. If it's been a while since you've used Construct, then you may find it useful to read through a chapter or two again before continuing, because certain steps assume that you are able to complete acO ons we have performed in the past. Multiplayer: getting your friends involved We're ready to start our next game, a multiplayer side-scrolling shooter, but before we add any shooO ng, we'll need to have the multiplayer side-scroller part finished first. So let's get to it! Time for action – creating the game assets and title screen The first thing we will need to do is to create our game content and create the first layout of the game, the title screen. First, draw up our player graphics and guns. We'll want the torso to be a separate object from the legs for easier animation. Use red dots where the legs will be attached to as markers for image point placement later on. Also include drawings for three weapons: a pistol, an uzi, and a shotgun: Next, we can draw up our enemies for the game. In this case, we'll use an enemy robot with a turret arm that shoots balls of plasma: We'll also need some scenery and ground objects for the levels: Finally, we'll need a graphic to tell the player to go onto the next screen when no enemies are present: Now we can move on to starting our game. Create a new project and set its Name to SideShooter, and enter your Creator name. Then set the window size to 800 by 600. Create the global variables CanGoNext, CurrentScreen, NumPlayers, P1Lives, P2Lives, GameWonLost, and RespawnY with values 0, 0, 1, 3, 3, 0, and 100 respectively. Following that, rename the first layout to Title and its event sheet to Title events . On this layout, create the layers Text, Buttons, and Background in top-to-bottom order. Selecting the Background layer, create a Panel object and name it Background before setting its top corner filters to Green and bottom corner filters to DarkGreen. Stretch this object to cover the whole of the layout and lock the layer. Now, on the Buttons layer, create a sprite and draw a box with the word Play in it. PosiO on this object in the center of the layout. This will be the start button for our game and should have the name btnPlay. Next, add the Mouse & Keyboard and XAudio2 objects into the layout and give them the global property. In order to finish the layout design, create a Text object on the Text layer and set its name to Title and its text to SideShooter, and position it above the btnPlay object: Switch over to the event sheet editor to add in a Start of layout event and use it to play a music file, Title.mp3, and loop it. This can be any title music you'd like, and it will also be played at the game's end screen. Next, create an event for the MouseKeyboard object with the condition Mouse is over object to check if the mouse overlaps btnPlay. Give this event the action Set colour filter for the btnPlay object to set the filter to Grey – 40. Now create an Else event to set the filter color back to White. In order to finish the event sheet, create an event with the condition On object clicked and select the object btnPlay. Add actions to this event to set the value of NumPlayers to 1 and the value of CurrentScreen to 0 before adding the final System action Next Layout: Time for action – designing the level Now that we have our graphics created, we can put them into our second layout and make the first playable level. Now we're ready to create a level layout. Create a layout and name it Level1 and its event sheet Level1 events . Then create our main game event sheet Game. For the layout, set its width to a multiple of the screen width (800), and check the box for Unbounded scrolling. In the event sheet of this layout, include our main event sheet Game. Next, give this layout the layers HUD, Foreground, FrontScenery, Objects, LevelLights, ShadowCasters, BackScenery, and Walls in top-to-bottom order. After that, set the ScrollX and ScrollY rates of the HUD and Walls layers to 0% On the objects layer, create a Tiled Background object named Ground and give it the smaller light gray tile image. Ensure it has the Solid attribute and stretch and place some of them to form a level design. Now create a Sprite object with the light green tile image and name it CrateBox . Give it the Solid attribute as well and place some around the level too. Have its collisions mode set to Bounding box. Next, create a Sprite named ExitLevel and fill it with a solid color. Give it a width of 32 and stretch it so that it's taller than the display height (600). Then finish the object by checking the box for Invisible on start and placing it at the end of the level: With the base layout complete, we can now add in three more invisible objects to handle scrolling. These are going to be Box objects with the names BackStopper, FrontStopper, and GoNextScreen. Have the BackStopper and FrontStopper objects colored red and marked Solid with a width of 120. Set the Hotspot property of the BackStopper object to Bottom-right, and the FrontStopper to Bottom-Left, before positioning them at 0,600 and 800,600 respectively. Next, have the GoNextScreen box colored green and a width of 32 as well as a Hotspot of Bottom-right. Position this object at 800,600: Time for action – creating player characters and conveyor belt objects Now we can create our player character objects and also add moving and staO c conveyor belts into our level. We are now ready to create our player objects. Start by inserting a Sprite and paste the standing image of the player character legs into it. Have an image point named 1 at the red spot that we drew earlier, and then place the hotspot at the bottom-middle point of the image ( num-pad 2) as shown in the following image: Name this sprite P1Legs, and for its Default animation, set the animation Tag to Stopped before checking the Auto Mirror checkbox in the main Appearance settings of the object. Next, give it the platform behavior with the settings shown as follows: Next, scroll down to the Angle properties box and check the Auto mirror checkbox. Now we are ready to add the object to a family. Scroll up to the Groups sub-group Families and click on Add to bring up the Construct: New Family screen. Note that Construct Classic comes with some default families, but will also display any family that have been used previously: Click on Edit Families to bring up the Construct: Families editor screen. On this screen, enter the name Legs and click on the + button: We can now draw the family image using the image editor that appears, shown as follows: After finishing the image, save it by exiting out of the image editor and check that the family is now in the list. Once finished, click on Done to return to the family selection page. Now select the Legs family and click on Ok: Now we can add the animation Walk in for our object. In this animation, set the Tag to Walking, and add the other leg sprites so they match the following image: Now change the settings of the animation to have an Animation speed of 5 and check the Loop checkbox. Next, copy the object and right-click on the layout to use the Paste Clone option, and name this clone as P2Legs. In the Platform behavior for this object, change the Control setting to Player 2 . Now go into the project properties and scroll to the bottom section, Controls. Click on the Add / Edit option for the Manage Controls setting at the bottom to bring up the controls box: Use the red X button to remove all of the controls below Jump. Then click on the Green plus button to add a new control. Select this control and click on the Pencil and paper button to change its name to Move Left. Click on the Key name for this control to bring up a drop-down box and set it to A. Now, in the Player drop-down box, select Player 2 for this control. It should match the following screenshot: Now continue to add controls until it matches the settings in the following screenshot before clicking on Done: Next, we'll create the bodies of our player objects. Create a Sprite called P1Torso and paste the normal body image of our character into it. Then position the Hotspot in the bottom-middle of the body. Give this sprite an image point 1 in the centre of its hand: Rename the Default animation to Normal and set its speed to 0, and check the Auto Mirror checkbox for this object as well. Create two more animations, Up and Down respectively. Set their frames to match the following screenshot: Now give the object a new family and name it Body. Then create Private Variables of names Weapon, Ammo, and GunAngle . Set the starting values to 0, 99, and 0 respectively. Clone this object as well to create P2Torso, and replace the sprites with the second player graphics. Now select P1Legs and scroll down to the Groups | Container properties to click on Add object and select P1Torso. The properties box and sprites should match the following screenshot: Next, put the P2Torso object into the container P2Legs using the same method. Now, on the Walls layer, create a Tiled Background object named FactoryWall and paste the dark wall graphic into it. Then resize it to 800 in Width by 600 in Height and set its position to 0,0 . The layout should look similar to the following screenshot: Switch to the FrontScenery layer and create a Tiled Background object called ConveyorMiddle, and give it the center piece of the conveyor belt images: Give this object the Private Variables of Direction and Speed with starting values of 0. Place these around the map to act as scenery, as well as using them as an object to move players with at certain points. Set the Direction variable to 1 to have the conveyor belt move right, and 2 to move left. Speed is the attribute used to determine how fast a player character is moved by the conveyor belt; a speed of 25 works well in this instance. The following screenshot shows a moving conveyor belt in the layout: On the same layer, create a Sprite with the name ConveyorStart and Collisions set to None. Use the starting conveyor belt image for this object and set the Hotspot to the middle-right (num-pad 6). Give this sprite the Attribute of Destroy on Startup. Create a second Sprite with the same setttigs called ConveyorEnd and a Hotspot in the middle-left (num-pad 4). Both sprites are shown in the following screenshot: Time for action – creating the HUD objects Now, we will move on to creating the Heads Up Display (HUD) objects for our game. We are now ready to create the HUD objects for our game. Switch to the HUD layer and create a Sprite called P1Face, and give it an image of the P1Torso head and set its Collisions mode to Bounding Box: Next, create a Text object called P1Info and set it up to match the following screenshot: Create similar objects replacing P1 with P2 for the second player. In this case, have the objects match the following layout screenshot: Now create a Text object for when the second player is not playing, and call it P2Join. Set its text to Press left control to join and have it matched to the following screenshot: Give it a Sine behavior to make it fade in and out by matching its settings to those in the following screenshot: Now create the final HUD item, a Sprite called NextSign, and place the next arrow image into it. Set the Collisions of this object to None:
Read more
  • 0
  • 0
  • 7433
article-image-warfare-unleashed-implementing-gameplay
Packt
10 Jul 2013
24 min read
Save for later

Warfare Unleashed Implementing Gameplay

Packt
10 Jul 2013
24 min read
(For more resources related to this topic, see here.) Equipping the entities The SceneNode base class was inherited by the Entity class. Entities are the central part of this chapter. It's all about the interaction between entities of different kinds. Before starting to implement all those interactions, it is reasonable to think about crucial properties our entities need to have. Introducing hitpoints Since, we are preparing our airplanes for the battlefield, we need to provide them with new specific attributes. To our class definition of Entity, we add a new member variable that memorizes the current hitpoints. Hitpoints ( HP ) are a measure for the hull integrity of an entity; the entity is destroyed as soon as the hitpoints reach or fall below zero. In addition to the member variable, we provide member functions that allow the modification of the hitpoints. We do not provide direct write access, however, the hitpoints can be decreased (the plane is damaged) or increased (the plane is repaired). Also, a destroy() function instantly destroys the entity. class Entity : public SceneNode{public:explicit Entity(int hitpoints);void repair(int points);void damage(int points);void destroy();int getHitpoints() const;bool isDestroyed() const;...private:int mHitpoints;...}; The implementation is as expected: repair() adds the specified hitpoints, damage() subtracts them, and destroy() sets them to zero. Storing entity attributes in data tables In our game, there are already two different airplanes with different attributes. For this chapter, we introduce a third one to make the game more interesting. With an increasing amount of new aircraft types, attributes such as speed, hitpoints, used texture, or fire rate may vary strongly among them. We need to think of a way to store those properties in a central place, allowing easy access to them. What we clearly want to avoid are case differentiations in every Aircraft method, since this makes the local logic code less readable, and spreads the attributes across different functions. Instead of if/else cascades or switch statements, we can store the attributes in a central table, and just access the table every time we need an attribute. Let's define the type of such a table entry in the case of an airplane. We choose the simplest way, and have a structure AircraftData with all members public. This type is defined in the file DataTables.hpp. struct AircraftData{int hitpoints;float speed;Textures::ID texture;}; While AircraftData is a single table entry, the whole table is represented as a sequence of entries, namely std::vector<AircraftData>. Next, we write a function that initializes the table for different aircraft types. We begin to define a vector of the correct size (Aircraft::TypeCount is the last enumerator of the enum Aircraft::Type, it contains the number of different aircraft types). Since the enumerators are consecutive and begin at zero, we can use them as indices in our STL container. We thus initialize all the attributes for different airplanes, and eventually return the filled table. std::vector<AircraftData> initializeAircraftData(){std::vector<AircraftData> data(Aircraft::TypeCount);data[Aircraft::Eagle].hitpoints = 100;data[Aircraft::Eagle].speed = 200.f;data[Aircraft::Eagle].texture = Textures::Eagle;data[Aircraft::Raptor].hitpoints = 20;data[Aircraft::Raptor].speed = 80.f;data[Aircraft::Raptor].texture = Textures::Raptor;...return data;} The global function initializeAircraftData() is declared in DataTables.hpp and defined in DataTables.cpp. It is used inside Aircraft.cpp, to initialize a global constant Table. This constant is declared locally in the .cpp file, so only the Aircraft internals can access it. In order to avoid name collisions in other files, we use an anonymous namespace. namespace{const std::vector<AircraftData> Table = initializeAircraftData();} Inside the Aircraft methods, we can access a constant attribute of the own plane type using the member variable mType as index. For example, Table[mType].hitpoints denotes the maximal hitpoints of the current aircraft. Data tables are only the first step of storing gameplay constants. For more flexibility, and to avoid recompiling the application, you can also store these constants externally, for example, in a simple text file or using a specific file format. The application initially loads these files, parses the values, and fills the data tables accordingly. Nowadays, it is very common to load gameplay information from external resources. There are text-based formats such as YAML or XML, as well as, many application-specific text and binary formats. There are also well-known C++ libraries such as Boost.Serialize (www.boost. org) that help with loading and saving data structures from C++. One possibility that has recently gained popularity consists of using script languages, most notably Lua (www.lua.org), in addition to C++. This has the advantage that not only constant data, but dynamic functionality can be outsourced and loaded during runtime. Displaying text We would like to add some text on the display, for example, to show the hitpoints or ammunition of different entities. Since this text information is supposed to be shown next to the entity, it stands to reason to attach it to the corresponding scene node. We therefore, create a TextNode class which inherits SceneNode as shown in the following code: class TextNode : public SceneNode{public:explicit TextNode(const FontHolder& fonts,const std::string& text);void setString(const std::string& text);private:virtual void drawCurrent(sf::RenderTarget& target,sf::RenderStates states) const;private:sf::Text mText;}; The implementation of the functions is not complicated. The SFML class sf::Text provides most of what we need. In the TextNode constructor, we retrieve the font from the resource holder and assign it to the text. TextNode::TextNode(const FontHolder& fonts, const std::string& text){mText.setFont(fonts.get(Fonts::Main));mText.setCharacterSize(20);setString(text);} The function to draw the text nodes just forwards the call to the SFML render target, as you know it from sprites. void TextNode::drawCurrent(sf::RenderTarget& target, sf::RenderStatesstates) const{target.draw(mText, states);} For the interface, mainly the following method is interesting. It assigns a new string to the text node, and automatically adapts to its size. centerOrigin() is a utility function we wrote; it sets the object's origin to its center, which simplifies positioning a lot. void TextNode::setString(const std::string& text){mText.setString(text);centerOrigin(mText);} In the Aircraft constructor, we create a text node and attach it to the aircraft itself. We keep a pointer mHealthDisplay as a member variable and let it point to the attached node. std::unique_ptr<TextNode> healthDisplay(new TextNode(fonts, ""));mHealthDisplay = healthDisplay.get();attachChild(std::move(healthDisplay)); In the method Aircraft::update(), we check for the current hitpoints, and convert them to a string, using our custom toString() function. The text node's string and relative position are set. Additionally, we set the text node's rotation to the negative aircraft rotation, which compensates the rotation in total. We do this in order to have the text always upright, independent of the aircraft's orientation. mHealthDisplay->setString(toString(getHitpoints()) + " HP");mHealthDisplay->setPosition(0.f, 50.f);mHealthDisplay->setRotation(-getRotation()); Creating enemies Enemies are other instances of the Aircraft class. They appear at the top of the screen and move downwards, until they fly past the bottom of the screen. Most properties are the same for the player and enemies, so we only explain the new aircraft functionality. Movement patterns By default, enemies fly downwards in a straight line. But it would be nice if different enemies moved differently, giving the feeling of a very basic artificial intelligence ( AI ). Thus, we introduce specific movement patterns. Such a pattern can be described as a sequence of directions to which the enemy airplane heads. A direction consists of an angle and a distance. struct Direction{Direction(float angle, float distance);float angle;float distance;}; Our data table for aircraft gets a new entry for the sequence of directions as shown in following code: struct AircraftData{int hitpoints;float speed;Textures::ID texture;std::vector<Direction> directions;}; Let's implement a zigzag movement pattern for the Raptor plane. First, it steers for 80 units in 45 degrees direction. Then, the angle changes to -45 degrees, and the plane traverses 160 units back. Last, it moves again 80 units in +45 degrees direction, until it arrives at its original x position. data[Aircraft::Raptor].directions.push_back(Direction( 45, 80));data[Aircraft::Raptor].directions.push_back(Direction(-45, 160));data[Aircraft::Raptor].directions.push_back(Direction( 45, 80)); For the Avenger plane, we use a slightly more complex pattern: it is essentially a zigzag, but between the two diagonal movements, the plane moves straight for 50 units. data[Aircraft::Avenger].directions.push_back(Direction(+45, 50));data[Aircraft::Avenger].directions.push_back(Direction( 0, 50));data[Aircraft::Avenger].directions.push_back(Direction(-45, 100));data[Aircraft::Avenger].directions.push_back(Direction( 0, 50));data[Aircraft::Avenger].directions.push_back(Direction(+45, 50)); The following figure shows the sequence of directions for both planes; the Raptor plane is located on the left, Avenger on the right: This way of defining movement is very simple, yet it enables a lot of possibilities. You can let the planes fly in any direction (also sideward or backwards); you can even approximate curves when using small intervals. Now, we look at the logic we have to implement to follow these movement patterns. To the Aircraft class, we add two member variables: mTravelledDistance, which denotes the distance already travelled for each direction, and mDirectionIndex, to know which direction the plane is currently taking. First, we retrieve the aircraft's movement pattern and store it as a reference to const named directions. We only proceed if there are movement patterns for the current type (otherwise the plane flies straight down). void Aircraft::updateMovementPattern(sf::Time dt){const std::vector<Direction>& directions= Table[mType].directions;if (!directions.empty()){ Second, we check if the current direction has already been passed by the plane (that is, the travelled distance is higher than the direction's distance). If so, the index is advanced to the next direction. The modulo operator allows a cycle; after finishing the last direction, the plane begins again with the first one. float distanceToTravel= directions[mDirectionIndex].distance;if (mTravelledDistance > distanceToTravel){mDirectionIndex= (mDirectionIndex + 1) % directions.size();mTravelledDistance = 0.f;} Now, we have to get a velocity vector out of the angle. First, we turn the angle by 90 degrees (by default, 0 degrees points to the right), but since our planes fly downwards, we work in a rotated coordinate system, such that we can use a minus to toggle between left/right. We also have to convert degrees to radians, using our function toRadian(). The velocity's x component is computed using the cosine of the angle multiplied with the maximal speed; analogue for the y component, where the sine is used. Eventually, the travelled distance is updated: float radians= toRadian(directions[mDirectionIndex].angle + 90.f);float vx = getMaxSpeed() * std::cos(radians);float vy = getMaxSpeed() * std::sin(radians);setVelocity(vx, vy);mTravelledDistance += getMaxSpeed() * dt.asSeconds();}} Note that if the distance to travel is no multiple of the aircraft speed, the plane will fly further than intended. This error is usually small, because there are many logic frames per second, and hardly noticeable, since each enemy will only be in the view for a short time. Spawning enemies It would be good if enemies were initially inactive, and the world created them as soon as they come closer to the player. By doing so, we do not need to process enemies that are relevant in the distant future; the scene graph can concentrate on updating and drawing active enemies. We create a structure nested inside the World class that represents a spawn point for an enemy. struct SpawnPoint{SpawnPoint(Aircraft::Type type, float x, float y);Aircraft::Type type;float x;float y;}; A member variable World::mEnemySpawnPoints of type std::vector<SpawnPoint> holds all future spawn points. As soon as an enemy position enters the battlefield, the corresponding enemy is created and inserted to the scene graph, and the spawn point is removed. The World class member function getBattlefieldBounds(), returns sf::FloatRect to the battlefield area, similar to getViewBounds(). The battlefield area extends the view area by a small rectangle at the top, inside which new enemies spawn before they enter the view. If an enemy's y coordinate lies below the battlefield's top member, the enemy will be created at its spawn point. Since enemies face downwards, they are rotated by 180 degrees. void World::spawnEnemies(){while (!mEnemySpawnPoints.empty()&& mEnemySpawnPoints.back().y> getBattlefieldBounds().top){SpawnPoint spawn = mEnemySpawnPoints.back();std::unique_ptr<Aircraft> enemy(new Aircraft(spawn.type, mTextures, mFonts));enemy->setPosition(spawn.x, spawn.y);enemy->setRotation(180.f);mSceneLayers[Air]->attachChild(std::move(enemy));mEnemySpawnPoints.pop_back();}} Now, let's insert the spawn points. addEnemy() effectively calls mEnemySpawnPoints.push_back(), and interprets the passed coordinates relative to the player's spawn position. After inserting all spawn points, we sort them by their y coordinates. By doing so, spawnEnemies() needs to check only the elements at the end of the sequence instead of iterating through it every time. void World::addEnemies(){addEnemy(Aircraft::Raptor, 0.f, 500.f);addEnemy(Aircraft::Avenger, -70.f, 1400.f);...std::sort(mEnemySpawnPoints.begin(), mEnemySpawnPoints.end(),[] (SpawnPoint lhs, SpawnPoint rhs){return lhs.y < rhs.y;});} Here is an example of the player facing four Avenger enemies. Above each, you see how many hitpoints it has left. Adding projectiles Finally, time to add what makes a game fun. Shooting down stuff is essential for our game. The code to interact with the W orld class is already defined, thanks to the actions in Player and to the existing Entity base class. All that's left is to define the projectiles themselves. We start with the Projectile class. We have normal machine gun bullets and homing missiles represented by the same class. This class inherits from the Entity class and is quite small, since it doesn't have anything special that differentiates it from other entities apart from collision tests, which we will talk about later. class Projectile : public Entity{public:enum Type{AlliedBullet,EnemyBullet,Missile,TypeCount};public:Projectile(Type type,const TextureHolder& textures);void guideTowards(sf::Vector2f position);bool isGuided() const;virtual unsigned int getCategory() const;virtual sf::FloatRect getBoundingRect() const;float getMaxSpeed() const;int getDamage() const;private:virtual void updateCurrent(sf::Time dt,CommandQueue& commands);virtual void drawCurrent(sf::RenderTarget& target,sf::RenderStates states) const;private:Type mType;sf::Sprite mSprite;sf::Vector2f mTargetDirection;}; Nothing fun or exciting here; we add some new helper functions such as the one to guide the missile towards a target. So let's have a quick look at the implementation. You might notice, we use the same data tables that we used in the Aircraft class to store data. Projectile::Projectile(Type type, const TextureHolder& textures): Entity(1), mType(type), mSprite(textures.get(Table[type].texture)){centerOrigin(mSprite);} The constructor simply creates a sprite with the texture we want for the projectile. We will check out the guide function when we actually implement the behavior of missiles. The rest of the functions don't hold anything particularly interesting. Draw the sprite and return a category for the commands and other data needed. To get an overview of the class hierarchy in the scene graph, here is an inheritance diagram of the current scene node types. The data table structures which are directly related to their corresponding entities are shown at the bottom of the following diagram: Firing bullets and missiles So let's try and shoot some bullets in the game. We start with adding two new actions in the Player class: Fire and LaunchMissile. We define the default key bindings for these to be the Space bar and M keys. Player::Player(){// Set initial key bindingsmKeyBinding[sf::Keyboard::Left] = MoveLeft;mKeyBinding[sf::Keyboard::Right] = MoveRight;mKeyBinding[sf::Keyboard::Up] = MoveUp;mKeyBinding[sf::Keyboard::Down] = MoveDown;mKeyBinding[sf::Keyboard::Space] = Fire;mKeyBinding[sf::Keyboard::M] = LaunchMissile;// ...}void Player::initializeActions(){// ...mActionBinding[Fire].action = derivedAction<Aircraft>(std::bind(&Aircraft::fire, _1));mActionBinding[LaunchMissile].action =derivedAction<Aircraft>(std::bind(&Aircraft::launchMissile, _1));} So when we press the keys bound to those two actions, a command will be fired which calls the aircraft's fire() and launchMissile() functions. However, we cannot put the actual code that fires the bullet or missile in those two functions. The reason is, because if we could, we would have no concept of how much time has elapsed. We don't want to fire a projectile for every frame. We want there to be some cool down until the next time we fire a bullet, to accomplish that we need to use the delta time passed in the aircraft's update() function. Instead, we mark what we want to fire by setting the Boolean flags mIsFiring or mIsLaunchingMissile to true in the Aircraft::fire() and the Aircraft::launchMissile() functions, respectively. Then we perform the actual logic in the update() function using commands. In order to make the code clearer to read, we have extracted it to its own function. void Aircraft::checkProjectileLaunch(sf::Time dt, CommandQueue&commands){if (mIsFiring && mFireCountdown <= sf::Time::Zero){commands.push(mFireCommand);mFireCountdown += sf::seconds(1.f / (mFireRateLevel+1));mIsFiring = false;}else if (mFireCountdown > sf::Time::Zero){mFireCountdown -= dt;}if (mIsLaunchingMissile){commands.push(mMissileCommand);mIsLaunchingMissile = false;}} We have a cool down for the bullets. When enough time has elapsed since the last bullet was fired, we can fire another bullet. The actual creation of the bullet is done using a command which we will look at later. After we spawn the bullet, we reset the countdown. Here, we use += instead of =; with a simple assignment, we would discard a little time remainder in each frame, generating a bigger error as time goes by. The time of the countdown is calculated using a member variable mFireCountdown in Aircraft. Like that, we can improve the aircraft's fire rate easily. So if the fire rate level is one, then we can fire a bullet every half a second, increase it to level two, and we get every third of a second. We also have to remember to keep ticking down the countdown member, even if the user is not trying to fire. Otherwise, the countdown would get stuck when the user released the Space bar. Next is the missile launch. We don't need a countdown here, because in the Player class, we made the input an event-based (not real-time based) input. bool Player::isRealtimeAction(Action action){switch (action){case MoveLeft:case MoveRight:case MoveDown:case MoveUp:case Fire:return true;default:return false;}} Since the switch statement does not identify LaunchMissile as a real-time input, the user has to release the M key before he can shoot another missile. The user wants to save his missiles for the moment he needs them. So, let's look at the commands that we perform, in order to actually shoot the projectiles. We define them in the constructor in order to have access to the texture holder. This shows one of the strengths of lambda expressions in C++11. Aircraft::Aircraft(Type type, const TextureHolder& textures){mFireCommand.category = Category::SceneAirLayer;mFireCommand.action =[this, &textures] (SceneNode& node, sf::Time){createBullets(node, textures);};mMissileCommand.category = Category::SceneAirLayer;mMissileCommand.action =[this, &textures] (SceneNode& node, sf::Time){createProjectile(node, Projectile::Missile, 0.f, 0.5f,textures);};} Now, we can pass the texture holder to the projectiles without any extra difficulty, and we don't even have to keep an explicit reference to the resources. This makes the Aircraft class and our code a lot simpler, since the reference does not need to exist in the update() function. The commands are sent to the air layer in the scene graph. This is the node where we want to create our projectiles. The missile is a bit simpler to create than bullets, that's why we call directly Aircraft::createProjectile(). So how do we create bullets then? void Aircraft::createBullets(SceneNode& node, const TextureHolder&textures) const{Projectile::Type type = isAllied()? Projectile::AlliedBullet : Projectile::EnemyBullet;switch (mSpreadLevel){case 1:createProjectile(node, type, 0.0f, 0.5f, textures);break;case 2:createProjectile(node, type, -0.33f, 0.33f, textures);createProjectile(node, type, +0.33f, 0.33f, textures);break;case 3:createProjectile(node, type, -0.5f, 0.33f, textures);createProjectile(node, type, 0.0f, 0.5f, textures);createProjectile(node, type, +0.5f, 0.33f, textures);break;}} For projectiles, we provide different levels of fire spread in order to make the game more interesting. The player can feel that progress is made, and that his aircraft becomes more powerful as he is playing. The function calls createProjectile() just as it was done for the missile. So how do we actually create the projectile and attach it to the scene graph? void Aircraft::createProjectile(SceneNode& node,Projectile::Type type, float xOffset, float yOffset,const TextureHolder& textures) const{std::unique_ptr<Projectile> projectile(new Projectile(type, textures));sf::Vector2f offset(xOffset * mSprite.getGlobalBounds().width,yOffset * mSprite.getGlobalBounds().height);sf::Vector2f velocity(0, projectile->getMaxSpeed());float sign = isAllied() ? -1.f : +1.f;projectile->setPosition(getWorldPosition() + offset * sign);projectile->setVelocity(velocity * sign);node.attachChild(std::move(projectile));} We create the projectile with an offset from the player and a velocity required by the projectile type. Also, depending on if this projectile is shot by an enemy or the player, we will have different directions. We do not want the enemy bullets to go upwards like the player's bullets or the other way around. Implementing gunfire for enemies is now a tiny step; instead of calling fire() when keys are pressed, we just call it always. We do this by adding the following code to the beginning of the checkProjectileLaunch() function: if (!isAllied())fire(); Now we have bullets that fly and split the sky. Homing missiles What would a modern aircraft be if it hadn't got an arsenal of homing missiles? This is where we start to add intelligence to our missiles; they should be capable of seeking enemies autonomously. Let's first look at what we need to implement on the projectile site. For homing missiles, the functions guideTowards() and isGuided(), as well as the variable mTargetDirection are important. Their implementation looks as follows: bool Projectile::isGuided() const{return mType == Missile;}void Projectile::guideTowards(sf::Vector2f position){assert(isGuided());mTargetDirection = unitVector(position - getWorldPosition());} The function unitVector() is a helper we have written. It divides a vector by its length, thus, always returns a vector of length one. The target direction is therefore a unit vector headed towards the target. In the function updateCurrent(), we steer our missile. We change the current missile's velocity by adding small contributions of the target direction vector to it. By doing so, the velocity vector continuously approaches the target direction, having the effect that the missile flies along a curve towards the target. approachRate is a constant that determines, to what extent the target direction contributes to the velocity. newVelocity, which is the weighted sum of the two vectors, is scaled to the maximum speed of the missile. It is assigned to the missile's velocity, and its angle is assigned to the missile's rotation. We use +90 here, because the missile texture points upwards (instead of right). void Projectile::updateCurrent(sf::Time dt,CommandQueue& commands){if (isGuided()){const float approachRate = 200.f;sf::Vector2f newVelocity = unitVector(approachRate* dt.asSeconds() * mTargetDirection + getVelocity());newVelocity *= getMaxSpeed();float angle = std::atan2(newVelocity.y, newVelocity.x);setRotation(toDegree(angle) + 90.f);setVelocity(newVelocity);}Entity::updateCurrent(dt, commands);} Note that there are many possibilities to guide a missile. Steering behaviors define a whole field of AI; they incorporate advanced mechanisms such as evasion, interception, and group behavior. Don't hesitate to search on the internet if you're interested. Now, we have guided the missile to a certain position, but how to retrieve that position? We want our missile to pursuit the closest enemy. For this, we switch from Projectile to the World class, where we write a new function. First, we store all currently active (that is, already spawned and not yet destroyed) enemies in the member variable mActiveEnemies. With the command facility, this task is almost trivial: void World::guideMissiles(){Command enemyCollector;enemyCollector.category = Category::EnemyAircraft;enemyCollector.action = derivedAction<Aircraft>([this] (Aircraft& enemy, sf::Time){if (!enemy.isDestroyed())mActiveEnemies.push_back(&enemy);}); Next, we have to find the nearest enemy for each missile. We set up another command, now for projectiles, that iterates through the active enemies to find the closest one. Here, distance() is a helper function that returns the distance between the centers of two scene nodes. Command missileGuider;missileGuider.category = Category::AlliedProjectile;missileGuider.action = derivedAction<Projectile>([this] (Projectile& missile, sf::Time){// Ignore unguided bulletsif (!missile.isGuided())return;float minDistance = std::numeric_limits<float>::max();Aircraft* closestEnemy = nullptr;FOREACH(Aircraft* enemy, mActiveEnemies){float enemyDistance = distance(missile, *enemy);if (enemyDistance < minDistance){closestEnemy = enemy;minDistance = enemyDistance;}} In case we found a closest enemy, we let the missile chase it. if (closestEnemy)missile.guideTowards(closestEnemy->getWorldPosition());}); After defining the second command, we push both to our queue, and reset the container of active enemies. Remember that the commands are not yet executed, they wait in the queue until they are invoked on the scene graph in World::update(). mCommandQueue.push(enemyCollector);mCommandQueue.push(missileGuider);mActiveEnemies.clear();} That's it, now we are able to fire and forget! The result looks as follows: Picking up some goodies Now we have implemented enemies and projectiles. But even if the player shot enemy airplanes down, and had exciting battles, he wouldn't remark that his success changes anything. You want to give the player the feeling that he is progressing in the game. Usual for this game genre are power-ups that the enemies drop when they are killed. So let's go ahead and implement that in our game. Now this is the same story as with the projectile. Most of the things we need have already been implemented; therefore, this will be quite easy to add. What we want is only an entity that, when the player touches it, applies an effect to the player and disappears. Not much work with our current framework. class Pickup : public Entity{public:enum Type{HealthRefill,MissileRefill,FireSpread,FireRate,TypeCount};public:Pickup(Type type,const TextureHolder& textures);virtual unsigned int getCategory() const;virtual sf::FloatRect getBoundingRect() const;void apply(Aircraft& player) const;protected:virtual void drawCurrent(sf::RenderTarget& target,sf::RenderStates states) const;private:Type mType;sf::Sprite mSprite;}; So, let's start looking at a few interesting parts. As usual, we have a data table, create a sprite and center it, so the constructor looks just as you would expect it. Let's investigate the apply() function, and how the data table is created. In apply(), a function object stored in the table is invoked with player as argument. The initializePickupData() function initializes the function objects, using std::bind() that redirects to the Aircraft member functions. void Pickup::apply(Aircraft& player) const{Table[mType].action(player);}std::vector<PickupData> initializePickupData(){std::vector<PickupData> data(Pickup::TypeCount);data[Pickup::HealthRefill].texture = Textures::HealthRefill;data[Pickup::HealthRefill].action= std::bind(&Aircraft::repair, _1, 25);data[Pickup::MissileRefill].texture = Textures::MissileRefill;data[Pickup::MissileRefill].action= std::bind(&Aircraft::collectMissiles, _1, 3);data[Pickup::FireSpread].texture = Textures::FireSpread;data[Pickup::FireSpread].action= std::bind(&Aircraft::increaseSpread, _1);data[Pickup::FireRate].texture = Textures::FireRate;data[Pickup::FireRate].action= std::bind(&Aircraft::increaseFireRate, _1);return data;} The pickups call already defined functions on the player aircraft that let us modify its state. These functions may repair it, refill it with missiles, or improve its firepower. It's nice when things just work out of the box. That's how the scene looks when two pickups (health and fire rate) are floating in the air. You may notice that the player's Eagle plane shoots two bullets at once, which is the result of a previously collected fire spread pickup.
Read more
  • 0
  • 0
  • 7272

article-image-creating-our-first-game
Packt
31 May 2013
11 min read
Save for later

Creating Our First Game

Packt
31 May 2013
11 min read
(For more resources related to this topic, see here.) Let's get serious – the game The game we will implement now is inspired by Frogger. In this old school arcade game, you played the role of a frog trying to cross the screen by jumping on logs and avoiding cars. In our version, the player is a developer who has to cross the network cable by jumping packets and then cross the browser "road" by avoiding bugs. To sum up, the game specifications are as follows: If the player presses the up arrow key once, the "frog" will go forward one step. By pressing the right and left arrow key, the player can move horizontally. In the first part (the network cable) the player has to jump on packets coming from the left of the screen and moving to the right. The packets are organized in lines where packets of each line travel at different speeds. Once the player is on a packet, he/she will move along with it. If a packet drives the player outside of the screen, or if the player jumps on the cable without reaching a packet, he/she will die and start at the beginning of the same level once again. In the second part (the browser part) the player has to cross the browser screen by avoiding the bugs coming from the left. If the player gets hit by a bug he/she will start at the beginning of the same level once again. These are very simple rules, but as you will see they will already give us plenty of things to think about. Learning the basics Throughout this article, we will use DOM elements to render game elements. Another popular solution would be to use the Canvas element. There are plus and minus points for both technologies and there are a few effects that are simply not possible to produce with only DOM elements. However, for the beginner, the DOM offers the advantage of being easier to debug, to work on almost all existing browsers (yes, even on Internet Explorer 6), and in most cases to offer reasonable speed for games. The DOM also abstracts the dirty business of having to target individual pixels and tracking which part of the screen has to be redrawn. Even though Internet Explorer supports most of the features we will see in this book, I would not recommend creating a game that supports it. Indeed, its market share is negligible nowadays (http://www.ie6countdown.com/) and you will encounter some performance issues. Now from some game terminology, sprites are the moving part of a game. They may be animated or nonanimated (in the sense of changing their aspect versus simply moving around). Other parts of the game may include the background, the UI, and tiles. Framework During this article, we will write some code; part of the code belongs to an example game and is used to describe scenes or logic that are specific to it. Some code, however, is very likely to be reused in each of your games. For this reason, we will regroup some of those functions into a framework that we will cleverly call gameFramework or gf in short. A very simple way to define a namespace in JavaScript is to create an object and add all your function directly to it. The following code gives you an example of what this might look like for two functions, shake and stir, in the namespace cocktail. // define the namespacevar cocktail = {};// add the function shake to the namespacecocktail.shake = function(){...}// add the function stir to the namespacecocktail.stir = function(){...} This has the advantage of avoiding collision with other libraries that use similar names for their objects or functions. Therefore, from now on when you see any function added to the namespace, it will mean that we think those functions will be used by the other games we will create later in this article or that you might want to create yourself. The following code is another notation for namespace. Which one you use is a personal preference and you should really use the one that feels right to you! var cocktail = {// add the function shake to the namespaceshake: function(){...},// add the function stir to the namespacestir: function(){...}}; Typically, you would keep the code of the framework in a JS file (let's say gameFramework.js) and the code of the game in another JS file. Once your game is ready to be published, you may want to regroup all your JavaScript code into one file (including jQuery if you wish so) and minimize it. However, for the whole development phase it will be way more convenient to keep them separate. Sprites Sprites are the basic building blocks of your game. They are basically images that can be animated and moved around the screen. To create them you can use any image editor. If you work on OS X, there is a free one that I find has been particularly well done, Pixen (http://pixenapp.com/). You can use animated gifs. With this method you have no way to access the index of the current frame through JavaScript, and no control over when the animation starts to play or when it ends. Furthermore, having many animated GIFs tends to slow things down a lot. You can change the source of the image. This is already a better solution, but provides worse performance if proposed and requires a large number of individual images. Another disadvantage is that you cannot choose to display only one part of the image; you have to show the entire image each time. Finally, if you want to have a sprite made of a repeating image, you will have to use many img elements. For the sake of completeness, we should mention here one advantage of img; it's really easy to scale an img element—just adjust the width and height. The proposed solution uses simple divs of defined dimensions and sets an image in the background. To generate animated sprites, you could change the background image, but instead we use the background position CSS property. The image used in this situation is called a sprite sheet and typically looks something like the following screenshot: The mechanism by which the animation is generated is shown in the following screenshot: Another advantage is that you can use a single sprite sheet to hold multiple animations. This way you will avoid having to load many different images. Depending on the situation, you may still want to use more than one sprite sheet, but it's a good thing to try to minimize their number. Implementing animations It's very simple to implement this solution. We will use .css() to change the background properties and a simple setInterval to change the current frame of the animation. Therefore, let's say that we have a sprite sheet containing 4 frames of a walk cycle where each frame measures 64 by 64 pixels. First, we simply have to create a div with the sprite sheet as its background. This div should measure 64 by 64 pixels, otherwise the next frame would leak onto the current one. In the following example, we add the sprite to a div with the ID mygame. $("#mygame").append("<div id='sprite1'>"); $("#sprite1").css("backgroundImage","url('spritesheet1.png')"); As the background image is by default aligned with the upper-left corner of the div, we will only see the first frame of the walk-cycle sprite sheet. What we want is to be able to change what frame is visible. The following function changes the background position to the correct position based on the argument passed to it. Take a look at the following code for the exact meaning of the arguments: /** * This function sets the current frame. * -divId: the Id of the div from which you want to change the * frame * -frameNumber: the frame number * -frameDimension: the width of a frame **/ gameFramework.setFrame = function(divId,frameNumber, frameDimension) { $("#"+divId) .css("bakgroundPosition", "" + frameNumber * frameDimension + "px 0px"); } Now we have to call this at regular intervals to produce the animation. We will use setInterval with an interval of 60 milliseconds, that is, around 17 frames per second. This should be enough to give the impression of walking; however, this really has to be fine-tuned to match your sprite sheet. To do this we use an anonymous function that we pass to setInterval, which will in turn call our function with the correct parameter. var totalNumberOfFrame = 4;var frameNumber = 0;setInterval(function(){gameFramework.setFrame("sprite1",frameNumber, 64);frameNumber = (frameNumber + 1) % totalNumberOfFrame;}, 60); You probably noticed that we're doing something special to compute the current frame. The goal is to cover values from 0 to 3 (as they're 4 frames) and to loop back to 0 when we reach 4. The operation we use for this is called modulo (%) and it's the rest of the integer division (also known as Euclidean division). For example, at the third frame we have 3 / 4 which is equal to 0 plus a remainder of 3, so 3 % 4 = 3. When the frame number reaches 4 we have 4 / 4 = 1 plus a remainder of 0, so 4 % 4 = 0. This mechanism is used in a lot of situations. Adding animations to our framework As you can see there are more and more variables needed to generate an animation: the URL of the image, the number of frames, their dimension, the rate of the animation, and the current frame. Furthermore, all those variables are associated with one animation, so if we need a second one we have to define twice as many variables. The obvious solution is to use objects. We will create an animation object that will hold all the variables we need (for now, it won't need any method). This object, like all the things belonging to our framework, will be in the gameFramework namespace. Instead of giving all the values of each of the properties of the animation as an argument, we will use a single object literal, and all the properties that aren't defined will default to some well-thought-out values. To do this, jQuery offers a very convenient method: $.extend. This is a very powerful method and you should really take a look at the API documentation (http://api.jquery.com/) to see everything that it can do. Here we will pass to it three arguments: the first one will be extended with the values of the second one and the resulting object will be extended with the values of the third. /*** Animation Object.**/gf.animation = function(options) {var defaultValues = {url : false,width : 64,numberOfFrames : 1,currentFrame : 0,rate : 30};$.extend(this, defaultValues, options);} To use this function we will simply create a new instance of it with the desired values. Here you can see the values used in the preceding examples: var firstAnim = new gameFramework.animation({url: "spritesheet1.png",numberOfFrames: 4,rate: 60}); As you can see, we didn't need to specify width: 64 because it's the default value! This pattern is very convenient and you should keep it in mind each time you need default values and also the flexibility to override them. We can rewrite the function to use the animation object: gf.setFrame = function(divId, animation) {$("#" + divId).css("bakgroundPosition", "" + animation.currentFrame *animation.width + "px 0px");} Now we will create a function for our framework based on the technique we've already seen, but this time it will use the new animation object. This function will start animating a sprite, either once or in a loop. There is one thing we have to be careful about—if we define an animation for a sprite that is already animated we need to deactivate the current animation and replace it with the new one. To do this we will need an array to hold the list of all intervals' handles. Then we'll only need to check if one exists for this sprite and clear it, then define it again. gf.animationHandles = {}; /** * Sets the animation for the given sprite. **/ gf.setAnimation = function(divId, animation, loop){ if(gf.animationHandles[divId]){ clearInterval(gf.animationHandles[divId]); } if(animation.url){ $("#"+divId).css("backgroundImage","url('"+animation. url+"')"); } if(animation.numberOfFrame > 1){ gf.animationHandles[divId] = setInterval(function(){ animation.currentFrame++; if(!loop && currentFrame > animation.numberOfFrame){ clearInterval(gf.animationHandles[divId]); gf.animationHandles[divId] = false; } else { animation.currentFrame %= animation. numberOfFrame; gf.setFrame(divId, animation); } }, animation.rate); } } This will provide a convenient, flexible, and quite high-level way to set an animation for a sprite.
Read more
  • 0
  • 0
  • 7229

article-image-creating-cool-content
Packt
23 Apr 2015
26 min read
Save for later

Creating Cool Content

Packt
23 Apr 2015
26 min read
In this article by Alex Ogorek, author of the book Mastering Cocos2d Game Development you'll be learning how to implement the really complex, subtle game mechanics that not many developers do. This is what separates the good games from the great games. There will be many examples, tutorials, and code snippets in this article intended for adaption in your own projects, so feel free to come back at any time to look at something you may have either missed the first time, or are just curious to know about in general. In this article, we will cover the following topics: Adding a table for scores Adding subtle sliding to the units Creating movements on a Bézier curve instead of straight paths (For more resources related to this topic, see here.) Adding a table for scores Because "we want a way to show the user their past high scores, in the GameOver scene, we're going to add a table that displays the most recent high scores that are saved. For this, we're going to use CCTableView. It's still relatively new, but it works for what we're going to use it. CCTableView versus UITableView Although UITableView might be known to some of you who've made non-Cocos2d apps before, you "should be aware of its downfalls when it comes to using it within Cocos2d. For example, if you want a BMFont in your table, you can't add LabelBMFont (you could try to convert the BMFont into a TTF font and use that within the table, but that's outside the scope of this book). If you still wish to use a UITableView object (or any UIKit element for that matter), you can create the object like normal, and add it to the scene, like this (tblScores is the name of the UITableView object): [[[CCDirector sharedDirector] view] addSubview:tblScores]; Saving high scores (NSUserDefaults) Before "we display any high scores, we have to make sure we save them. "The easiest way to do this is by making use of Apple's built-in data preservation tool—NSUserDefaults. If you've never used it before, let me tell you that it's basically a dictionary with "save" mechanics that stores the values in the device so that the next time the user loads the device, the values are available for the app. Also, because there are three different values we're tracking for each gameplay, let's only say a given game is better than another game when the total score is greater. Therefore, let's create a saveHighScore method that will go through all the total scores in our saved list and see whether the current total score is greater than any of the saved scores. If so, it will insert itself and bump the rest down. In MainScene.m, add the following method: -(NSInteger)saveHighScore { //save top 20 scores //an array of Dictionaries... //keys in each dictionary: // [DictTotalScore] // [DictTurnsSurvived] // [DictUnitsKilled]   //read the array of high scores saved on the user's device NSMutableArray *arrScores = [[[NSUserDefaults standardUserDefaults] arrayForKey:DataHighScores] mutableCopy]; //sentinel value of -1 (in other words, if a high score was not found on this play through) NSInteger index = -1; //loop through the scores in the array for (NSDictionary *dictHighScore in arrScores) { //if the current game's total score is greater than the score stored in the current index of the array...    if (numTotalScore > [dictHighScore[DictTotalScore] integerValue])    { //then store that index and break out of the loop      index = [arrScores indexOfObject:dictHighScore];      break;    } } //if a new high score was found if (index > -1) { //create a dictionary to store the score, turns survived, and units killed    NSDictionary *newHighScore = @{ DictTotalScore : @(numTotalScore),    DictTurnsSurvived : @(numTurnSurvived),    DictUnitsKilled : @(numUnitsKilled) };    //then insert that dictionary into the array of high scores    [arrScores insertObject:newHighScore atIndex:index];    //remove the very last object in the high score list (in other words, limit the number of scores)    [arrScores removeLastObject];    //then save the array    [[NSUserDefaults standardUserDefaults] setObject:arrScores forKey:DataHighScores];    [[NSUserDefaults standardUserDefaults] synchronize]; }   //finally return the index of the high score (whether it's -1 or an actual value within the array) return index; } Finally, call "this method in the endGame method right before you transition to the next scene: -(void)endGame { //call the method here to save the high score, then grab the index of the high score within the array NSInteger hsIndex = [self saveHighScore]; NSDictionary *scoreData = @{ DictTotalScore : @(numTotalScore), DictTurnsSurvived : @(numTurnSurvived), DictUnitsKilled : @(numUnitsKilled), DictHighScoreIndex : @(hsIndex)}; [[CCDirector sharedDirector] replaceScene:[GameOverScene sceneWithScoreData:scoreData]]; } Now that we have our high scores being saved, let's create the table to display them. Creating the table It's "really simple to set up a CCTableView object. All we need to do is modify the contentSize object, and then put in a few methods that handle the size and content of each cell. So first, open the GameOverScene.h file and set the scene as a data source for the CCTableView: @interface GameOverScene : CCScene <CCTableViewDataSource> Then, in the initWithScoreData method, create the header labels as well as initialize the CCTableView: //get the high score array from the user's device arrScores = [[NSUserDefaults standardUserDefaults] arrayForKey:DataHighScores];    //create labels CCLabelBMFont *lblTableTotalScore = [CCLabelBMFont labelWithString:@"Total Score:" fntFile:@"bmFont.fnt"];   CCLabelBMFont *lblTableUnitsKilled = [CCLabelBMFont labelWithString:@"Units Killed:" fntFile:@"bmFont.fnt"];   CCLabelBMFont *lblTableTurnsSurvived = [CCLabelBMFont labelWithString:@"Turns Survived:" fntFile:@"bmFont.fnt"]; //position the labels lblTableTotalScore.position = ccp(winSize.width * 0.5, winSize.height * 0.85); lblTableUnitsKilled.position = ccp(winSize.width * 0.675, winSize.height * 0.85); lblTableTurnsSurvived.position = ccp(winSize.width * 0.875, winSize.height * 0.85); //add the labels to the scene [self addChild:lblTableTurnsSurvived]; [self addChild:lblTableTotalScore]; [self addChild:lblTableUnitsKilled]; //create the tableview and add it to the scene CCTableView * tblScores = [CCTableView node]; tblScores.contentSize = CGSizeMake(0.6, 0.4); CGFloat ratioX = (1.0 - tblScores.contentSize.width) * 0.75; CGFloat ratioY = (1.0 - tblScores.contentSize.height) / 2; tblScores.position = ccp(winSize.width * ratioX, winSize.height * ratioY); tblScores.dataSource = self; tblScores.block = ^(CCTableView *table){    //if the press a cell, do something here.    //NSLog(@"Cell %ld", (long)table.selectedRow); }; [self addChild: tblScores]; With the CCTableView object's data source being set to self we can add the three methods that will determine exactly how our table looks and what data goes in each cell (that is, row). Note that if we don't set the data source, the table view's method will not be called; and if we set it to anything other than self, the methods will be called on that object/class instead. That being" said, add these three methods: -(CCTableViewCell*)tableView:(CCTableView *)tableView nodeForRowAtIndex:(NSUInteger)index { CCTableViewCell* cell = [CCTableViewCell node]; cell.contentSizeType = CCSizeTypeMake(CCSizeUnitNormalized, CCSizeUnitPoints); cell.contentSize = CGSizeMake(1, 40); // Color every other row differently CCNodeColor* bg; if (index % 2 != 0) bg = [CCNodeColor nodeWithColor:[CCColor colorWithRed:0 green:0 blue:0 alpha:0.3]]; else bg = [CCNodeColor nodeWithColor: [CCColor colorWithRed:0 green:0 blue:0 alpha:0.2]]; bg.userInteractionEnabled = NO; bg.contentSizeType = CCSizeTypeNormalized; bg.contentSize = CGSizeMake(1, 1); [cell addChild:bg]; return cell; }   -(NSUInteger)tableViewNumberOfRows:(CCTableView *)tableView { return [arrScores count]; }   -(float)tableView:(CCTableView *)tableView heightForRowAtIndex:(NSUInteger)index { return 40.f; } The first method, tableView:nodeForRowAtIndex:, will format each cell "based on which index it is. For now, we're going to color each cell in one of two different colors. The second method, tableViewNumberOfRows:, returns the number of rows, "or cells, that will be in the table view. Since we know there are going to be 20, "we can technically type 20, but what if we decide to change that number later? "So, let's stick with using the count of the array. The third "method, tableView:heightForRowAtIndex:, is meant to return the height of the row, or cell, at the given index. Since we aren't doing anything different with any cell in particular, we can hardcode this value to a fairly reasonable height of 40. At this point, you should be able to run the game, and when you lose, you'll be taken to the game over screen with the labels across the top as well as a table that scrolls on the right side of the screen. It's good practice when learning Cocos2d to just mess around with stuff to see what sort of effects you can make. For example, you could try using some ScaleTo actions to scale the text up from 0, or use a MoveTo action to slide it from the bottom or the side. Feel free to see whether you can create a cool way to display the text right now. Now that we have the table in place, let's get the data displayed, shall we? Showing the scores Now that "we have our table created, it's a simple addition to our code to get the proper numbers to display correctly. In the nodeForRowAtIndex method, add the following block of code right after adding the background color to the cell: //Create the 4 labels that will be used within the cell (row). CCLabelBMFont *lblScoreNumber = [CCLabelBMFont labelWithString: [NSString stringWithFormat:@"%d)", index+1] fntFile:@"bmFont.fnt"]; //Set the anchor point to the middle-right (default middle-middle) lblScoreNumber.anchorPoint = ccp(1,0.5); CCLabelBMFont *lblTotalScore = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"%d", [arrScores[index][DictTotalScore] integerValue]] fntFile:@"bmFont.fnt"];   CCLabelBMFont *lblUnitsKilled = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"%d", [arrScores[index][DictUnitsKilled] integerValue]] fntFile:@"bmFont.fnt"];   CCLabelBMFont *lblTurnsSurvived = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"%d", [arrScores[index][DictTurnsSurvived] integerValue]] fntFile:@"bmFont.fnt"]; //set the position type of each label to normalized (where (0,0) is the bottom left of its parent and (1,1) is the top right of its parent) lblScoreNumber.positionType = lblTotalScore.positionType = lblUnitsKilled.positionType = lblTurnsSurvived.positionType = CCPositionTypeNormalized;   //position all of the labels within the cell lblScoreNumber.position = ccp(0.15,0.5); lblTotalScore.position = ccp(0.35,0.5); lblUnitsKilled.position = ccp(0.6,0.5); lblTurnsSurvived.position = ccp(0.9,0.5); //if the index we're iterating through is the same index as our High Score index... if (index == highScoreIndex) { //then set the color of all the labels to a golden color    lblScoreNumber.color =    lblTotalScore.color =    lblUnitsKilled.color =    lblTurnsSurvived.color = [CCColor colorWithRed:1 green:183/255.f blue:0]; } //add all of the labels to the individual cell [cell addChild:lblScoreNumber]; [cell addChild:lblTurnsSurvived]; [cell addChild:lblTotalScore]; [cell addChild:lblUnitsKilled]; And that's it! When you play the game and end up at the game over screen, you'll see the high scores being displayed (even the scores from earlier attempts, because they were saved, remember?). Notice the high score that is yellow. It's an indication that the score you got in the game you just played is on the scoreboard, and shows you where it is. Although the CCTableView might feel a bit weird with things disappearing and reappearing as you scroll, let's get some Threes!—like sliding into our game. If you're considering adding a CCTableView to your own project, the key takeaway here is to make sure you modify the contentSize and position properly. By default, the contentSize is a normalized CGSize, so from 0 to 1, and the anchor point is (0,0). Plus, make sure you perform these two steps: Set the data source of the table view Add the three table view methods With all that in mind, it should be relatively easy to implement a CCTableView. Adding subtle sliding to the units If you've ever played Threes! (or if you haven't, check out the trailer at http://asherv.com/threes/, and maybe even download the game on your phone), you would be aware of the sliding feature when a user begins to make "their move but hasn't yet completed the move. At the speed of the dragging finger, the units slide in the direction they're going to move, showing the user where each unit will go and how each unit will combine with another. This is useful as it not only adds that extra layer of "cool factor" but also provides a preview of the future for the user if they want to revert their decision ahead of time and make a different, more calculated move. Here's a side note: if you want your game to go really viral, you have to make the user believe it was their fault that they lost, and not your "stupid game mechanics" (as some players might say). Think Angry Birds, Smash Hit, Crossy Road, Threes!, Tiny Wings… the list goes on and on with more games that became popular, and all had one underlying theme: when the user loses, it was entirely in their control to win or lose, and they made the wrong move. This" unseen mechanic pushes players to play again with a better strategy in mind. And this is exactly why we want our users to see their move before it gets made. It's a win-win situation for both the developers and the players. Sliding one unit If we can get one unit to slide, we can surely get the rest of the units to slide by simply looping through them, modularizing the code, or some other form of generalization. That being said, we need to set up the Unit class so that it can detect how far "the finger has dragged. Thus, we can determine how far to move the unit. So, "open Unit.h and add the following variable. It will track the distance from the previous touch position: @property (nonatomic, assign) CGPoint previousTouchPos; Then, in the touchMoved method of Unit.m, add the following assignment to previousTouchPos. It sets the previous touch position to the touch-down position, but only after the distance is greater than 20 units: if (!self.isBeingDragged && ccpDistance(touchPos, self.touchDownPos) > 20) { self.isBeingDragged = YES; //add it here: self.previousTouchPos = self.touchDownPos; Once that's in place, we can begin calculating the distance while the finger is being dragged. To do that, we'll do a simple check. Add the following block of code at the end of touchMoved, after the end of the initial if block: //only if the unit is currently being dragged if (self.isBeingDragged) {    CGFloat dist = 0;    //if the direction the unit is being dragged is either UP or "     DOWN    if (self.dragDirection == DirUp || self.dragDirection == DirDown)    //then subtract the current touch position's Y-value from the "     previously-recorded Y-value to determine the distance to "     move      dist = touchPos.y - self.previousTouchPos.y;      //else if the direction the unit is being dragged is either "       LEFT or RIGHT    else if (self.dragDirection == DirLeft ||        self.dragDirection == DirRight)        //then subtract the current touch position's Y-value from "         the previously-recorded Y-value to determine the "         distance to move      dist = touchPos.x - self.previousTouchPos.x;   //then assign the touch position for the next iteration of touchMoved to work properly self.previousTouchPos = touchPos;   } The "assignment of previousTouchPos at the end will ensure that while the unit is being dragged, we continue to update the touch position so that we can determine the distance. Plus, the distance is calculated in only the direction in which the unit is being dragged (up and down are denoted by Y, and left and right are denoted by X). Now that we have the distance between finger drags being calculated, let's push "this into a function that will move our unit based on which direction it's being dragged in. So, right after you've calculated dist in the previous code block, "call the following method to move our unit based on the amount dragged: dist /= 2; //optional [self slideUnitWithDistance:dist "withDragDirection:self.dragDirection]; Dividing the distance by 2 is optional. You may think the squares are too small, and want the user to be able to see their square. So note that dividing by 2, or a larger number, will mean that for every 1 point the finger moves, the unit will move by 1/2 (or less) points. With that method call being ready, we need to implement it, so add the following method body for now. Since this method is rather complicated, it's going to be "added in parts: -(void)slideUnitWithDistance:(CGFloat)dist withDragDirection:(enum UnitDirection)dir { } The first thing "we need to do is set up a variable to calculate the new x and y positions of the unit. We'll call these newX and newY, and set them to the unit's current position: CGFloat newX = self.position.x, newY = self.position.y; Next, we want to grab the position that the unit starts at, that is, the position the "unit would be at if it was positioned at its current grid coordinate. To do that, "we're going to call the getPositionForGridCoordinate method from MainScene, (since that's where the positions are being calculated anyway, we might as well use that function): CGPoint originalPos = [MainScene "getPositionForGridCoord:self.gridPos]; Next, we're going to move the newX or newY based on the direction in which the unit is being dragged. For now, let's just add the up direction: if (self.dragDirection == DirUp) {    newY += dist;    if (newY > originalPos.y + self.gridWidth)      newY = originalPos.y + self.gridWidth;    else if (newY < originalPos.y)      newY = originalPos.y; } In this if block, we're first going to add the distance to the newY variable "(because we're going up, we're adding to Y instead of X). Then, we want to "make sure the position is at most 1 square up. We're going to use the gridWidth (which is essentially the width of the square, assigned in the initCommon method). Also, we need to make sure that if they're bringing the square back to its original position, it doesn't go into the square beneath it. So let's add the rest of the directions as else if statements: else if (self.dragDirection == DirDown) {    newY += dist;    if (newY < originalPos.y - self.gridWidth)      newY = originalPos.y - self.gridWidth;    else if (newY > originalPos.y)      newY = originalPos.y; } else if (self.dragDirection == DirLeft) {    newX += dist;    if (newX < originalPos.x - self.gridWidth)      newX = originalPos.x - self.gridWidth;    else if (newX > originalPos.x)      newX = originalPos.x; } else if (self.dragDirection == DirRight) {    newX += dist;    if (newX > originalPos.x + self.gridWidth)      newX = originalPos.x + self.gridWidth;    else if (newX < originalPos.x)      newX = originalPos.x; } Finally, we "will set the position of the unit based on the newly calculated "x and y positions: self.position = ccp(newX, newY); Running the game at this point should cause the unit you drag to slide along "with your finger. Nice, huh? Since we have a function that moves one unit, "we can very easily alter it so that every unit can be moved like this. But first, there's something you've probably noticed a while ago (or maybe just recently), and that's the unit movement being canceled only when you bring your finger back to the original touch down position. Because we're dragging the unit itself, we can "cancel" the move by dragging the unit back to where it started. However, the finger might be in a completely different position, so we need to modify how the cancelling gets determined. To do that, in your touchEnded method of Unit.m, locate this if statement: if (ccpDistance(touchPos, self.touchDownPos) > "self.boundingBox.size.width/2) Change it to the following, which will determine the unit's distance, and not the finger's distance: CGPoint oldSelfPos = [MainScene "getPositionForGridCoord:self.gridPos];   CGFloat dist = ccpDistance(oldSelfPos, self.position); if (dist > self.gridWidth/2) Yes, this means you no longer need the touchPos variable in touchEnded if "you're getting that "warning and wish to get rid of it. But that's it for sliding 1 unit. Now we're ready to slide all the units, so let's do it! Sliding all units Now "that we have the dragging unit being slid, let's continue and make all the units slide (even the enemy units so that we can better predict our troops' movement). First, we need a way to move all the units on the screen. However, since the Unit class only contains information about the individual unit (which is a good thing), "we need to call a method in MainScene, since that's where the arrays of units are. Moreover, we cannot simply call [MainScene method], since the arrays are "instance variables, and instance variables must be accessed through an instance "of the object itself. That being said, because we know that our unit will be added to the scene as "a child, we can use Cocos2d to our advantage, and call an instance method on the MainScene class via the parent parameter. So, in touchMoved of Unit.m, make the following change: [(MainScene*)self.parent slideAllUnitsWithDistance:dist "withDragDirection:self.dragDirection]; //[self slideUnitWithDistance:dist "withDragDirection:self.dragDirection]; Basically we've commented out (or deleted) the old method call here, and instead called it on our parent object (which we cast as a MainScene so that we know "which functions it has). But we don't have that method created yet, so in MainScene.h, add the following method declaration: -(void)slideAllUnitsWithDistance:(CGFloat)dist "withDragDirection:(enum UnitDirection)dir; Just in case you haven't noticed, the enum UnitDirection is declared in Unit.h, which is why MainScene.h imports Unit.h—so that we can make use of that enum in this class, and the function to be more specific. Then in MainScene.m, we're going to loop through both the friendly and enemy arrays, and call the slideUnitWithDistance function on each individual unit: -(void)slideAllUnitsWithDistance:(CGFloat)dist "withDragDirection:(enum UnitDirection)dir { for (Unit *u in arrFriendlies)    [u slideUnitWithDistance:dist withDragDirection:dir]; for (Unit *u in arrEnemies)    [u slideUnitWithDistance:dist withDragDirection:dir]; } However, that" still isn't functional, as we haven't declared that function in the "header file for the Unit class. So go ahead and do that now. Declare the function header in Unit.h: -(void)slideUnitWithDistance:(CGFloat)dist withDragDirection:(enum "UnitDirection)dir; We're almost done. We initially set up our slideUnitWithDistance method with a drag direction in mind. However, only the unit that's currently being dragged will have a drag direction. Every other unit will need to use the direction it's currently facing "(that is, the direction in which it's already going). To do that, we just need to modify how the slideUnitWithDistance method does its checking to determine which direction to modify the distance by. But first, we need to handle the negatives. What does that mean? Well, if you're dragging a unit to the left and a unit being moved is supposed to be moving to the left, it will work properly, as x-10 (for example) will still be less than the grid's width. However, if you're dragging left and a unit being moved is supposed to be moving right, it won't be moving at all, as it tries to add a negative value x -10, but because it needs to be moving to the right, it'll encounter the left-bound right away (of less than the original position), and stay still. The following diagram should help explain what is meant by "handling negatives." As you can see, in the top section, when the non-dragged unit is supposed to be going left by 10 (in other words, negative 10 in the x direction), it works. But when the non-dragged unit is going the opposite sign (in other words, positive 10 in the x direction), it doesn't. To" handle this, we set up a pretty complicated if statement. It checks when the drag direction and the unit's own direction are opposite (positive versus negative), and multiplies the distance by -1 (flips it). Add this to the top of the slideUnitWithDistance method, right after you grab the newX and the original position: -(void)slideUnitWithDistance:(CGFloat)dist withDragDirection:(enum UnitDirection)dir { CGFloat newX = self.position.x, newY = self.position.y; CGPoint originalPos = [MainScene getPositionForGridCoord:self.gridPos]; if (!self.isBeingDragged &&   (((self.direction == DirUp || self.direction == DirRight) && (dir == DirDown || dir == DirLeft)) ||   ((self.direction == DirDown || self.direction == DirLeft) && (dir == DirUp || dir == DirRight)))) {    dist *= -1; } } The logic of this if statement works is as follows: Suppose the unit is not being dragged. Also suppose that either the direction is positive and the drag direction is negative, or the direction is negative and the drag direction is positive. Then multiply by -1. Finally, as mentioned earlier, we just need to handle the non-dragged units. So, in every if statement, add an "or" portion that will check for the same direction, but only if the unit is not currently being dragged. In other words, in the slideUnitWithDistance method, modify your if statements to look like this: if (self.dragDirection == DirUp || (!self.isBeingDragged && self.direction == DirUp)) {} else if (self.dragDirection == DirDown || (!self.isBeingDragged && self.direction == DirDown)) {} else if (self.dragDirection == DirLeft || (!self.isBeingDragged && self.direction == DirLeft)) {} else if (self.dragDirection == DirRight || (!self.isBeingDragged && self.direction == DirRight)) {} Finally, we can run the game. Bam! All the units go gliding across the screen with our drag. Isn't it lovely? Now the player can better choose their move. That's it for the sliding portion. The key to unit sliding is to loop through the arrays to ensure that all the units get moved by an equal amount, hence passing the distance to the move function. Creating movements on a Bézier curve If you don't know what a Bézier curve is, it's basically a line that goes from point A to point B over a curve. Instead of being a straight line with two points, it uses a second set of points called control points that bend the line in a smooth way. When you want to apply movement with animations in Cocos2d, it's very tempting to queue up a bunch of MoveTo actions in a sequence. However, it's going to look a lot nicer ( in both the game and the code) if you use a smoother Bézier curve animation. Here's a good example of what a Bézier curve looks like: As you can see, the red line goes from point P0 to P3. However, the line is influenced in the direction of the control points, P1 and P2. Examples of using a Bézier curve Let's list" a few examples where it would be a good choice to use a Bézier curve instead of just the regular MoveTo or MoveBy actions: A character that will perform a jumping animation, for example, in Super Mario Bros A boomerang as a weapon that the player throws Launching a missile or rocket and giving it a parabolic curve A tutorial hand that indicates a curved path the user must make with their finger A skateboarder on a half-pipe ramp (if not done with Chipmunk) There are obviously a lot of other examples that could use a Bézier curve for their movement. But let's actually code one, shall we? Sample project – Bézier map route First, to make things go a lot faster—as this isn't going to be part of the book's project—simply download the project from the code repository or the website. If you open the project and run it on your device or a simulator, you will notice a blue screen and a square in the bottom-left corner. If you tap anywhere on the screen, you'll see the blue square make an M shape ending in the bottom-right corner. If you hold your finger, it will repeat. Tap again and the animation will reset. Imagine the path this square takes is over a map, and indicates what route a player will travel with their character. This is a very choppy, very sharp path. Generally, paths are curved, so let's make one that is! Here is a screenshot that shows a very straight path of the blue square: The following screenshot shows the Bézier path of the yellow square: Curved M-shape Open MainScene.h and add another CCNodeColor variable, named unitBezier: CCNodeColor *unitBezier; Then open MainScene.m and add the following code to the init method so that your yellow block shows up on the screen: unitBezier = [[CCNodeColor alloc] initWithColor:[CCColor colorWithRed:1 green:1 blue:0] width:50 height:50]; [self addChild:unitBezier]; CCNodeColor *shadow2 = [[CCNodeColor alloc] initWithColor:[CCColor blackColor] width:50 height:50]; shadow2.anchorPoint = ccp(0.5,0.5); shadow2.position = ccp(26,24); shadow2.opacity = 0.5; [unitBezier addChild:shadow2 z:-1]; Then, in the sendFirstUnit method, add the lines of code that will reset the yellow block's position as well as queue up the method to move the yellow block: -(void)sendFirstUnit { unitRegular.position = ccp(0,0); //Add these 2 lines unitBezier.position = ccp(0,0); [self scheduleOnce:@selector(sendSecondUnit) delay:2]; CCActionMoveTo *move1 = [CCActionMoveTo actionWithDuration:0.5 "position:ccp(winSize.width/4, winSize.height * 0.75)]; CCActionMoveTo *move2 = [CCActionMoveTo actionWithDuration:0.5 "position:ccp(winSize.width/2, winSize.height/4)]; CCActionMoveTo *move3 = [CCActionMoveTo actionWithDuration:0.5 "position:ccp(winSize.width*3/4, winSize.height * 0.75)]; CCActionMoveTo *move4 = [CCActionMoveTo actionWithDuration:0.5 "position:ccp(winSize.width - 50, 0)]; [unitRegular runAction:[CCActionSequence actions:move1, move2, "move3, move4, nil]]; } After this, you'll need to actually create the sendSecondUnit method, like this: -(void)sendSecondUnit { ccBezierConfig bezConfig1; bezConfig1.controlPoint_1 = ccp(0, winSize.height); bezConfig1.controlPoint_2 = ccp(winSize.width*3/8, "winSize.height); bezConfig1.endPosition = ccp(winSize.width*3/8, "winSize.height/2); CCActionBezierTo *bez1 = [CCActionBezierTo "actionWithDuration:1.0 bezier:bezConfig1]; ccBezierConfig bezConfig2; bezConfig2.controlPoint_1 = ccp(winSize.width*3/8, 0); bezConfig2.controlPoint_2 = ccp(winSize.width*5/8, 0); bezConfig2.endPosition = ccp(winSize.width*5/8, winSize.height/2); CCActionBezierBy *bez2 = [CCActionBezierTo "actionWithDuration:1.0 bezier:bezConfig2]; ccBezierConfig bezConfig3; bezConfig3.controlPoint_1 = ccp(winSize.width*5/8, "winSize.height); bezConfig3.controlPoint_2 = ccp(winSize.width, winSize.height); bezConfig3.endPosition = ccp(winSize.width - 50, 0); CCActionBezierTo *bez3 = [CCActionBezierTo "actionWithDuration:1.0 bezier:bezConfig3]; [unitBezier runAction:[CCActionSequence actions:bez1, bez2, bez3, nil]]; } The preceding method creates three Bézier configurations and attaches them to a MoveTo command that takes a Bézier configuration. The reason for this is that each Bézier configuration can take only two control points. As you can see in this marked-up screenshot, where each white and red square represents a control point, you can make only a U-shaped parabola with a single Bézier configuration. Thus, to make three U-shapes, you need three Bézier configurations. Finally, make sure that in the touchBegan method, you make the unitBezier stop all its actions (that is, stop on reset): [unitBezier stopAllActions]; And that's it! When you run the project and tap on the screen (or tap and hold), you'll see the blue square M-shape its way across, followed by the yellow square in its squiggly M-shape. If you" want to adapt the Bézier MoveTo or MoveBy actions for your own project, you should know that you can create only one U-shape with each Bézier configuration. They're fairly easy to implement and can quickly be copied and pasted, as shown in the sendSecondUnit function. Plus, as the control points and end position are just CGPoint values, they can be relative (that is, relative to the unit's current position, the world's position, or an enemy's position), and as a regular CCAction, they can be run with any CCNode object quite easily. Summary In this article, you learned how to do a variety of things, from making a score table and previewing the next move, to making use of Bézier curves. The code was built with a copy-paste mindset, so it can be adapted for any project without much reworking (if it is required at all). Resources for Article: Further resources on this subject: Cocos2d-x: Installation [article] Dragging a CCNode in Cocos2D-Swift [article] Animations in Cocos2d-x [article]
Read more
  • 0
  • 0
  • 7190
Packt
25 Mar 2015
35 min read
Save for later

Fun with Sprites – Sky Defense

Packt
25 Mar 2015
35 min read
This article is written by Roger Engelbert, the author of Cocos2d-x by Example: Beginner's Guide - Second Edition. 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 article, you will learn: How to optimize the development of your game with sprite sheets How to use bitmap fonts in your game How easy it is to implement and run actions How to scale, rotate, swing, move, and fade out a sprite How to load multiple .png files and use them to animate a sprite How to create a universal game with Cocos2d-x (For more resources related to this topic, see here.) The game – sky defense Meet our stressed-out city of...your name of choice here. It's a beautiful day when suddenly the sky begins to fall. There are meteors rushing toward the city and it is your job to keep it safe. The player in this game can tap the screen to start growing a bomb. When the bomb is big enough to be activated, 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 detonation, 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 retina screen and it will be scaled down to fit all the other screens. The game will be played in landscape mode, and it will not need to support multitouch. The start project 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 field in Deployment Info to Universal, and the Device Family field is set to Universal. And in RootViewController.mm, the supported interface orientation is set to Landscape. The game we are going to build requires only one class, GameLayer.cpp, and you will find that the interface for this class already contains all the information it needs. Also, some of the more trivial or old-news logic is already in place in the implementation file as well. But I'll go over this as we work on the game. Adding screen support for a universal app 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. 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 retina 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 retina, then for regular iPad which is shared by iPhone retina, and for the regular iPhone. We end by setting the scale factor based on the designed target. Adding background music Still inside AppDelegate.cpp, we load the sound files 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("background.mp3").c_str()); We end by setting the effects' volume down a tad: //lower playback volume for effects audioEngine->setEffectsVolume(0.4f); 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 initializes by calling three methods: createGameScreen, createPools, and createActions. We'll create all our screen elements inside the first method, and then create object pools so we don't instantiate any sprite inside the main loop; and we'll create all the main actions used in our game inside the createActions method. And as soon as the game initializes, 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 multiple images together in one image file. In order to texture a sprite with one of these images, you must have the information of where in the sprite sheet that particular image is found (its rectangle). Sprite sheets are often organized in two files: the image one and a data file that describes where in the image you can find the individual textures. I used TexturePacker to create these files for the game. You can find them inside the ipad, ipadhd, and iphone folders inside Resources. There is a sprite_sheet.png file for the image and a sprite_sheet.plist file that describes the individual frames inside the image. This is what the sprite_sheet.png file looks like: Batch drawing sprites In Cocos2d-x, sprite sheets can be used in conjunction with a specialized node, called SpriteBatchNode. This node can be used whenever you wish to use multiple sprites that share the same source image inside the same node. So you could have multiple 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 multiple instances of sprites displaying as many different textures as you could pack inside your sprite sheet. With SpriteBatchNode, you can substantially reduce the number of calls during the rendering stage of your game, which will help when targeting less powerful systems, though not noticeably in more modern devices. Let me show you how to create a SpriteBatchNode. Time for action – creating SpriteBatchNode Let's begin implementing the createGameScreen method in GameLayer.cpp. Just below the lines that add the bg sprite, we instantiate 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 first load all the frame information described by the sprite_sheet.plist file into SpriteFrameCache. And then we create the batch node with the sprite_sheet.png file, 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 putting stuff inside _gameBatchNode. 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); } 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);   } Notice 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 file. The screen so far is made up of two instances of city_dark.png tiling at the bottom of the screen, and two instances of city_light.png also tiling. 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 }; We use the addChild( Node, zOrder) method to layer our sprites on top of each other, using different 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 find in the enumerated list, which is kForeground. Why go through the trouble of tiling 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 optimize sprite sheets. This is not necessary in this particular game. What just happened? We began creating the initial 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 manipulating their z order. Bitmap fonts in Cocos2d-x The Cocos2d-x Label class has a static 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 matter 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 file (font.png), and then a data file (font.fnt) describing where each character is. The following screenshot shows how the font sprite sheet looks like for our game: The difference between Label and a regular SpriteBatchNode class is that the data file also feeds the Label object information 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 instantiated with the name of the data file and their initial string value: _scoreDisplay = Label::createWithBMFont("font.fnt", "0"); And the value for the label is changed through the setString method: _scoreDisplay->setString("1000"); Just as with every other image in the game, we also have different versions of font.fnt and font.png in our Resources folders, one for each screen definition. FileUtils will once again do the heavy lifting of finding the correct file for the correct screen. So now let's create the labels for our game. Time for action – creating bitmap font labels Creating a bitmap font is somewhat similar to creating a batch node. Continuing 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); 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 first bitmap font object in Cocos2d-x. Now let's finish creating our game's sprites. Time for action – adding the final screen sprites The last sprites we need to create are the clouds, the bomb and shockwave, and our game state messages. 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); } 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); Then create the _shockwave sprite that appears after the _bomb goes off: _shockWave = Sprite::createWithSpriteFrameName ("shockwave.png"); _shockWave->getTexture()->generateMipmap(); _shockWave->setVisible(false); _gameBatchNode->addChild(_shockWave); 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 information 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. Notice that they are also part of our batch node. Next comes the bomb sprite and our first new call: _bomb->getTexture()->generateMipmap(); With this we are telling the framework to create antialiased copies of this texture in diminishing sizes (mipmaps), since we are going to scale it down later. This is optional of course; sprites can be resized without first generating mipmaps, but if you notice loss of quality in your scaled sprites, you can fix that by creating 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. I add the sparkle and the halo sprites as children to the _bomb sprite. This will use the container characteristic of nodes to our advantage. When I grow the bomb, all its children will grow with it. Notice 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 particular child from a sprite as follows: auto halo = (Sprite *)   bomb->getChildByTag(kSpriteHalo); We now have our game screen in place: Next come object pools. Time for action – creating our object pools The pools are just vectors of objects. And here are the steps to create them: Inside the createPools method, we first 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); } 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); } 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 respective pool indices to retrieve these from the vector as needed as you'll see in a moment. But first we need to take care of actions and animations. With object pools, we reduce the number of instantiations 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. Actions in a nutshell If you remember, a node will store information about position, scale, rotation, visibility, and opacity of a node. And in Cocos2d-x, there is an Action class to change each one of these values over time, in effect animating these transformations. Actions are usually created with a static method create. The majority of these actions are time-based, so usually the first parameter you need to pass an action is the time length for the action. So for instance: auto fadeout = FadeOut::create(1.0f); This creates a fadeout action 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 flexible system that allows us to create any combination of actions and transformations to achieve any effect we desire. You may, for instance, choose to create an action sequence (Sequence) that contains more than one action; or you can apply easing effects (EaseIn, EaseOut, and so on) to your actions. You can choose to repeat an action a certain number of times (Repeat) or forever (RepeatForever); and you can add callbacks to functions you want called once an action is completed (usually inside a Sequence action). Time for action – creating actions with Cocos2d-x Creating actions with Cocos2d-x is a very simple process: Inside our createActions method, we will instantiate the actions we can use repeatedly in our game. Let's create our first actions: 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(); Actions can be combined in many different forms. Here, the retained _swingHealth action is a RepeatForever action of Sequence that will rotate the health sprite first one way, then the other, with EaseInOut wrapping the RotateTo action. RotateTo takes 1.2 seconds to rotate the sprite first 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 actions: //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(); First, another Sequence. This will fade out the sprite and call the shockwaveDone function, which is already implemented in the class and turns the _shockwave sprite invisible when called. The last one is a RepeatForever action of a RotateBy action. In half a second, the sprite running this action will rotate -90 degrees and will do that again and again. What just happened? You just got your first glimpse of how to create actions in Cocos2d-x and how the framework allows for all sorts of combinations to accomplish any effect. It may be hard at first to read through a Sequence action 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 animations. Animating a sprite in Cocos2d-x The key thing to remember is that an animation is just another type of action, one that changes the texture used by a sprite over a period of time. In order to create an animation action, you need to first create an Animation object. This object will store all the information regarding the different sprite frames you wish to use in the animation, the length of the animation in seconds, and whether it loops or not. With this Animation object, you then create a Animate action. Let's take a look. Time for action – creating animations Animations are a specialized type of action that require a few extra steps: Inside the same createActions method, add the lines for the two animations we have in the game. First, we start with the animation 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); } Then we use the Animation object inside a Animate action: 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(); The same steps are repeated to create the other explosion animation used when the player hits a meteor or a health pack. animation = Animation::create(); for(int i = 1; i <= 7; i++) { 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 action 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 animation. 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 first animation and seven for the second. The textures (or frames) are SpriteFrame objects we grab from SpriteFrameCache, which as you remember, contains all the information from the sprite_sheet.plist data file. 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 animation 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 animation is over. For example, if I have an explosion animation that will run on a meteor sprite, then by the end of the explosion animation, the sprite will revert to displaying the meteor texture. Time for the actual action. Animate receives the Animation object as its parameter. (In the first animation, we shift the position 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); } 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 final 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 actions and callbacks! So let's implement our touch events first. Time for action – handling touches Time to bring the player to our party: 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()) {      SimpleAudioEngine::getInstance()->stopAllEffects();      _gameOverMessage->setVisible(false);         }       this->resetGame();    return true; } 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 effects 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 initial values, and set _running to true. Next we handle the touches. But we only need to handle one each time 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 {      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()); } } 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(); If _bomb is the right size, we start our _shockwave. If it isn't, we play a bomb failure sound effect; there is no explosion and _shockwave is not made visible. 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 actions on it: we grow the _shockwave sprite to twice the scale the bomb was when it went off and we run a copy of _shockwaveSequence that we created earlier. Finally, if no _bomb is currently visible on screen, we create one. And we run clones of previously created actions 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. 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 first, 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. Let's write the implementation 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)); } Next, add the implementation of stopGame: void GameLayer::stopGame() {       _running = false;       //stop all actions currently running    int i;    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 actions with stopGame(). Already implemented in the class is the method that makes the game more difficult as time progresses. If you take a look at the method (increaseDifficulty) you will see that it reduces the interval between meteors and reduces the time it takes for meteors to reach the ground. All we need now is the update method to run the countdowns and check for collisions. Time for action – updating the game We already have the code that updates the countdowns inside the update. If it's time to add a meteor or a health pack we do it. If it's time to make the game more difficult to play, we do that too. It is possible to use an action for these timers: a Sequence action with a Delay action 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 time to add our main loop: 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)); } 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 multiple hits. Next we move the clouds: //move clouds for (auto sprite : _clouds) { 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); } I chose not to use a MoveTo action for the clouds to show you the amount of code that can be replaced by a simple action. If not for Cocos2d-x actions, we would have to implement logic to move, rotate, swing, scale, and explode all our sprites! And finally: if (_bomb->isVisible()) {    if (_bomb->getScale() > 0.3f) {      if (_bomb->getOpacity() != 255)        _bomb->setOpacity(255);    } } 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 pretty straightforward when you don't have to worry about updating individual sprites, as our actions take care of that for us. We pretty much only need to run collision checks between our sprites, and to determine when it's time to throw something new at the player. So now the only thing left to do is grab the meteors and health packs from the pools when their timers 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 respective vector: 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++;    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::fallingObjectDone, this, meteor) ), nullptr);   meteor->setVisible ( true ); meteor->runAction(repeatRotate); meteor->runAction(sequence); _fallingObjects.pushBack(meteor); } We grab the next available meteor from the pool, then we pick a random start and end x value for its MoveTo action. The meteor starts at the top of the screen and will move to the bottom towards the city, but the x value is randomly picked each time. We rotate the meteor inside a RepeatForever action, and we use Sequence to move the sprite to its target position and then call back fallingObjectDone when the meteor has reached its target. We finish by adding the new meteor we retrieved from the pool to the _fallingObjects vector so we can check collisions with it. The method to retrieve the health (resetHealth) sprites is pretty much the same, except that swingHealth action is used instead of rotate. You'll find that method already implemented in GameLayer.cpp. What just happened? So in resetGame we set the timers, and we update them in the update method. We use these timers to add meteors and health packs to the screen by grabbing the next available one from their respective pool, and then we proceed to run collisions between an exploding bomb and these falling objects. Notice 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 letting sprites through. When you take a look at that method inside GameLayer.cpp, you will notice 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 effect, and run the explosion animation; an autorelease copy of the _groundHit action we retained earlier, so we don't need to repeat all that logic every time we need to run this action. If the item is a health pack, we increase the energy or give the player some points, play a nice sound effect, and hide the sprite. Play the game! We've been coding like mad, and it's finally time to run the game. But first, 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(); } The actual game screen will now look something like this: Now, let's take this to Android. Time for action – running the game in Android Follow these steps to deploy the game to Android: This time, there is no need to alter the manifest because the default settings are the ones we want. So, navigate to proj.android and then to the jni folder and open the Android.mk file in a text editor. Edit the lines in LOCAL_SRC_FILES to read as follows: LOCAL_SRC_FILES := hellocpp/main.cpp \                    ../../Classes/AppDelegate.cpp \                    ../../Classes/GameLayer.cpp Follow the instructions from the HelloWorld and AirHockey examples to import the game into Eclipse. Save it and run your application. This time, you can try out different size screens if you have the devices. What just happened? You just ran a universal app in Android. And nothing could have been simpler. Summary In my opinion, after nodes and all their derived objects, actions are the second best thing about Cocos2d-x. They are time savers and can quickly spice things up in any project with professional-looking animations. And I hope with the examples found in this article, you will be able to create any action you need with Cocos2d-x. Resources for Article: Further resources on this subject: Animations in Cocos2d-x [article] Moving the Space Pod Using Touch [article] Cocos2d-x: Installation [article]
Read more
  • 0
  • 0
  • 6889

article-image-adding-bodies-world
Packt
11 Dec 2012
4 min read
Save for later

Adding Bodies to the World

Packt
11 Dec 2012
4 min read
(For more resources related on Spring, see here.) Creating a fixture A fixture is used to bind the shape on a body, and to define its material setting density, friction, and restitution. The first step is to create the fixture: var fixtureDef:b2FixtureDef = new b2FixtureDef(); fixtureDef.shape=circleShape; Once we have created the fixture with the constructor, we assign the previously created shape using the shape property. Finally we are ready to add the ball to the world: var theBall_b2Body=world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef); b2Body is the body itself: the physical, concrete body that has been created using the bodyDef attribute. To recap, use the following steps when you want to place a body in the world: i. Create a body definition, which will hold body information such as its position. ii. Create a shape, which is how the body will look. iii. Create a fixture to attach the shape to the body definition. iv. Create the body itself in the world using the fixture. Once you know the importance of each step, adding bodies to your Box2D World will be easy and fun. Back to our project. The following is how the class should look now: package {import flash.display.Sprite;import flash.events.Event;import Box2D.Dynamics.*;import Box2D.Collision.*;import Box2D.Collision.Shapes.*;import Bo x2D.Common.Math.*;public class Main extends Sprite {private var world:b2World;private var worldScale_Number=30;public function Main() {world=new b2World(new b2Vec2(0,9.81),true);var bodyDef_b2BodyDef=new b2BodyDef();bodyDef.position.Set(320/worldScale,30/worldScale);var circleShape:b2CircleShape;circleShape=new b2CircleShape(25/worldScale);var fixtureDef:b2FixtureDef = new b2FixtureDef();fixtureDef.shape=circleShape;var theBall_b2Body=world.CreateBody(bodyDef);theBall.CreateFixture(fixtureDef);addEventListener(Event.ENTER_FRAME,updateWorld);}private function updateWorld(e:Event):void {world.Step(1/30,10,10);world.ClearForces;}}} Time to save the project and test it. Ready to see your first Box2D body in action? Run the movie! Ok, it did not display anything. Before you throw this article, let me tell you that Box2D only simulates the physic world, but it does not display anything. This means your body is alive and kicking in your Box2D World; it's just that you can't see it. Creating a box shape Let's perform the following steps: First, body and fixture definitions can be reassigned to define our new body. This way, we don't need to declare another bodyDef variable, but we just need to reuse the one we used for the creation of the sphere by changing its position: bodyDef.position.Set(320/worldScale,470/worldScale); Now the body definition is located in the horizontal center, and close to the bottom of the screen. To create a polygon shape, we will use the b2PolygonShape class : var polygonShape_b2PolygonShape=new b2PolygonShape(); This way we create a polygon shape in the same way we created the circle shape earlier. Polygon shapes must follow some restrictions, but at the moment because we only need an axis-aligned box, the SetAsBox method is all we need. polygonShape.SetAsBox(320/worldScale,10/worldScale); The method requires two arguments: the half-width and the half-height of the box. In the end, our new polygon shape will have its center at pixels (320, 470), and it will have a width of 640 pixels and a height of 20 pixels—just what we need to create a fl oor. Now we change the shape attribute of the fixture definition, attaching the new polygon shape: fixtureDef.shape=polygonShape; Finally, we can create the world body and embed the fixture in it, just like we did with the sphere. var theFloor_b2Body=world.CreateBody(bodyDef); theFloor.CreateFixture(fixtureDef); The following is how your Main function should look now: public function Main() {world=new b2World(new b2Vec2(0,9.81),true);var bodyDef_b2BodyDef=new b2BodyDef();bodyDef.position.Set(320/worldScale,30/worldScale);var circleShape:b2CircleShape;circleShape=new b2CircleShape(25/worldScale);var fixtureDef_b2FixtureDef=new b2FixtureDef();fixtureDef.shape=circleShape;var theBall_b2Body=world.CreateBody(bodyDef);theBall.CreateFixture(fixtureDef);bodyDef.position.Set(320/worldScale,470/worldScale);var polygonShape_b2PolygonShape=new b2PolygonShape();polygonShape.SetAsBox(320/worldScale,10/worldScale);fixtureDef.shape=polygonShape;var theFloor_b2Body=world.CreateBody(bodyDef);theFloor.CreateFixture(fixtureDef);var debugDraw_b2DebugDraw=new b2DebugDraw();var debugSprite_Sprite=new Sprite();addChild(debugSprite);debugDraw.SetSprite(debugSprite);debugDraw.SetDrawScale(worldScale);debugDraw.SetFlags(b2DebugDraw.e_shapeBit);debugDraw.SetFillAlpha(0.5);world.SetDebugDraw(debugDraw);addEventListener(Event.ENTER_FRAME,updateWorld);} Test the movie and you'll see the floor:
Read more
  • 0
  • 0
  • 6244
Modal Close icon
Modal Close icon