GamePlay 3D Framework - easy start to cross-platform development of 3D games
Good time of day, dear habrazhiteli!
After taking a course on computer graphics at the university and having played enough with OpenGL, I decided that it was time to move on and try myself in game development. Frankly speaking, I didn’t really want to write my own engine from scratch. The main goal was to see how it was done, to learn lessons and maybe create something based on the selected engine. A quick search revealed that the open cross-platform engines are a bit tight. One has a problem with Linux, Windows or Mac OS, another with fresh versions of mobile OS, the third is almost abandoned ... But I did come across one very attractive copy, which I want to tell about in this article.
')
The name of this framework is GamePlay 3D. Information about it on the Internet is not very much, what can we say about Runet. This is an open source framework written in C ++ for programming games in C ++ with all the advantages and disadvantages that follow from it. The authors of the project position it as a universal tool, a kind of analog cocos2d for 3D games. To start writing on GamePlay 3D you do not need to have a deep knowledge of OpenGL, GLSL or 3D graphics math, but we all understand that to achieve a good result you cannot get away from it. Details and a small example to start under the cut.
I’ll start with a brief list of the main features of the engine:
support for Linux, Windows, Mac OS X, Android, iOS and Blackberry OS
C ++ API, Lua game scripts
OpenGL 3.2+ and OpenGL ES 2.0
build a project for different OS in one click *
simple and convenient configuration of scenes (objects, materials, physics, animation)
physics engine bullet (well, this is kind of standard)
A ready tool for converting 3D scenes and fonts into your own binary format.
support for input from the keyboard, mouse, joystick, screen joystick and sensors
comfortable work with sound (wav, ogg) in 3D
your own GUI with buttons, checkboxes, sliders, etc.
and much more.
* Well, almost one ...
A more complete list of features from the project site
Full-featured OpenGL 3.2+ (desktop) and OpenGL ES 2.0 (mobile) based rendering system
Shader-based material system with built-in common shader library
Node-based scene graph and noise collision objects
Heightmap based terrains with multiple surface layers and LOD
Declarative scene bindings (materials) and node attachments (particle emitters, physics collision objects, 3D audio sources)
Declarative particle system
Easy-to-use text and sprite rendering
Physics system (using Bullet physics)
vehicle physics
character physics
rigid body dynamics
constraints
ghost objects
Declarative UI system with support for themeable 2D and 3D forms. Includes the following built-in core controls and layouts:
button
label
text box
slider
check box
radio button
absolute layout
vertical layout
flow layout
Fully extensible animation system with skeletal character animation support
Complete 3D audio system with WAV and OGG support
Full vector math library with 2D / 3D math and 3D object culling support
Mouse, keyboard, touch, gestures and gamepad support
Lua script bindings and binding generator tool
AI state machine
We now understand how to quickly and easily create a small demo with a tiny scene, several objects, physics and basic lighting, using exclusively open source tools. References to the materials used and the archive with the finished project are scattered throughout the text and brought together at the end of the article.
We will create our first demo in Linux. We will need:
favorite IDE or text editor
cmake, make, gcc
Blender with support for export to COLLADA
Building the engine consists of downloading the source from Github, installing dependencies and running cmake ... && make in the build directory (Windows and Mac OS X users open an existing Visual Studio project or XCode, respectively, and compile with one click). After the compilation is successfully completed, we will start creating our project by executing the gameplay-newproject. Script (sh | bat) in the framework root directory. The script will ask a few questions, by answering which we will get the finished fish of the project in the directory indicated by us. Let's call Promo Demo and specify the current directory (the root directory of the engine) as the path.
Project
The created project already contains all the necessary files for compiling and running the game, as well as project files for Visual Studio and Xcode. Linux users have to take care of setting up the project for their IDE. The entire setting consists in specifying the directory with the source of the game, adding engine libraries to the project, etc. Now we will not dwell on this, but simply use the usual vim, the utilities cmake and make. We will compile the project, launch and see what we have, and then we will figure out what it consists of and add some drama.
cd Demo/build cmake .. make
Run the resulting binary:
cd ../bin/linux ./Demo
If everything is ok, then you should see something like this:
So, what do we see in the game folder with the exception of the VS, Xcode and cmake project files? game.config - base configuration such as resolution, full screen or window, etc. res / - directory with game resources res / box.gpb - game scene (just a cube with a camera and a light source), encoded in the native format box.dae - the game scene in its raw form, in this case in the format COLLADA colored. (frag | vert) - shaders used in cube material res / box.material - cube material description src / - source src / Demo. (cpp | h) - base class android / - project to build games for Android bin / - compiled binaries
But the ready-made example with a rotating cube is too simple and boring, besides, we want to dig a little deeper engine: add more cubes and even balls, determine their physical properties and materials.
Here is what we get in the end:
To begin with, let's create a scene in Blender: cubes hang over a certain surface, just above a couple of balls for complete happiness. It is important to note that in the game, height is determined by the y axis, not z , unlike Blender (in fact, it doesn’t matter, but there’s some kind of problem if the height is not defined on the GamePlay 3D axis, that for the purity of the experiment we will use y ). We need to somehow name all the objects so that we can access them from the code of the game and distinguish them from each other. We proceed simply - all cubes will be called boxX , balls ballX , where X is any number. The surface over which they soar, we name floor. Call the camera camera and rotate it so that the lens gets what you need. The scene is ready and it can be exported in a format understood by the utility gameplay-encoder. There are two options to choose from: FBX and COLLADA. We choose COLLADA, because at this stage it is easier (details at the end of the article).
The result looks something like this:
For the lazy and for those who do not have a blender at hand
Here comes the gameplay-encoder. This utility converts various incompatible file formats into the native binary format of the framework. We don’t want to waste time and memory on a scene parser from XML or render a vector font in 3D? It is better to parse such objects in advance and save them in an optimized binary form, so that later you can simply load them into memory. So, with a slight hand movement, we convert the scene.dae file exported from Blender to scene.gpb and put this file in the res / subdirectory of our project:
Unlike the starting example with a rotating cube, we will not manually search for objects in the scene and determine their properties. We will simply create a file describing the scene, and then the framework will do everything on its own. So, the scene description file game.scene:
It is worth noting that the variables in the shaders do not appear by themselves from nowhere. It is necessary to pay attention to what is written in the shader code and determine the corresponding variables in the materials, if necessary. The framework comes with a small library of ready-made shaders that implement different types of lighting, texturing, and more. We used a simple variant of coloring objects in one color. To make this case work, simply copy the entire library of shaders from path / to / GamePlay / gameplay / res / shaders to our project in res / shaders .
game.physics:
// collisionObject box { // - type = RIGID_BODY // bounding box - shape = BOX // mass = 2.0 } // collisionObject ball { type = RIGID_BODY // bounding box - shape = SPHERE mass = 1.0 } // , collisionObject floor { type = RIGID_BODY shape = BOX mass = 0 }
The values of possible physical parameters are described in detail in the documentation.
The point is soapy - load scene.gpb in the game code. We have the base class Demo, which inherits the class Game. The life cycle of the game is as follows:
game initialization in the initialize () method ;
update the state of the world before each frame in the update method (float elapsedTime);
rendering a scene in the render method (float elapsedTime);
finalizing the application in the finalize () method ;
We only change the initialize () method and empty the update (float elapsedTime) method .
Usually in the initialize () method we load scene files, scripts, and set the initial state of the game. So do:
Add the initializeScene () method, which will assign lighting parameters for each scene object using the light source we created. I think comments are unnecessary here:
bool Demo::initializeScene(Node* node) { Model* model = node->getModel(); if (model) { Material* material = model->getMaterial(); if (material && material->getTechnique()->getPassByIndex(0)->getEffect()->getUniform("u_lightDirection")) { material->getParameter("u_ambientColor")->setValue(_scene->getAmbientColor()); material->getParameter("u_lightColor")->setValue(_lightNode->getLight()->getColor()); material->getParameter("u_lightDirection")->setValue(_lightNode->getForwardVectorView()); } } returntrue; }
Do not forget to add the signature of the initializeScene () method and the private variable Node * _lightNode in Demo.h. The update (float elapsedTime) method will be left empty, because we will not change the state of the world in this example, physics will do everything for us.
Full listing of Demo.h and Demo.cpp
#ifndef TEMPLATEGAME_H_ #define TEMPLATEGAME_H_ #include"gameplay.h" using namespace gameplay; /** * Main game class. */ class Demo: public Game { public: /** * Constructor. */ Demo(); /** * @see Game::keyEvent */ void keyEvent(Keyboard::KeyEvent evt, int key); /** * @see Game::touchEvent */ void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex); protected: /** * @see Game::initialize */ void initialize(); /** * @see Game::finalize */ void finalize(); /** * @see Game::update */ void update(float elapsedTime); /** * @see Game::render */ void render(float elapsedTime); private: /** * Draws the scene each frame. */ bool drawScene(Node* node); bool initializeScene(Node* node); Scene* _scene; Node* _lightNode; }; #endif
#include"Demo.h"// Declare our game instance Demo game; Demo::Demo() : _scene(NULL), _lightNode(NULL) { } void Demo::initialize() { _scene = Scene::load("res/game.scene"); _lightNode = Node::create("directionalLight1"); Light* light = Light::createDirectional(_scene->getActiveCamera()->getNode()->getForwardVector()); light->setColor(1.0, 1.0, 1.0); _lightNode->setLight(light); // Set the aspect ratio for the scene's camera to match the current resolution _scene->getActiveCamera()->setAspectRatio((float)getWidth() / (float)getHeight()); _scene->visit(this, &Demo::initializeScene); } void Demo::finalize() { SAFE_RELEASE(_lightNode); SAFE_RELEASE(_scene); } void Demo::update(float elapsedTime) { } void Demo::render(float elapsedTime) { // Clear the color and depth buffers clear(CLEAR_COLOR_DEPTH, Vector4::zero(), 1.0f, 0); // Visit all the nodes in the scene for drawing _scene->visit(this, &Demo::drawScene); } bool Demo::initializeScene(Node* node) { Model* model = node->getModel(); if (model) { Material* material = model->getMaterial(); if (material && material->getTechnique()->getPassByIndex(0)->getEffect()->getUniform("u_lightDirection")) { material->getParameter("u_ambientColor")->setValue(_scene->getAmbientColor()); material->getParameter("u_lightColor")->setValue(_lightNode->getLight()->getColor()); material->getParameter("u_lightDirection")->setValue(_lightNode->getForwardVectorView()); } } return true; } bool Demo::drawScene(Node* node) { // If the node visited contains a model, draw it Model* model = node->getModel(); if (model) { model->draw(); } return true; } void Demo::keyEvent(Keyboard::KeyEvent evt, int key) { if (evt == Keyboard::KEY_PRESS) { switch (key) { case Keyboard::KEY_ESCAPE: exit(); break; } } } void Demo::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex) { switch (evt) { case Touch::TOUCH_PRESS: break; case Touch::TOUCH_RELEASE: break; case Touch::TOUCH_MOVE: break; }; }
Do not forget to also run cmake before compiling to include new files in the assembly. We compile, run the finished binary and observe how the cubes and balls have the color we specified, fall to the surface under the influence of the laws of physics we specified and are illuminated in accordance with the parameters of the light source we created. Hooray!
Cross platform
A small excursion into the assembly of the game for other platforms.
Android: you need to download the Android SDK and NDK, and install Apache Ant. Paths to the SDK and NDK must be in the PATH. Go to the android directory of our project, run the update project -t 1 -p.-s && ndk-build and get the finished apk. Mac OS X / iOS: open the project file in XCode, build, run. Windows: Open the Visual Studio project file, build, run. Blackberry Playbook: see the description on the site. Unfortunately, I am not the owner of the Playbook and can not comment on the situation. But since The authors of the framework initially wrote it under the Playbook, I think there should be no problems with it.
disadvantages
Where do without them?
The biggest problem I have encountered at the moment is blender & gameplay-encoder. The first one does not know how to export normal maps to COLLADA, so you can forget about bump-mapping. There is a patch written by one of the users of the framework, but this patch is not yet included in the blender code and its inclusion is not planned yet. Rebuilding blender with this patch is not an easy task, I haven't succeeded yet. Of course, you can use FBX instead of COLLADA. But there is another problem - it is not so easy to rebuild the gameplay-encoder. It comes in the form of a ready-made binary and works fine without FBX support, because FBX is proprietary. The FBX SDK can be downloaded free of charge from the Autodesk site, but the gameplay-encoder assembly with FBX support is also a big headache. Somewhere there is an old version of opencollada that refuses to link with new versions of libpcrecpp, somewhere the FBX SDK is naughty, etc. And if on Ubuntu 12.04 it is still possible, then on more recent Linux - sadness. The solution is to use Maya or 3DsMax where you need bump-mapping or use other methods. Or spend time assembling Blender or gameplay-encoder.
Documentation - it is beautiful. All classes and methods are well documented, in the gameplay-api directory there is generated HTML generated by doxygen. But the tutorial is sparse. The framework comes with several examples of games in the gameplay-samples directory. The creation of some of them is analyzed in the documentation on the site, but it is far from complete. It may take time to figure out what is happening in these examples and why.
Small bugs. The project is obviously quite young. Sometimes there are small bugs, which, however, quickly corrected. Authors actively accept pool requests and patches and are generally community friendly.
Results
In this example, we touched on only a small part of what GamePlay 3D offers us. There are many more goodies and joys, starting from the same simple simulation of vehicles and characters with a physics engine, simple programming of animation and sound, elementary input control from various devices and ending with convenient configuration sharing for different platforms. Recently, an article on Habré skipped about how hard it is to write games for Android, because, among other things, different phones use different hardware texture compression chips. To solve this problem, you can, for example, create different configuration files with paths to the corresponding textures for different chips: game.atc.config for devices with atc chips, game.dxt.config for dxt, game.pvr.config for pvr, game. png.conf for PC, and the engine will pick up the config for the specific device without any questions.
I hope you liked the article and someone decides to join the development of this wonderful framework or write your next (or first) project on it. The post turned out to be quite voluminous, so let's take a closer look at the materials, add interactivity to the demo and fasten the sounds another time.