-
-
Notifications
You must be signed in to change notification settings - Fork 205
Introduction to Game Dev using Axmol
Introduction: So, you want to make a game?
What makes up a game
What is the Axmol game engine?
The basics
How Axmol helps you
Building blocks: Details, details, details
Node
Scene
Scene Graph
Sprites
NOTE: This is a work in progress
Perhaps you are here because you have a great idea for a game, or you're interested in learning something about game development, or some other reason that is equally as important.
No matter what your reasons are, it helps to keep a few things in mind:
-
Have a somewhat clear goal of what it is you want to achieve; jumping in without having a destination in mind will work against you, bringing much frustration and wasted time with it.
-
Avoid overwhelming yourself trying to do everything at once. Have a big idea? Great! Now split it into smaller, more manageable, pieces. Not sure how? That's fine, hopefully by the end of this guide you will have a better idea on how to do this.
-
Avoid focusing too much on doing things the "best way"; there are a multitude of ways to get things done, some better than others, but they don't matter, at least not at the start. Just a tip: get things working in any way you can, and then when you're finally playing your game, and satisfied that it looks and plays as you want it to, then you can go back and improve the bits and pieces if you absolutely need to.
A game is made up of many individual parts, all working in sync to showcase your vision. Those parts may include the functionality such as displaying images, playing animations, playing sound effects and music, handling things bouncing into each other (physics!), and processing input from the player (keyboard, mouse, controller etc.).
In order to bring your vision to life, at least in a reasonable timeframe, you would not want to create all those parts from scratch; why re-invent the wheel when one exists to the job, and to do it well. This is where the game engine fits in, providing the necessary parts, or building blocks, that do much of the heavy lifting for you, and all you need to do as the developer is to connect the parts together, and direct them to do what you need them to. It almost seems so simple, doesn't it? It can be, but let us not get ahead of ourselves, just yet.
There may be many reasons to choose one game engine over another, but since you are reading through the Axmol documentation, we will focus on this game engine. ;-)
From the description on the front page of the repository, which you may have already come across: "Axmol Engine is an open-source, C++ multi-platform engine designed for mobile devices, desktop, and Xbox, well-suited for 2D game development. It was launched in November 2019 as a fork of Cocos2d-x v4.0."
Let's focus on the first sentence of that description, and what it means for you:
-
Open source: This engine is under the MIT license, meaning you can do almost anything with it, and there will be no royalties or payment required to use this engine in your projects. Please do read through the MIT license (don't worry, it's not a long read).
-
C++: This is the programming language the engine is written in. Since you're here, the assumption is that you are familiar with C++ to some degree. If you're not familiar with C++, that is OK, it just means you have a bit of work to do (C++ is great, really!). Projects using this engine typically use the C++ language as well, but this engine does support another language, being the LUA scripting language, if that is your preference. An understanding of C++ is always beneficial, since it will allow you to understand the deep, dark, inner workings of the engine… I joke, it's not at all gloomy in there, and while you definitely do not need to know how it all works, it will help you immensely if you do get to know what makes the game engine tick.
-
Multi-platform: Axmol supports many platforms, such as iOS, Android, MacOS, Windows, Xbox etc... What does this mean for you? Well, imagine writing your code once, and having it run on all platforms; that is exactly what it means. Note that while the majority of the code that forms a game will work on all the supported platforms, there are differences in those platforms that would require the developer to write code specific to those platforms; from personal experience, 99+% of the code is common for all platforms, and less than 1% is platform-specific, so nothing to be concerned about.
-
2D game development: This engine is optimized for 2D games, but it also has support for 3D (although that is not the main focus of the engine at this point in time). You can also have a mix of 2D and 3D if the game idea you have requires that.
Now, on to the second sentence in the description, which means exactly what it states, being that Axmol is a fork of Cocos2d-x v4.0 game engine.
Cocos2d-x, which was a great game engine, no longer seems to be actively supported (since 2019), and since many developers relied on it for their projects, it was unfortunate (or fortunate!) that we are where we are today. Axmol was brought to life by @halx99 in 2019, and from that point on, it has been continuously improved and supported, which is perhaps why more and more developers have been choosing Axmol for their new projects, or a place to migrate their existing Cocos2d-x projects to.
While Axmol came from Cocos2d-x, it has diverged to be something more; much of the API is compatible between both engines, but there are differences and newer features in Axmol, which only serve to make it an even greater choice for game projects.
We will be focusing on 2D game development, so with that in mind, what are the most common parts that make up a 2D game?
-
Scene: Imaging the scene as a stage in a theatre, and on that stage, you would have your props, and the actors. The props may be interactive, and the actors may be moving around performing their roles (visually and audibly), so we need some way to represent those props and actors, and their behaviour on the stage…
-
Sprite: This is an object that you will be making the most use of in your game. The sprite is used to display something on the screen. It can be a static image, or it can be a series of images (frames) in sequential order, which we can refer to as an animation; there are other ways to animate graphics, but that's a topic for later. Going back to our "stage", think of object on the stage, be it a prop or an actor, being represented by their own sprite, which you can animate and move around in the scene (ie. an actor jumping and down, all while waving their arms around!).
-
Sound: Let's say you want to hear the actors on your stage, so how do we achieve that? Well, play an audio track! Yes, it's that simple. Of course, it is up to you to decide when to play the sound, but it really is that simple. If you need to play music, then, guess what, play that music! Just imagine the amazing atmosphere you can create by having the right mix of music and sounds effects playing at the same time! We will cover the "how-to" in more detail later.
-
Physics: It wouldn't be much fun if the actors on your stage would try walk around, only to fall through the floor! No, we can't have that (unless it's a feature in your game!). So, what can we do about it? Quick answer: Physics! (well, kind of). You don't need knowledge of physics, but we do need a way to utilize it for our purposes, such as in detecting when objects (such as sprites) collide, and have those objects react to that accordingly. Luckily, some very smart people out there who know a thing or two about physics have done the work for us, and all of that is available in Axmol, giving us all the wonderful functionality we need.
-
Input: Your game would need some way to be controlled by the player. This can be from a keyboard, mouse, gaming controller etc... This is perhaps the easiest part of the process, since a game engine would provide you with the functionality you need to receive this input, and carry out an action accordingly. There really isn't much to it!
You may have other requirements for your game, ones which may require an understanding of more advanced topics, such as networking (connecting multiple players together) and shaders (adding fancy visual effects). Don't stress about needing to know these things, because as with everything, you will know when you need them, and it would just be a simple task of investing a bit of time in learning them (from the gazillion sources available to you on the internet). Also, just for your information, these and most other topics covered here are not restricted to Axmol, so any guides available for any game engine would give you some insight on how things work.
The Axmol game engine provides all the basics and more to help you get started with your game. It takes care of much of the work for you, and all you would need to do is provide the "glue" that connects all the game building blocks together. For example, your game would receive input from the player, and based on that input, you would direct the player object (such as the sprite) to move in a certain direction.
There are several ways to install Axmol, with the most up-to-date information being available in the DevSetup guide.
This guide assumes your development environment is Windows, using Visual Studio 2022 and C++ as our chosen development language. If you are using MacOS, then simply swap out any platform specific console commands for the MacOS equivalents.
Once Axmol is installed and setup correctly, we should now be able to create a new project using the axmol
command available to us. The project will be created based on a default template, which exists in the {axmol}/templates/
path, in case you are curious and want to check it out. Avoid modifying anything in the template folder, or in fact any other folder within the axmol engine path (unless you know exactly what you're doing). Any modifications you want to make to the project files should be done to your own generated project, and not to the default template.
To create a new project, run the following command in a Powershell window:
axmol new -p org.axmol.mygame -d . -l cpp mygame
This command utilizes the axmol
script to generate a new project with the folder name "mygame", in the C++ language (via -l cpp
). The -p
is the package name for the project, which you must provide, and would typically be in reverse domain notation, but you can set it to anything you want.
Navigate into the mygame
folder that is created.
Now, let's create the build files for this project, once again using the axmol
script, but this time passing the build
switch to it.
axmol build -p win32 -c
The axmol build
tells the script that we want to run the build command. The -p
switch indicates which platform we wish to target for our project, so in this case win32
means it will be a Windows native project, and by default, it will be using the x64 architecture (64bit). The -c
at the end indicates that all we want the build command to do is to generate the Visual Studio project and solution files. If you leave out the -c
, then the axmol build
command will actually attempt to compile your project via the console. The DevSetup guide linked above has more information regarding the meaning of these switches and many others you can use for the axmol
script.
The command will generate a folder named "build" inside the root path of the "mygame" folder, as you can see in this screenshot:
So, open up the generated "mygame.sln" solution in Visual Studio.
Before we do anything else, let us test this new project to make sure it builds and runs successfully in Visual Studio. This is usually done via the F5 shortcut (unless you have remapped the command to a different key). If everything builds successfully, then you should end up with the following window:
The default folders created when a new project is created via the axmol new
command will look something like this:
The default location of all source code is the {project}/Source
folder, where you will find the initial generated source files:
How you structure your source code is up to you, but if you're just starting out, then keep all source code in this folder (or in sub-folders of Source
). Note, while sub-folders of Source/
can be any name you want, there is one name you should not use unless you have a specific purpose to use it, and that is shaders
. The Source/shaders/
folder is reserved for custom shaders that you may want to add to your game, and if you do not know what shaders are just yet, then that is OK, it is not something you need to know in order to start developing a game.
The default location for all assets (graphics, sounds, fonts etc.) that your project uses should be placed in the {project}/Content/
folder. For a new project, this is what it looks like:
There are platform specific folders that are in the project, which are there for the purpose of supporting specific target platforms, such as iOS, Android, Windows etc. etc... These folders contain code and resources that you may need to modify for the specific needs of your project, but avoid deleting these folders (unless you know what you're doing!):
Lastly, one of the most important files is the {project}/CMakeLists.txt
file. Any configuration changes related to your project would be added to this file:
Axmol utilizes CMake to generate the project files for each supported development environment, such as for Microsoft Visual Studio, Apple Xcode, Android Studio etc. etc... The CMakeLists.txt
file is at the heart of that, as it contains all the configuration settings that CMake will use in order to correctly generate the project build files.
If your knowledge of CMake is limited, then do spend some time learning it as you work on your project, because it is important to know. You don't need to know everything about it, but enough to be comfortable in setting up your project based on your requirements.
At some point you may want to dive in to learn more about the bits and pieces you can use to build up your game. This section will give you more insight into these building blocks, and if ever you want to know even more, well the source code for this engine is right at your fingertips, so don't be afraid to take a look at how these building blocks work, and what you can do with them.
Before we get started with the details of typical objects you would use in your game, let us touch on what a Node
is.
A Node
is a type of object that can be added to a scene. On its own, a node does not do much, not even displaying anything on the screen. Its purpose is to implement the common properties and functionality used in all types of nodes that inherit from it, such as the position, rotation, and scale of a objects on the screen.
In case it is not already obvious, Scene
and Sprite
are both types of nodes, inheriting the same functionality from Node
, and both adding their own unique functionality on top depending on their specific requirements and features. The concept of inheritance is outside the scope of this guide, and something related to object-oriented programming languages such as C++, so if you're not sure how this works, and would like to know more, then a quick internet search for "C++ inheritance" may help point you in the right direction.
As mentioned earlier, the scene is like a stage where everything takes place. Something important to keep in mind is that there can only be one scene active at any time.
The basic scene in Axmol is named Scene
. On its own it isn't really useful, since it simply exists to provide the base functionality of a scene. To make use of it, we would need to create our own type of scene, and inherit the functionality from the existing Scene
type. You can see an example of this when a new project is created in Axmol, where a default scene is created for you, named MainScene
. MainScene
is a sub-class of Scene
, and simply expands on Scene
by building up the objects that belong in that scene (such as the sprites and menu items).
When node objects are created, we can add them to the scene, and the scene then manages those nodes. This forms a sort of tree of nodes, often referred to as a "2D Scene Graph", with the root being the scene, and the branches and leaves being the child nodes in that scene. Generally, any node can have its own children, so for example, a sprite can have many child sprites of its own. This is important to know, because it allows certain features and functionality that you are more than likely to make use of in your game. We will expand on what that functionality is a bit later.
Adding objects to the any node is accomplished via one of the addChild()
API methods that belong to the Node
type, and since a Scene
inherits from Node
, then these methods exist in the Scene
too.
For a 2D game, the order in which we add objects to the scene matters, since it will dictate the order of visibility (local z-order) of those objects in the scene. The addChild()
methods also support explicitly setting the order of the objects in the scene, something which you may want to do if you need certain objects displayed in front or behind others. The default z-order starts at 0, and objects can be below 0, at 0, or above 0, depending on how you want to order them.
Examples:
// The following adds a previously created sprite displaying a background image, named backgroundSprite, at local z order of -10
scene->addChild(backgroundSprite, -10);
// The following does not explicitly set a local Z order for the sprite, then by default it will be 0.
scene->addChild(boxSprite);
// Objects at a higher Z value will be displayed on top of objects with lower Z values.
Scene->addChild(playerSprite, 20);
Note that if more than one sprite is at the same Z value, then display order is based on the order of adding them to the scene, with the newest being on top, and the oldest being at the bottom.
The coordinate system in a scene is based on x,y
of 0,0
, being in the bottom left corner of the view, so maximum x and y coordinates would be at the top right corner of the view.
When displaying objects, the position is always relative to the parent of that object. If sprites are added as children of a scene, then their position is relative to that scene.
The scene graph mentioned earlier can be thought of as a tree of nodes, starting at the root node, which would be a Scene
object. Refer to the diagram below on what a possible scene graph may look like:
In the scene graph image above, the root node would be F (our scene). Root node F has 2 child objects, being B and G, and those nodes also have their children as well. B has children A and D, while G has a single child I, and so on and so forth.
Now, to render these nodes in the correct order, the renderer would need to first sort these nodes by their local Z order values, and then use a form of tree traversal. The specific technique used in Axmol for this purpose is in-order traversal.
In-order traversal processes the nodes from the left-hand side of the tree, then the root node, then the right hand side of the tree. So, if we think about the previous example above where we add child nodes to a scene, the nodes with the lowest value will be on the left side, and the child nodes with the highest values will be on the right side.
How does the in-order traversal impact what you see on the screen? Well, this can be easily understood if you grab a piece of paper, and cut it into 9 pieces of random sizes (this is important!), to represent nodes A, B, C, D, E, F, G, H and I. Don't forget to write down one of those letters on each of the 9 pieces of paper.
The next step is to traverse the tree in the diagram above (also known as walking the tree), stacking the pieces of paper based on the in-order traversal. The correct in-order transversal is A, B, C, D, E, F, G, H, I (letters facing upwards), from bottom to top. So, the piece of paper with A would be at the bottom of the pile, while the I piece would be at the top of the pile.
Finally, imagine that each piece of paper represents a different sprite on your screen (note, yes root node F is a scene, but for the sake of this example, assume it is a sprite). Pick up that pile of paper, keeping the order as it is, and with the letters facing you, place it directly on your computer screen; this would represent how you would see the sprites, which would be I first, and if sprite H is large enough to not be hidden by sprite I, then you would see some of it too, then some of G, then F etc. etc., all the way back to the last sprite, A.
The ubiquitous objects that are the core of every game. As we touched on earlier, a Sprite is a game object that allows you to display an image, but as you may have guessed, that isn't all we can do with it. What it also allows you to do is to adjust certain properties of that object, such as the position, rotation, scale, color tint, etc... You can also have that sprite animate, in the simplest form by simply providing the sprite with a sequence of images (frames) that it loops through.
Sprites would typically use image files in the PNG and JPG formats. If you require transparency in your images, then you would use PNG, since the JPG format does not support transparency.
The simplest way to create a sprite based on a simple image is to call the following:
auto sprite = Sprite::create("image.png"); // Load an image file named "image.png", and create a sprite from it
We are not actually doing anything with the sprite at the moment, since we're just creating it. To actually display it on the screen, you would need to add this sprite object to your scene, and then set its position. For example:
auto sprite = Sprite::create("image.png");
scene->addChild(sprite);
sprite->setPosition(Vec2(100, 100)); // add the sprite at x,y coordinate of 100, 100
Sprites may also be created from a small slice of a larger image, which we refer to as a sprite sheet or texture atlas. This larger image contains many individual images on it, and there are several benefits to using this method of creating sprites. Without getting into too much detail, some of the benefits include improved performance by reducing the work required to draw graphics on the screen, as well as saving memory. If you are interested in learning more about this topic, please check this wiki entry.
Of course, once you place a sprite on the screen, you might want to make it bigger or smaller, give it an angle, or adjust its transparency. It doesn't matter if you modify the sprite before or after adding it to the scene.
sprite->setScale(0.5); // scales the sprite to half the size
sprite->setOpacity(128); // set the opacity of the sprite, 0 is transparent, 255 is opaque
sprite->setRotation(90); // rotates the sprite 90 degrees
// etc
A full list can be found in the API documentation.
Apart from sprites, another way to draw elements on the screen is by using lines and shapes like circles, triangles, or other polygons. For this, Axmol provides DrawNodes. These shapes can be filled with solid colors without the need for textures. DrawNodes are often used as placeholders for debugging or as temporary game elements before adding the final sprites.
Here is an example of how to draw a circle:
auto circle = DrawNode::create();
circle->drawCircle(Vec2(150, 150), 50, 0, 50, false, Color4F::RED);
// Position, radius, angle, segments, drawLineToCenter, color
this->addChild(circle);
Here a list of some primitives you can use:
-
drawDot
: draws a dot -
drawLine
: draws a line -
drawRect
: draws a rectangle -
drawCircle
: draws a circle -
drawTriangle
: draws a triangle -
drawPolygon
: draws a polygon - ...
Filled:
-
drawSolidRect
: draws a solid rectangle -
drawSolidCircle
: draws a solid circle -
drawSolidTriangle
: draws a solid triangle -
drawSolidPolygon
: draws a solid polygon - ...
Paths:
-
drawQuadBezier
: draws a quad bezier path. -
drawCubicBezier
: draws a cubic bezier path -
drawCardinalSpline
: draws a cardinal spline path -
drawCatmullRom
: draws a catmul rom path - ...
Forms:
-
drawPie
: draws a pie/arc -
drawStar
: draws a star -
drawSolidStar
: draws a solid star - ...
A full list can be found in the API documentation.
- ...
Games needs movement to express situations, or just to add some flavor. In order to move sprites around, Axmol uses actions
Let's say we want to move the sprite we created before from its original position (100, 100)
to a new position, (300, 300)
. This is how we do it.
auto moveSprite = MoveTo::create(1.0f, Vec2(300, 300));
sprite->runAction(moveSprite);
After MoveTo::create
we have two elements. The first one is the duration (in the example, 1 second), and the second is the position where the sprite will move. After executing this code, the sprite will move in straight line from its original position to the new position. Again, actions can be created before or after adding the sprite to the scene.
There are lots of actions available. Here are some of them:
auto scaleSprite = ScaleTo::create(1.0f, 2.0f); // Modifies scale (duration, final scale)
auto fade = FadeTo::create(0.5f, 0); // Modifies opacity (duration, final opacity)
auto wait = DelayTime::create(1.0f); // Adds a delay (duration)
// etc
A full list can be found in the API documentation.
Spawn
, Sequence
and Repeat
, RepeatForever
. Also CallFunc
. (WIP)
EaseIn
, EaseOut
, EaseInOut
. (WIP)
To-be-continued