A closer look at the Urho3D Game Engine

16. October 2014

 

GameFromScratch has a long running “A closer look at” series of articles that take a deep dive into a particular game engine.  Hopefully by the end, you the reader will have an idea if this engine is the right fit for you or not.logo

 

Today we are looking at the Urho3D engine, a game engine that somehow flew below my radar for a very long time.  It started life as Bofh3D but apparently was renamed after a tyrannical fish king ( seriously… and thus the fish in the logo to your right! ) According to Google Translate, Urho is Finnish for “Brave”, while 3D… I hope you know that one by this point!  The 3D is a bit of a misnomer though, as like many modern 3D engines, there is a 2D component as well, so you can just as easily make 2D games if that’s what you want to do.

 

Let’s start straight away with their own description:

 

Urho3D is a lightweight, cross-platform 2D and 3D game engine implemented in C++ and released under the MIT license. Greatly inspired by OGRE and Horde3D.

 

Features

  • Direct3D9 or OpenGL rendering (Shader Model 2, OpenGL 2.0 or OpenGL ES 2.0 required as minimum)
  • HLSL or GLSL shaders + caching of HLSL bytecode
  • Configurable rendering pipeline. Default implementations for forward, light pre-pass and deferred rendering
  • Component based scene model
  • Skeletal (with hardware skinning), vertex morph and node animation
  • Automatic instancing on SM3 capable hardware
  • Point, spot and directional lights
  • Shadow mapping for all light types; cascaded shadow maps for directional lights
  • Particle rendering
  • Geomipmapped terrain
  • Static and skinned decals
  • Auxiliary view rendering (reflections etc.)
  • Geometry, material & animation LOD
  • Software rasterized occlusion culling
  • Post-processing
  • HDR renderingv1.31
  • 2D sprites and particles that integrate into the 3D scenev1.31
  • Task-based multithreading
  • Hierarchical performance profiler
  • Scene and object load/save in binary and XML format
  • Keyframe animation of object attributesnew
  • Background loading of resourcesnew
  • Keyboard, mouse, joystick and touch input (if available)
  • Cross-platform support using SDL 2.0 (currently runs on Windows, Linux, Mac OS X, Android, iOS, and Raspberry Piv1.3)
  • Physics using Bullet
  • 2D physics using Box2Dnew
  • Scripting using AngelScript
  • Alternative script interface using Luav1.3 or LuaJITv1.31 (on Windows, Linux, Mac OS X, Android, and Raspberry Pi)
  • Networking using kNet + possibility to make HTTP requestsv1.3
  • Pathfinding using Recast/Detourv1.23
  • Image loading using stb_image + DDS / KTX / PVR compressed texture support
  • 2D and “3D” audio playback, Ogg Vorbis support using stb_vorbis + WAV format support
  • TrueType font rendering using FreeType, AngelCode bitmap fonts are also supported
  • Unicode string support
  • Inbuilt UI system
  • Scene editor and UI-layout editor implemented in script with undo & redo capabilities
  • Model/scene/animation/material import from formats supported by Open Asset Import Library
  • Alternative model/animation import from OGRE mesh.xml and skeleton.xml files
  • Supported build tools and IDEs: Visual Studio, Xcode, Eclipse, CodeBlocks, GCC, LLVM/Clang, MinGW-W64
  • Supports both 32-bit and 64-bitv1.3 build
  • Build as single external libraryv1.3 (can be linked against statically or dynamically)

 

The line greatly inspired by Ogre3D seems incredibly accurate to me.  On my initial explorations, that is what it most reminded me of, and that is certainly not an insult.  My nutshell description of Urho3D is:

 

A cross platform, open source, C++ based, Lua and AngelScript scripted game engine that runs on Windows, Mac and Linux and can target all those plus iOS, Android and Raspberry Pi.

 

So the question remains, what’s the developer experience like?  Well, let’s find out!

 

The Source Code

 

Being open source, Urho3D is available on Github.

image

 

I only took a quick browse through the actual code but from what I saw, it’s clean and well written in a modern C++ style.  The project is laid out intuitively, the engine and platforms nicely decoupled and things are pretty much where you would expect them to be.  The code is fairly sparsely commented, but the things that need to be commented, are.  We will touch on the documentation a bit later on.

 

Getting Started

 

Getting started is pretty simple.  Download the source code archive, extract it and use CMake to build the project files for your platform of choice.  I was up and running in a matter of minutes, however I already had all of the required development tools installed and configured.  If you’ve never used CMake before you may be in for a bit of a fight and if something goes wrong, CMake starts to feel strangely like black magic.  For me though, it mostly just worked.  A warning though, download the master branch!  The version linked of their main page is outdated to the point that the documentation doesn’t actually work.  They really should update the official release version so that it matches their getting started manual!

 

Once unzipped, Urho3D looks something like this:

image

 

Simply run the sh or bat file appropriate to your platform and you are good to go.  One thing to be aware of up front, Urho3D has samples in both AngelScript and C++, but by default the C++ projects aren’t created by cmake.  If you want them, when calling the script, add –DURHO3D_SAMPLES=1.  Additionally, Lua support isn’t added out of the box, if you want Lua support as –DURHO3D_LUA=1.

 

So for example, to get started on Windows using Visual Studio 2013, with Lua and C++ sample support, run:

cmake_vs2013.bat –DURHO3D_SAMPLES=1 –DURHO3D_LUA=1

Now if you go into the Build directory, you will see Visual Studio ( or XCode, or Makefile, whatever you chose ) projects.

 

image

 

Simply open Urho3D.sln in Visual Studio and you are done.

 

Samples Samples and More Samples

 

This is one area where Urho3D is well represented.  There are a number of included samples, written in both AngelScript and C++.  Here they are:

 

image

 

For C++, each sample is a project within your solution.  In the case of AngelScript however, each is simply a script file to be run.  Once you’ve built the Engine, you should have a tool named Urho3DPlayer ( or Urho3DPlayer_d if you built for debug ).  This is a command line utility, simply run it and pass in the path to a script to run.  The scripts are located under the Bin folder in the directory /Data/Scripts.

image

 

They are the sample examples as the C++, except of course implemented as AngelScript.

From the command line, in the bin folder, running:

Urho3DPlayer Data\Scripts\11_Physics.as

Will then load and run the script:

image

 

It’s worth noting, I also used the –w switch to run the player in Windowed mode so I could take a screen shot.  Hit ESC to exit.  Oh and Urho3D has annoying behavior of grabbing your mouse cursor, don’t worry when you lose your mouse cursor ( even Windowed ), exit with ESC or alt-tab away and you get your cursor back.  I hate really hate when windowed applications take complete control of my mouse!

 

The code in the samples is well documented, and they cover a wide variety of topics.  This is most likely going to be your primary learning source for getting up to speed quick.

 

To get an idea of a Urho3D application’s structure, let’s take a look at one of the samples, 03_Sprites.  When run, it will do this (except in motion that is):

 

image

 

Now let’s take a look at the corresponding AngelScript and C++ sources.

 

03_Sprites.as

 

// Moving sprites example.
// This sample demonstrates:
//     - Adding Sprite elements to the UI
//     - Storing custom data (sprite velocity) inside UI elements
//     - Handling frame update events in which the sprites are moved

#include "Scripts/Utilities/Sample.as"

// Number of sprites to draw
const uint NUM_SPRITES = 100;

Array<Sprite@> sprites;

void Start()
{
    // Execute the common startup for samples
    SampleStart();

    // Create the sprites to the user interface
    CreateSprites();

    // Hook up to the frame update events
    SubscribeToEvents();
}

void CreateSprites()
{
    // Get rendering window size as floats
    float width = graphics.width;
    float height = graphics.height;

    // Get the Urho3D fish texture
    Texture2D@ decalTex = cache.GetResource("Texture2D", "Textures/UrhoDecal.dds");

    for (uint i = 0; i < NUM_SPRITES; ++i)
    {
        // Create a new sprite, set it to use the texture
        Sprite@ sprite = Sprite();
        sprite.texture = decalTex;

        // The UI root element is as big as the rendering window, set random position within it
        sprite.position = Vector2(Random() * width, Random() * height);

        // Set sprite size & hotspot in its center
        sprite.size = IntVector2(128, 128);
        sprite.hotSpot = IntVector2(64, 64);

        // Set random rotation in degrees and random scale
        sprite.rotation = Random() * 360.0f;
        sprite.SetScale(Random(1.0f) + 0.5f);

        // Set random color and additive blending mode
        sprite.color = Color(Random(0.5f) + 0.5f, Random(0.5f) + 0.5f, Random(0.5f) + 0.5f);
        sprite.blendMode = BLEND_ADD;

        // Add as a child of the root UI element
        ui.root.AddChild(sprite);

        // Store sprite's velocity as a custom variable
        sprite.vars["Velocity"] = Vector2(Random(200.0f) - 100.0f, Random(200.0f) - 100.0f);

        // Store sprites to our own container for easy movement update iteration
        sprites.Push(sprite);
    }
}

void MoveSprites(float timeStep)
{
    float width = graphics.width;
    float height = graphics.height;

    // Go through all sprites
    for (uint i = 0; i < sprites.length; ++i)
    {
        Sprite@ sprite = sprites[i];

        // Rotate
        float newRot = sprite.rotation + timeStep * 30.0f;
        sprite.rotation = newRot;

        // Move, wrap around rendering window edges
        Vector2 newPos = sprite.position + sprite.vars["Velocity"].GetVector2() * timeStep;
        if (newPos.x < 0.0f)
            newPos.x += width;
        if (newPos.x >= width)
            newPos.x -= width;
        if (newPos.y < 0.0f)
            newPos.y += height;
        if (newPos.y >= height)
            newPos.y -= height;
        sprite.position = newPos;
    }
}

void SubscribeToEvents()
{
    // Subscribe HandleUpdate() function for processing update events
    SubscribeToEvent("Update", "HandleUpdate");
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    // Take the frame time step, which is stored as a float
    float timeStep = eventData["TimeStep"].GetFloat();

    // Move sprites, scale movement with time step
    MoveSprites(timeStep);
}

// Create XML patch instructions for screen joystick layout specific to this sample app
String patchInstructions =
        "<patch>" +
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">" +
        "        <attribute name=\"Is Visible\" value=\"false\" />" +
        "    </add>" +
        "</patch>";

 

And now the C++ versions:

Sprite.h

#pragma once

#include "Sample.h"

/// Moving sprites example.
/// This sample demonstrates:
///     - Adding Sprite elements to the UI
///     - Storing custom data (sprite velocity) inside UI elements
///     - Handling frame update events in which the sprites are moved
class Sprites : public Sample
{
    // Enable type information.
    OBJECT(Sprites);

public:
    /// Construct.
    Sprites(Context* context);

    /// Setup after engine initialization and before running the main loop.
    virtual void Start();

protected:
    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
    virtual String GetScreenJoystickPatchString() const { return
        "<patch>"
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
        "        <attribute name=\"Is Visible\" value=\"false\" />"
        "    </add>"
        "</patch>";
    }

private:
    /// Construct the sprites.
    void CreateSprites();
    /// Move the sprites using the delta time step given.
    void MoveSprites(float timeStep);
    /// Subscribe to application-wide logic update events.
    void SubscribeToEvents();
    /// Handle the logic update event.
    void HandleUpdate(StringHash eventType, VariantMap& eventData);

    /// Vector to store the sprites for iterating through them.
    Vector<SharedPtr<Sprite> > sprites_;
};

 

Sprite.cpp

#include "CoreEvents.h"
#include "Engine.h"
#include "Graphics.h"
#include "ResourceCache.h"
#include "Sprite.h"
#include "Texture2D.h"
#include "UI.h"

#include "Sprites.h"

#include "DebugNew.h"

// Number of sprites to draw
static const unsigned NUM_SPRITES = 100;

// Custom variable identifier for storing sprite velocity within the UI element
static const StringHash VAR_VELOCITY("Velocity");

DEFINE_APPLICATION_MAIN(Sprites)

Sprites::Sprites(Context* context) :
    Sample(context)
{
}

void Sprites::Start()
{
    // Execute base class startup
    Sample::Start();

    // Create the sprites to the user interface
    CreateSprites();

    // Hook up to the frame update events
    SubscribeToEvents();
}

void Sprites::CreateSprites()
{
    ResourceCache* cache = GetSubsystem<ResourceCache>();
    Graphics* graphics = GetSubsystem<Graphics>();
    UI* ui = GetSubsystem<UI>();

    // Get rendering window size as floats
    float width = (float)graphics->GetWidth();
    float height = (float)graphics->GetHeight();

    // Get the Urho3D fish texture
    Texture2D* decalTex = cache->GetResource<Texture2D>("Textures/UrhoDecal.dds");

    for (unsigned i = 0; i < NUM_SPRITES; ++i)
    {
        // Create a new sprite, set it to use the texture
        SharedPtr<Sprite> sprite(new Sprite(context_));
        sprite->SetTexture(decalTex);

        // The UI root element is as big as the rendering window, set random position within it
        sprite->SetPosition(Vector2(Random() * width, Random() * height));

        // Set sprite size & hotspot in its center
        sprite->SetSize(IntVector2(128, 128));
        sprite->SetHotSpot(IntVector2(64, 64));

        // Set random rotation in degrees and random scale
        sprite->SetRotation(Random() * 360.0f);
        sprite->SetScale(Random(1.0f) + 0.5f);

        // Set random color and additive blending mode
        sprite->SetColor(Color(Random(0.5f) + 0.5f, Random(0.5f) + 0.5f, Random(0.5f) + 0.5f));
        sprite->SetBlendMode(BLEND_ADD);

        // Add as a child of the root UI element
        ui->GetRoot()->AddChild(sprite);

        // Store sprite's velocity as a custom variable
        sprite->SetVar(VAR_VELOCITY, Vector2(Random(200.0f) - 100.0f, Random(200.0f) - 100.0f));

        // Store sprites to our own container for easy movement update iteration
        sprites_.Push(sprite);
    }
}

void Sprites::MoveSprites(float timeStep)
{
    Graphics* graphics = GetSubsystem<Graphics>();
    float width = (float)graphics->GetWidth();
    float height = (float)graphics->GetHeight();

    // Go through all sprites
    for (unsigned i = 0; i < sprites_.Size(); ++i)
    {
        Sprite* sprite = sprites_[i];

        // Rotate
        float newRot = sprite->GetRotation() + timeStep * 30.0f;
        sprite->SetRotation(newRot);
        
        // Move, wrap around rendering window edges
        Vector2 newPos = sprite->GetPosition() + sprite->GetVar(VAR_VELOCITY).GetVector2() * timeStep;
        if (newPos.x_ < 0.0f)
            newPos.x_ += width;
        if (newPos.x_ >= width)
            newPos.x_ -= width;
        if (newPos.y_ < 0.0f)
            newPos.y_ += height;
        if (newPos.y_ >= height)
            newPos.y_ -= height;
        sprite->SetPosition(newPos);
    }
}

void Sprites::SubscribeToEvents()
{
    // Subscribe HandleUpdate() function for processing update events
    SubscribeToEvent(E_UPDATE, HANDLER(Sprites, HandleUpdate));
}

void Sprites::HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    using namespace Update;

    // Take the frame time step, which is stored as a float
    float timeStep = eventData[P_TIMESTEP].GetFloat();
    
    // Move sprites, scale movement with time step
    MoveSprites(timeStep);
}

 

EDIT: And the Lua example as well:

03_Sprites.lua

-- Moving sprites example.
-- This sample demonstrates:
--     - Adding Sprite elements to the UI
--     - Storing custom data (sprite velocity) inside UI elements
--     - Handling frame update events in which the sprites are moved

require "LuaScripts/Utilities/Sample"

local numSprites = 100
local sprites = {}

-- Custom variable identifier for storing sprite velocity within the UI element
local VAR_VELOCITY = StringHash("Velocity")

function Start()
    -- Execute the common startup for samples
    SampleStart()

    -- Create the sprites to the user interface
    CreateSprites()

    -- Hook up to the frame update events
    SubscribeToEvents()
end

function CreateSprites()
    local decalTex = cache:GetResource("Texture2D", "Textures/UrhoDecal.dds")

    local width = graphics.width
    local height = graphics.height

    for i = 1, numSprites do
        -- Create a new sprite, set it to use the texture
        local sprite = Sprite:new()
        sprite.texture = decalTex
        sprite:SetFullImageRect()

        -- The UI root element is as big as the rendering window, set random position within it
        sprite.position = Vector2(Random(width), Random(height))

        -- Set sprite size & hotspot in its center
        sprite:SetSize(128, 128)
        sprite.hotSpot = IntVector2(64, 64)

        -- Set random rotation in degrees and random scale
        sprite.rotation = Random(360.0)
        sprite.scale = Vector2(1.0, 1.0) * (Random(1.0) + 0.5)

        -- Set random color and additive blending mode
        sprite:SetColor(Color(Random(0.5) + 0.5, Random(0.5) + 0.5, Random(0.5) + 0.5, 1.0))
        sprite.blendMode = BLEND_ADD

        -- Add as a child of the root UI element
        ui.root:AddChild(sprite)

        -- Store sprite's velocity as a custom variable
        sprite:SetVar(VAR_VELOCITY, Variant(Vector2(Random(200.0) - 100.0, Random(200.0) - 100.0)))

        table.insert(sprites, sprite)
    end
end

function SubscribeToEvents()
    -- Subscribe HandleUpdate() function for processing update events
    SubscribeToEvent("Update", "HandleUpdate")
end

function MoveSprites(timeStep)
    local width = graphics.width
    local height = graphics.height

    for i = 1, numSprites do
        local sprite = sprites[i]
        sprite.rotation = sprite.rotation + timeStep * 30

        local newPos = sprite.position
        newPos = newPos + sprite:GetVar(VAR_VELOCITY):GetVector2() * timeStep

        if newPos.x >= width then
            newPos.x = newPos.x - width
        elseif newPos.x < 0 then
            newPos.x = newPos.x + width
        end
        if newPos.y >= height then
            newPos.y = newPos.y - height
        elseif newPos.y < 0 then
            newPos.y = newPos.y + height
        end
        sprite.position = newPos
    end
end

function HandleUpdate(eventType, eventData)
    local timeStep = eventData:GetFloat("TimeStep")

    MoveSprites(timeStep)
end

-- Create XML patch instructions for screen joystick layout specific to this sample app
function GetScreenJoystickPatchString()
    return
        "<patch>" ..
        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">" ..
        "        <attribute name=\"Is Visible\" value=\"false\" />" ..
        "    </add>" ..
        "</patch>"
end

 

As you can see, the code is clean enough and well enough documented to learn from. Unfortunately there aren't equivalent Lua examples right now.

EDIT: Ok, my bad.  Fortunately there are in fact Lua examples as well!  They were just very well hidden in the /Bin/Data/LuaScript folder.

 

Hello World

 

Urho3D commits a common sin and one that drives me absolutely nuts with game engines.  It’s Hello World, in fact, all of it’s C++ examples are built over a “Sample” class.  This means when the reader wants to start from scratch on their own project, they have to tear through the base class to figure out what goes into a core application.  I get why they do this, so they can focus on the feature they want to show, but at least one example should be as complete as possible with no underlying class to build on.  Fortunately I have done this for you.  The following is basically the “minimum usable” Urho3D application:

 

TestMain.h

#pragma once

#include "Application.h"


using namespace Urho3D;

class TestMain : public Urho3D::Application {
   OBJECT(TestMain);

public:
   TestMain(Urho3D::Context*);

   virtual void Setup();
   virtual void Start();
   virtual void Stop() {}

private:
   void onKeyDown(StringHash,  VariantMap&);

};

 

TestMain.cpp

#include "TestMain.h"
#include "Engine.h"
#include "Graphics.h"
#include "Input.h"
#include "InputEvents.h"
#include "ResourceCache.h"
#include "UI.h"
#include "Font.h"
#include "Text.h"

using namespace Urho3D;

DEFINE_APPLICATION_MAIN(TestMain)

TestMain::TestMain(Urho3D::Context* context) : Application(context){
}

void TestMain::Setup(){
   engineParameters_["FullScreen"] = false;
}

void TestMain::Start(){
   SubscribeToEvent(E_KEYDOWN, HANDLER(TestMain,onKeyDown));

   SharedPtr<Text> text(new Text(context_));
   text->SetText("Hello Cruel World!");
   text->SetColor(Color::WHITE);
   text->SetFont(GetSubsystem<ResourceCache>()->GetResource<Font>("Fonts/BlueHighway.ttf"), 42);
   text->SetHorizontalAlignment(HA_CENTER);
   text->SetVerticalAlignment(VA_CENTER);

   GetSubsystem<UI>()->GetRoot()->AddChild(text);
}

void TestMain::onKeyDown(StringHash event, VariantMap& data){
   engine_->Exit();
}

 

It creates a windowed Hello World application, displays the text “Hello Cruel World” in white, centered to the screen and waits for any key to be pressed before exiting.

While basic, it should give you some idea how Urho3D works.  There are times, like when trying to figure out parameters engineParameters takes that you really wish for better reference documentation, but it was fairly simple to get things up and running.  I did have a bit of a struggle with life cycle, when I tried to put more logic into Setup() instead of Start() but otherwise things mostly worked how I expected.   Speaking of documentation…

 

The Documentation

 

So what’s the documentation like?  It’s split in to two parts, the documentation that has been written covering the various aspects, tasks and systems in Urho3D.  There is also an auto generated class reference. You can read the documentation here.

 

As you can see, most of the major systems are covered:

image

 

The documentation is well written in clear English, and for the most part covers what you would expect it to.  For an open source project I have to say, the overall documentation level is very good.  The only area I was somewhat let down by was the reference material.

 

There is an automatically generated class reference available:

image

But the details are pretty sparse:

image

 

So, for example, if you are hunting down say… what audio formats are supported, this information can be a bit hard to find and may result in you having to jump into the source code.  I do wish there was more implementation specific details in the reference materials.

 

Perhaps I am nit picking at this point…  working so much in Java lately, JavaDoc has really spoiled me.

 

In summary, the documentation is solid, great in fact for an open source project.  I would however appreciate more detail in the reference material.

 

Tools

 

Part of what makes an engine and engine is the tooling it supports.  Urho3D is no exception.  We already mentioned Urho3DPlayer, but there are several other tools, one of which is actually run in the player.  There is a full blown 3D level editor:

image

 

The editor enables you to create and edit several node types:

image

 

And provides context appropriate property editing:

image

 

It’s not going to win any beauty pageants, but it is a full functioning 3D world editor written entirely in AngelScript.  So if it doesn’t have functionality you want, simply add it.  The code is all available in the Bin\Data\Scripts\Editor folder:

image

 

With full code access, you should easily be able to extend the editor to fit whatever type of game you are looking at creating.

 

In addition to the editor, there are a number of other tools.  There is AssetImporter from the Assimp project, for importing 3D assets.  There is also a tool for importing Ogre3D assets.  PackageTool, for pulling your assets all together, a shader compiler, lightmap generator and more.

 

Summary

 

Urho3D is an impressive, well documented, cross platform game engine with clean accessible code and a ton of examples.  There are of course some negatives too.  The tools aren’t nearly as polished as you see in many commercial engines, the reference material could be a bit more extensive and the community isn’t huge.  I can’t speak to performance as I never dove in that deeply.  Is it worth checking out for your own game project?  Well, if control and open source are important to you and you like C++, AngelScript and/or Lua, I would most certainly give it a look. 

 

What do you think, does Urdo3D look interesting to you?  Would you like to see more in depth tutorials from GameFromScratch.com?  Let me know!

Programming , ,




Guide to Creating a Game on an iPad–Meet Codea

15. October 2014

 

With this post we begin our voyage of creating a game from scratch entirely on an iPad.  We start by covering what is perhaps the most important piece, Codea.  This is the part of the puzzle that actually allows us to write and run code on the iPad, so yeah… it’s a bit critical.

 

This post is a bit of a milestone of sorts, this is the very first time I’ve done a voiced over video.  I intend to start doing video production a bit more often to supplement text tutorials, articles and reviews, so I hope you don’t find my delivery overly annoying!  Bear with me a bit while I get used to doing voice work and work out the kinks on video production.  Things will get better!  Um, I hope.

 

Here is the video, don’t worry, a text and picture based version follows for those with a video aversion.  The video is recorded in 1080p, so for the best video quality, I recommend directly opening the file on YouTube.

 

 

So, what exactly is Codea?

 

Basically it’s a complete game development system for iPad, that enables you to create games for the iPad, on the iPad.  It’s Lua based, includes a complete game library, code editor, asset manager, shader programmer, tons of examples and more.  It’s available on the App Store for $10.  Let’s take a quick guided tour of Codea.

 

The Main Interface

 

The interface itself is quite clean and basic.  When you load up Codea, you are greeted by this page:

 

Photo 2014-10-15, 2 21 13 PM

 

Tools

 

Across the top are the examples, across the bottom are your projects.  You can easily clone an example into a project, as you can also duplicate existing projects.  If you notice at the top left there is a bar with an arrow to it’s right.  This brings out the tools panel:

 

Photo 2014-10-15, 2 21 23 PM

 

From the top to bottom we have:

  • Reference – Brings up the integrated help files
  • Shader Lab – An interactive (and excellent) GLSL shader editor
  • Assets -  Asset management ( graphics, sounds, shaders, etc )
  • AirCode – Enable Codea for editing using a computer with a web browser.

 

Settings

 

To the top right is the settings panel.

Photo 2014-10-15, 2 21 18 PM

 

Here you can set your theme and font sizing, and most importantly, link your Dropbox.  This allows you to Dropbox account to Codea, enabling you to easily get assets in and out of Codea, something not easily done on iOS devices.

 

Code Editing

 

When you open a project, this is where you end up:

Photo 2014-10-15, 2 22 11 PM

 

Across the top are tabs representing the files in your project.  Press the + on the top right to create a new class or file.  As you can see, across the top of the soft keyboard, they’ve added a number of keys to make programming easier.  These include a cursor for moving left and right, tab for, um… tabbing.  There is also an integrated Find and Help buttons.  If you hook up a USB keyboard, the soft keyboard goes away.

 

Here is the find dialog:

 

Photo 2014-10-15, 2 20 34 PM

 

And much more important, the integrated help files:

Photo 2014-10-15, 2 20 53 PM

 

Running your code ( clicking the Play icon at the bottom right ), brings you to this screen:

Photo 2014-10-15, 2 20 16 PM

 

The parameters section enable you to create handy UI controls to interact with your app.  Output is basically a text console.  The icons enable you to pause, restart, take a snapshot and video cap of your app running.  When you run Aircode, your iPad goes to this screen, allowing you to live edit code and see it run in real time.

 

Code Editing Helpers

 

I’ll admit, coding on a touch screen without a keyboard isn’t a great experience, but Codea have made the process somewhat more bearable.  First was the keyboard extensions above.  Additionally they have implemented some nice touch friendly features, like:

 

Color Picker:

Photo 2014-10-15, 2 22 20 PM

 

Asset Chooser:

Photo 2014-10-15, 2 22 00 PM

Codea actually includes a fair bit of free content to get you started.

 

Font Chooser:

Photo 2014-10-15, 2 22 14 PM

 

GLSL Editor

I mentioned earlier that there is an integrated GLSL editor:

Photo 2014-10-15, 2 21 33 PM

You can edit the Vertex and Fragment scripts and see the result in realtime on the right.  The Bindings is where you can set parameters in to your shader.  As I said in the video, I really hope this gets spun off into it’s own product!

 

 

It’s a clean, simple but fairly comprehensive set of tools.  The included samples are quite impressive as well.   We will cover coding at a point in the future.

 

So that’s Codea.  We will get to know it a lot better of the next few weeks.

Programming , ,




Guest Tutorial: Creating a Game Using MightyEditor and Phaser

12. October 2014

 

The following is a guest tutorial written by Guntis at MightyEditor showing how you can easily use MightyEditor and Phaser to create a game.

 

 

This article will give you an example on how to use popular HTML5 game development framework Phaser and editor based on top of it - MightyEditor. This tutorial will take about an hour and only ~90 lines of actual code. Following game development aspects will be introduced: sprites, sprite animations, physics, collision, game states, player death/revival.

 

Requirements

 

You should use the newest version of Google Chrome. Other browsers should work, but are not tested.

 

Creating a project

 

In this tutorial we will create a coin collecting-spike avoiding platformer.

Go to http://mightyeditor.mightyfingers.com/ and click on "Create New Project". Enter the name of your game. In this tutorial, our game will be called "SockMan"

 

1

 

Next, locate the settings panel in the bottom-right and change the worldHeight, worldWidth, viewportHeight, and viewportWidth values to 1026x578, as shown in the image below. Viewport is the size of the in-game camera, but we won't be dealing with camera controls in this tutorial. 

 

2

 

Uploading game assets

 

Next up - game assets. First, download the assets and extract them. Then locate the asset manager in the upper-right corner and either select the Upload File/Upload Folder options or simply drag and drop the files from your file explorer straight to the manager panel.

 

3 

 

Creating the world

 

Select the stamp icon, then, in the asset panel, click on the image you want to place, bg_wall.png, for now and then click on the map grid to  place the asset on the map. Note, that the dark grey area represents the world/viewport. CTRL-click snaps the image to grid and you can move the image around afterwards by selecting the arrow tool in the left side menu. More accurate placement is possible by using the 'settings' menu in bottom-right. Next, place the 'grass_tile.png' asset like described above and you should you get a world like in the picture below.

 

4

 

Next up - grouping the placed objects together. Locate the objects panel in the middle-right and either select Add Group and then select and drag the objects in the newly created group or select the objects and then click on Group Selected Objects (SHIFT-click selects multiple objects). After that, rename the group to "bg" (double-click on group name) and your project should look like the image below.

 

 5

 

Note: it can be useful to lock (small lock icon on the right side) the background and any other groups/objects in place, so they become unclickable and don't mess with placing other objects.

 

Adding foreground objects

 

After placing background, we will add (almost) the rest of the objects to the game world - boxes, grass tiles, chest and spikes. To do that, simply use the stamp tool to place each object on the map (remember about CTRL to snap to grid) and group them accordingly - boxes and grass in "objects", spikes in "spikes" group and leave the chest 'groupless'. Everything should look similar to the image below, but feel free to create your own layout.

 

6

 

As for the actual character and coins, things get a little different, because there are multiple frames per image for animations. Select the character.png asset and in the Settings panel set its frameWidth and frameHeight values to 100px,the anchorX to 0.5 and anchorY values to 0.7.

 

7

After that just place the character sprite on the left side of the map by using the usual stamp tool. Repeat these step with the coin spritesheet, only changing the frameWidth to 113px and frameHeight to 71px. Both anchors are 0.5. Place multiple coins on the map and group them together in a 'coins' group. Finally, select the the text icon (big T) in the toolbar and place a "0" in the top right corner of your game world. In the objects panel, rename it to "gold". Now your game world should look like this and we can turn to actual coding.

 

8 

 

Switching to source editor

 

You can switch to the source editor in the top-left of the screen. Editor keyboard shortcuts can be found here.

 

9

 

Game states

 

Coding is done by using the Phaser development framework (homepage and documentation). There are different states (separate parts of the game like intro, menu, gameplay etc.). The MightyEditor gives you four states by default: boot, load, menu and play, but, in this tutorial, we will only be coding in the play state and without any menus, so we should just go straight to the play state. Switch to the 'menu.js' file and call the switch to the play state.

window.SockMan.state.menu = {
        create: function() {

            this.game.state.start("play");
        },

 

You can now click on the Open Game button in the top panel and the game will load... to a blank screen. The graphics must be initialized beforehand. To do that, we will switch to the play.js state. We need to use the mt.create function in the create method like this:

 

create: function() {
    this.bg = mt.create("bg");
    this.character = mt.create("character");
    this.spikes = mt.create("spikes");
    this.chest = mt.create("chest");
    this.objects = mt.create("objects");
    this.coins = mt.create("coins");
    this.gold = mt.create("gold");
},

 

This initializes and creates the game sprites and sprite groups. Now, when opening the game, the visuals will be there, but the image is static.

 

Adding Physics

 

To add movement, first we must enable physics on the character. To do that, head back to the Map Editor and select your character sprite. In the bottom-right, use the Physics panel to enable it. Set these values:

  • enable: 1
  • immovable: 0
  • size-width: 40
  • size-height: 70
  • gravity-allow: 1
  • gravity-y:1000
  • collideWorldBounds: 1

 

10

 

In the end, everything should look like this:  Note: the size parameters are for the physics body, so the game interacts only with the area you want, not the blank parts or the sprite. Your character should now fall through the floor and stay on the edge of the game (that's what collideWorldBounds is for). To fix that, we must enable collision for the objects group.  To do that, just change the physics enable parameter of the group and add this piece of code in your update method (a function, looped 60 times per second):

 

this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
            console.log('Collision detected');
        }, null, this);

And all should be well. The collision detection's first two arguments check for interaction between two entities - the character sprite and a group of objects - and the third argument is a function which does something when these two objects (the character and a separate object of the group) do collide. Congratulations! Your character no longer falls through the floor tiles (grass/boxes).

 

Movement

 

But we need to move. To do that, the keyboard controls must be enabled beforehand. Simply put these two lines in the create method:

 

this.cursors = this.game.input.keyboard.createCursorKeys();
this.space = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

 

The keyboard.createCursorKeys() method initializes the arrow keys. Other keys, like SPACEBAR, SHIFT or CTRL must be initialized like shown above. After that, define actions to do when a key is pressed in the update method:

 

if (this.cursors.left.isDown) {
    this.character.body.velocity.x = -200;
} else if (this.cursors.right.isDown) {
    this.character.body.velocity.x = 200;
} else {
    this.character.body.velocity.x = 0;
}
if (this.space.isDown || this.cursors.up.isDown) {
    this.character.body.velocity.y = -550;
}

 

Arrow keys control your character, alternatively you can jump with SPACEBAR. Or maybe we should say - fly! But we only want to jump, not fly. To do that, one must enable the character to jump only if it is standing on the ground. Delete the current piece of jump code (the one with space and cursors.up) and instead place this in your collision detection function, so it looks like this:

 

this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
    if (this.space.isDown || this.cursors.up.isDown) {
        if (object.body.touching.up) {
            this.character.body.velocity.y = -550;
        }
    }

}, null, this);

 

Adding Character Animations

 

Animations, just like mostly everything, must first be initialized. In the create method, add these lines of code:

this.character.animations.add('stand', [0, 1, 2, 3], 10, true);
this.character.animations.add('run', [6, 7, 8, 9, 10, 11], 10, true);
this.character.animations.add('jump', [12, 13], 10, false);
this.character.animations.add('die', [18, 19], 10, false);
this.character.animations.play('stand');

 

These lines describe animations for your character. The first argument is the 'key' of the animation, the second - an array of frames which this particular animation uses, the third - frames per second and the last argument describes whether the animation should loop. The last line starts an animation when you launch the game. The rest of the animations is played according to situation, when certain condition is met, like a button press. The rest is added in the update method like this:

 

if (this.cursors.left.isDown) {
    this.character.body.velocity.x = -200;
    this.character.animations.play('run');
    this.character.scale.x = -1;
} else if (this.cursors.right.isDown) {
    this.character.body.velocity.x = 200;
    this.character.animations.play('run');
    this.character.scale.x = 1;
} else {
    this.character.body.velocity.x = 0;
    this.character.animations.play('stand');
}

 

And for jump animation, update your collision detection function with this:

 

this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
    if (this.space.isDown || this.cursors.up.isDown) {
        if (object.body.touching.up) {
            this.character.animations.play('jump');
            this.character.body.velocity.y = -550;
        }

    }
}, null, this);

 

At this point you might be wondering why you can't see a jump animation. It's because it gets overwritten by the 'run' or 'stand' animations since their conditions are met as well. We must invent another variable which tells the animations whether the character is in the air. Long story short, after putting this variable in, all of the update method will look like this:

 

update: function() {
    var standing = false;
    this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
        if (object.body.touching.up) {
            standing = true;
        } else standing = false;
        if (this.space.isDown || this.cursors.up.isDown) {
            if (object.body.touching.up) {
                this.character.animations.play('jump');
                this.character.body.velocity.y = -550;
                standing = false;
            }

        }
    }, null, this);

    if (this.cursors.left.isDown) {
        this.character.body.velocity.x = -200;
        if (standing) this.character.animations.play('run');
        this.character.scale.x = -1;
    } else if (this.cursors.right.isDown) {
        this.character.body.velocity.x = 200;
        if (standing) this.character.animations.play('run');
        this.character.scale.x = 1;
    } else {
        this.character.body.velocity.x = 0;
        if (standing) this.character.animations.play('stand');
    }
}

 

Collecting Coins

 

Before you are able to collect coins, their physics must be enabled, the physics body size set to 24x24 and offsetY to 10.

 

 

Next, we initialize the animation:

 

this.coins.self.callAll('animations.add', 'animations', 'collect', [7, 8], 10, false);

 

Note the difference between this line and those used to initialize the character's animations. Since the character was a lone sprite but the coins are all in a group, we must use the callAll method which initializes the animation for each and every child of the group separately. The first argument of the method is the method you would normally use, the second is the context and the rest is identical to adding animations as usual. After animating, we determine overlapping between the character and coins, making the coin disappear  when touch ends and the animation is done playing, and adding +1 to the counter in the upper-right corner:

 

this.game.physics.arcade.overlap(this.character, this.coins.self, function(character, coin) {
    coin.body = null;
    var coinCollect = coin.animations.play('collect');
    coinCollect.killOnComplete = true;
    var newPoints = parseInt(this.gold._text) + 1;
    this.gold.setText(newPoints);
}, null, this);

 

The coin.body is set to null, disabling the physics body. Otherwise the points would be added as long as the animation is still playing. since the character overlaps the animation.

 

Spikes, Life and Death

 

Next up is being able to kill the character when it touches the spikes. To do this, you must enable the physics of your 'spikes' group and set the body height to 20px and the offsetY parameter to 44px - this changes the Y coordinate from which the body is calculated. Pretty much an alternative to anchorY. After that comes the now usual collision detection:

 

this.game.physics.arcade.collide(this.character, this.spikes.self, function(character, spike) {
    if (character.alive) character.animations.play('die');
    character.body.velocity.x = 0;
    character.body.velocity.y = 0;
    character.alive = false;
}, null, this); 

 

The only problem now is that, after dying, you can still move. To avoid this, check if the character.alive is true before movement:

 

var standing = false;
this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
    if (object.body.touching.up) {
        standing = true;
    } else standing = false;
    if ((this.space.isDown || this.cursors.up.isDown) && character.alive) {
        if (object.body.touching.up) {
            this.character.animations.play('jump');
            this.character.body.velocity.y = -550;
            standing = false;
        }

    }
}, null, this);
if (this.character.alive) {
    if (this.cursors.left.isDown) {
        this.character.body.velocity.x = -200;
        if (standing) this.character.animations.play('run');
        this.character.scale.x = -1;
    } else if (this.cursors.right.isDown) {
        this.character.body.velocity.x = 200;
        if (standing) this.character.animations.play('run');
        this.character.scale.x = 1;
    } else {
        this.character.body.velocity.x = 0;
        if (standing) this.character.animations.play('stand');
    }
}

 

And finally, we add the ability to revive and send your character to start position by pressing SPACEBAR (set the character coordinates to your starting coordinates, viewed in the Map Editor):

 

if (this.space.isDown && !this.character.alive) {
    this.character.revive();
    this.character.x = 68;
    this.character.y = 452;
}

 

Achieving the Goal

 

One last thing - making the game do something when touching the big chest. This time we won't be using physics but check for overlapping differently (viable for single sprites only, not groups). First, add this checkOverlap function after the update method (don't forget to add a coma after the ending brace of the update method):

 

checkOverlap: function(spriteA, spriteB) {
    var boundsA = spriteA.getBounds();
    var boundsB = spriteB.getBounds();
    return Phaser.Rectangle.intersects(boundsA, boundsB);
}

 

And finally - call this function from within update:

 

if (this.checkOverlap(this.character, this.chest)) {
    this.game.state.start("play");
}

 

In this case, the 'play' state is restarted as soon as you touch the chest.

 

Congratulations!

 

Your game is now be fully playable and the code should closely resemble this:

 

"use strict";
window.SockMan.state.play = {
    create: function() {
        this.bg = mt.create("bg");
        this.character = mt.create("character");
        this.spikes = mt.create("spikes");
        this.chest = mt.create("chest");
        this.objects = mt.create("objects");
        this.coins = mt.create("coins");
        this.gold = mt.create("gold");

        this.cursors = this.game.input.keyboard.createCursorKeys();
        this.space = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

        this.character.animations.add('stand', [0, 1, 2, 3], 10, true);
        this.character.animations.add('run', [6, 7, 8, 9, 10, 11], 10, true);
        this.character.animations.add('jump', [12, 13], 10, false);
        this.character.animations.add('die', [18, 19], 10, false);
        this.character.animations.play('stand');
        this.coins.self.callAll('animations.add', 'animations', 'collect', [7, 8], 10, false);
    },

    update: function() {
        var standing = false;
        this.game.physics.arcade.collide(this.character, this.objects.self, function(character, object) {
            if (object.body.touching.up) {
                standing = true;
            } else standing = false;
            if ((this.space.isDown || this.cursors.up.isDown) && character.alive) {
                if (object.body.touching.up) {
                    this.character.animations.play('jump');
                    this.character.body.velocity.y = -550;
                    standing = false;
                }
            }
        }, null, this);
        if (this.character.alive) {
            if (this.cursors.left.isDown) {
                this.character.body.velocity.x = -200;
                if (standing) this.character.animations.play('run');
                this.character.scale.x = -1;
            } else if (this.cursors.right.isDown) {
                this.character.body.velocity.x = 200;
                if (standing) this.character.animations.play('run');
                this.character.scale.x = 1;
            } else {
                this.character.body.velocity.x = 0;
                if (standing) this.character.animations.play('stand');
            }
        }

        this.game.physics.arcade.overlap(this.character, this.coins.self, function(character, coin) {
            coin.body = null;
            var coinCollect = coin.animations.play('collect');
            coinCollect.killOnComplete = true;
            var newPoints = parseInt(this.gold._text) + 1;
            this.gold.setText(newPoints);
        }, null, this);

        this.game.physics.arcade.collide(this.character, this.spikes.self, function(character, spike) {
            if (character.alive) character.animations.play('die');
            character.body.velocity.x = 0;
            character.body.velocity.y = 0;
            character.alive = false;
        }, null, this);

        if (this.space.isDown && !this.character.alive) {
            this.character.revive();
            this.character.x = 68;
            this.character.y = 452;
        }

        if (this.checkOverlap(this.character, this.chest)) {
            this.game.state.start("play");
        }
    },

    checkOverlap: function(spriteA, spriteB) {
        var boundsA = spriteA.getBounds();
        var boundsB = spriteB.getBounds();
        return Phaser.Rectangle.intersects(boundsA, boundsB);
    }
};

 

Full game project: http://mightyeditor.mightyfingers.com/#pe45-copy

Final game: http://mightyeditor.mightyfingers.com/data/projects/pe45/phaser/index.html

The end result:

 

Click here to open in a new window.

Programming , , ,




Cocos2d-x Tutorial Series: Game Loop, Updates and Action Handling

11. October 2014

 

 

So at this point in time we’ve covered configuring Cocos2d-x, basic graphics, mouse, touch and keyboard event handling but wouldn’t it be nice to, you know… do something?  Most games are pretty boring if they are completely static, no?  Well in this tutorial section we are going to make things a bit more interesting.  One of the ways we are going to add a bit of life to our game is using Actions, which we will cover in a second.  First we need to cover something else, the game loop.

 

Handling Updates

 

Pretty much every game ever made has a game loop, even if it’s hidden by the game engine.  A Cocos2d-x game is no exception, although it might not be immediately obvious.

What's a game loop?


A game loop is essentially the heart of a game, what causes the game to actually run. The following is a fairly typical game loop:

void gameLoop(){
   while (game != DONE){
      getInput();
      physicsEngine.stepForward();
      updateWorld();
      render();
   }
   cleanup();
}

 

As you can see, it's quite literally a loop that calls the various functions that make your game a game.  This is obviously a rather primitive example but really 90% of game loops end up looking very similar to this.

 

However, once you are using a game engine, things get slightly different.  All this stuff still happens, it’s just no longer your codes responsibility to handle it anymore.  Instead the game engine performs the loop and each step then calls back to your game code.  Consider when your game handles input events, where do those events come from?  Well chances are the game engine has a getInput() somewhere inside it, and as part of that process calls your event handlers.  Even though you don’t have to handle the games lifecycle yourself, it’s helpful to understand what’s going on behind the scenes.

 

So far in all of our examples we either handled everything in init() or in response to input event callbacks and that can only get you so far.  What happens when you want to update your game independently to input events?  One option is to update your game every time you render a frame of graphics, but this is generally not a great idea.  It’s very common to try to run graphics as fast as possible but update the game at a fixed frequency.  Plus, logically, does it really make sense to be updating stuff during a function that’s responsible for drawing graphics?  No, not really.

 

Fortunately there is a ready and much better named alternative… you guessed it, update.  The method update is part of the Node class and is easily overridden.  Let’s take a quick look at a game that handles update.  I got so sick of recreating scenes each time I created a new project, so the name might look somewhat familiar.

Also you are going to need a sprite for this sample.  Personally I am using a picture of my car… yeah, that’s it.

 

 

Veyron

 

Feel free to use whatever you want.  Now the code:

 

HelloWorld.h

#pragma once

#include "cocos2d.h"

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();
    virtual bool init() override;
    CREATE_FUNC(HelloWorld);

    void update(float) override;

private:
   cocos2d::Sprite* sprite;
};

 

HelloWorld.cpp

#include "HelloWorldScene.h"

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    sprite = Sprite::create("Veyron.png");
    sprite->setPosition(this->getBoundingBox().getMidX(), this->getBoundingBox().getMidY());
    this->addChild(sprite, 0);
    
    this->scheduleUpdate();
    return true;
}

void HelloWorld::update(float delta){
   auto position = sprite->getPosition();
   position.x -= 250 * delta;
   if (position.x  < 0 - (sprite->getBoundingBox().size.width / 2))
      position.x = this->getBoundingBox().getMaxX() + sprite->getBoundingBox().size.width/2;
   sprite->setPosition(position);
}

 

Now, if you run the code you get:

 

action1

 

So what’s going on here?  Well think back to that game loop example I gave earlier.  Now imagine somewhere deep inside Cocos2d-x when it performs the “updateWorld” portion, that it loops through all the the Nodes in the game and calls their update() method.  Well that’s basically exactly what happens.  The line:

 

     this->scheduleUpdate();

 

Is what tells Cocos2d-x to call the Node's update function.  We then override update to implement our logic.  The sole paramater passed to update is a float value representing the amount of time, in seconds since the last time the update function was called.  Therefore if it’s been 1/10 of a second since the last time update was called, the value passed in will be 0.1.

 

Inside the update itself, we simply change the position of our sprite until it is fully off screen on the left hand side.  At which point we move it to the right hand side and repeat the process.  The only code that is of interest here is this line:

position.x -= 250 * delta;

 

This is a pretty common technique in game dev for creating smooth animations.  What we are saying here is we want to move by 250 pixels to the left.  The problem is, we have no idea how fast our update is going to be called, so on a faster computer the car will move faster and on a slower computer it will move slower.  This is obviously not ideal.  Enter the delta value.  Since we know how long it was since the last frame, we know if we multiply our move amount by the fraction of a second each frame takes, it will perform roughly the same speed on all computers.  So, using the 0.10 value above, this means we are running 10 updates per second, so each time we will be updating by 250 * 0.10 or 25, literally a 10th of the amount we want to update.  If however this value is over one second, things will get screwy.  That said, if your game is running at less than 1FPS, you’ve got bigger problems to worry about!  So, in a nutshell, when moving on a frame by frame basis, express your units in seconds, then multiply them by the delta passed in to the update function.

 

Now remember earlier when I said it’s possible to run your updates in the render method but it wasn’t always ideal, how then do we control the frequency that our update is called?

 

Well, we can’t really as you never know how fast the computer or phone you are going to be running is.  You do however have control over the priority the updater will view your update function with.  By default when you call scheduleUpdate() your update function will be called every single frame.  If the node you are updating doesn’t actually need to be updated every frame, you are just wasting CPU power ( and battery life ).  If you have a lower priority update you can tell Cocos2d-x this using:

this->scheduleUpdateWithPriority(42);

 

The actual value passed in is simply relative to other priorities.  When Cocos is trying to decide which update’s to call, it will first call all of the update() that don’t have a priority set.  Then it will call the one with the lowest value, then the next highest, etc.  So if you have three Node with update set, one with no priority set, one with a priority of 42 and one with a priority of 13, the no priority update will be called first, then the 13 and finally the 42.  In some ways you aren’t actually setting the priority, you are setting the lack of priority! 

 

In place of overriding update() you can also use schedule and scheduleOnce to schedule any function to be called.  Either after a period of time or a number of times.  The called function needs to have the same profile as update, that is takes a single float parameter and a void return type.

 

Sometimes however instead of reacting each frame and updating your world, you just want to “fire and forget” something.  For example let’s say you want to move an object to a certain location over a certain period of time.  This is where Actions come in.

 

Using Actions

 

As just mentioned, Actions allow you to set something in motion and forget about it.  Actions are remarkably consistent in how they work, so I will only show small snippets of code for each one.  We are using the following code as our base:

 

#include "HelloWorldScene.h"

cocos2d::Scene* HelloWorld::createScene()
{
    auto scene = cocos2d::Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    sprite = cocos2d::Sprite::create("Veyron.png");
    sprite->setPosition(this->getBoundingBox().getMidX(), this->getBoundingBox().getMidY());
    this->addChild(sprite, 0);
    
    auto listener = cocos2d::EventListenerKeyboard::create();
    listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      // This is where our different actions are going to be implemented
      auto action = cocos2d::MoveTo::create(2, cocos2d::Vec2(0, 0));
      sprite->runAction(action);
   };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
        return true;
}

 

This is also our first example of using an Action. In this case we are using the MoveTo action to move the target node to the position (0,0) over a duration of 2 seconds. You run the action on a Node using the runAction method.  Run it and press any key and you will see:

 

MoveTo

 

There are several similar actions, let’s take a look at a couple of them now.  Instead of MoveTo, there is also MoveBy, which enables you to move your node relative to it’s current position, like so:

 

   auto listener = cocos2d::EventListenerKeyboard::create();
   listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      auto action = cocos2d::MoveBy::create(2, cocos2d::Vec2(300, 300));
      sprite->runAction(action);
   };

 

When you run this, instead of moving to a destination over a period of 2 seconds, we instead move by 300 right and 300 up over the same time period.

 

MoveBy

 

There are several similar Actions that can be used to transform and modify a Node such as RotateBy, RotateTo, ScaleTo, SkewTo, TintTo, TintBy and more.

 

In addition to transforming nodes, you can actually loop and sequence actions, to make combo’s.  Let’s take a look at an example of a sequence of several actions.  In this example we are going to perform a ScaleBy, TintTo then FadeTo back to back using the Sequence action.

 

   listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      cocos2d::Vector<cocos2d::FiniteTimeAction*> actions;
      actions.pushBack(cocos2d::ScaleBy::create(1.5, 1.5));
      actions.pushBack(cocos2d::TintTo::create(1.5, 255, 0, 0));
      actions.pushBack(cocos2d::FadeTo::create(1.5, 30));
      
      auto sequence = cocos2d::Sequence::create(actions);

      sprite->runAction(sequence);
   };

 

And when run:

Sequence

 

There are two things to be aware of from this example. First you may notice that TintTo takes three GLubyte values to repesent the red, green and blue values of the colour, while FadeTo takes a single GLubyte value to represent that alpha or transparency.  A GLubyte is an 8bit value that ranges from 0 to 255 in value.  In all cases 255 is the fully on value, and 0 is the fully off value.  Therefore the value (255,0,0) is 100% red, 0% green, 0% blue, while the value 30 in TintTo is 30/255 or 11.7% opaque.  The second import thing to note is the use of Vector.  This is a cocos2d value type, NOT a std::vector, although ultimately behind the scenes, I believe it is still implemented using a std::vector.  This means you cant use it as a std::vector, nor can you use a std::vector where a cocos2d::Vector is expected.  This also unfortunately means you can’t use initializer lists.

 

So, that’s how you can perform a number of actions in sequence, what happens if you want to perform them all at once?  You can do that too using Spawn, which personally I think could really have a better name!  Let’s look at exactly the same example using Spawn instead.  The only difference is I increased the duration of each action to 4 seconds, mostly just to make it easier to screen capture. :)

 

   listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      cocos2d::Vector<cocos2d::FiniteTimeAction*> actions;
      actions.pushBack(cocos2d::ScaleBy::create(4, 1.5));
      actions.pushBack(cocos2d::TintTo::create(4, 255, 0, 0));
      actions.pushBack(cocos2d::FadeTo::create(4, 30));
      
      auto parallel = cocos2d::Spawn::create(actions);

      sprite->runAction(parallel);
   };

 

And run it:

parallel

 

You also have the ability to repeat actions, both a certain number of times, or simply forever.  That is exactly what this example is going to do.  The first action moves to the right by 10 pixels every 0.2 of a second.  The second action scales the sprite up 30% every 2 seconds.  The first action will be repeated 10 times, the second forever, or until it crashes your computer that is. :)

 

   auto listener = cocos2d::EventListenerKeyboard::create();
   listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      auto action = cocos2d::MoveBy::create(0.2, cocos2d::Vec2(10, 0));
      auto action2 = cocos2d::ScaleBy::create(2, 1.3);
      auto repeat = cocos2d::Repeat::create(action, 10);
      auto repeatForever = cocos2d::RepeatForever::create(action2);

      sprite->runAction(repeat);
      sprite->runAction(repeatForever);
   };

 

Running:

repeat

 

So far we’ve only looked at Actions inherited from ActionInterval, which are actions that happen over time.  There are also actions that happen instantly, let’s take a look at some of them now.  These actions inherit from ActionInstant.  In this example we illustrate several instant actions ( as well as a MoveTo, DelayTime and Sequence, as a bunch of instant actions doesn’t make for a great demonstration! )

 

   auto listener = cocos2d::EventListenerKeyboard::create();
   listener->onKeyPressed = [=](cocos2d:: EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      cocos2d::Vector<cocos2d::FiniteTimeAction*> actions;
      actions.pushBack(cocos2d::MoveTo::create(1, cocos2d::Vec2(0, 0)));
      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::Place::create(cocos2d::Vec2(
         this->getBoundingBox().getMidX(), this->getBoundingBox().getMidY())));
      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::FlipX::create(true));
      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::FlipY::create(true));
      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::Hide::create());
      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::Show::create());
      actions.pushBack(cocos2d::DelayTime::create(1));

      actions.pushBack(cocos2d::CallFunc::create([=]()->void{
         this->setColor(cocos2d::Color3B::RED);
      }));

      actions.pushBack(cocos2d::DelayTime::create(1));
      actions.pushBack(cocos2d::RemoveSelf::create(false));

      auto sequence = cocos2d::Sequence::create(actions);
      sprite->runAction(sequence);
   };

 

This code running:

Instant

 

As you can see, instant actions work almost indentically.  FlipX mirrors the Node along the X axis, FlipY does the same across the Y axis.  DelayTime we havent used yet, does exactly what it’s name says, delays for the given amount of seconds before executing the next Action.  The Place action can by thought of as a 0 duration MoveTo call, putting the Node at the specified position.

 

CallFunc and RemoveSelf are the two actions that probably require the most explanation.  CallFunc enables you to call code using an action, in this case I use a lambda that simply changes the background color of the Layer.  CallFunc is an incredibly important action and allows you to do just about anything using Actions, such as updating state, playing a sound, etc.  RemoveSelf is another handy action… it’s basically a kill switch.  When a removeSelf action is encountered, that Node is removed from it’s parent.  Passing true causing cleanup to be done.  This is incredibly handy for something like handling the lifespan of a bullet in the scene for example.

 

Setting a Layer's Background Color


You may have noticed I changed the background of the scene in the previous example using a call to setColor(). However if you try to run this code as is, you will notice it doesn't actually work. This is because, behind the scenes, I made a couple small changes. Instead of our scene inheriting from Layer we instead inherit from LayerColor, which adds, you guessed it, color information. Additionally, install of calling Layer::init() in our own init, we call LayerColor::initWithColor(). With these two changes you can now set the background color in the layer.

 


Odds and Ends

 

There are a few interesting topics that fit into this chapter but we didn’t cover yet, so I am going to shoehorn them here at the end.  One very common activity developer’s want to perform when working with Actions is to pause them.  As you can have several Actions running at once, so then, what do you do when you want to pause your game?  Thankfully it’s quite simple to accomplish using ActionManager.

 

HelloWorldScene.h

#pragma once

#include "cocos2d.h"

class HelloWorld : public cocos2d::LayerColor
{
public:
    static cocos2d::Scene* createScene();
    virtual bool init() override;
    CREATE_FUNC(HelloWorld);

private:
   cocos2d::Sprite* sprite,*sprite2;
   cocos2d::Label* label;
   bool spritePaused = false;
   cocos2d::Vector<Node*> pausedNodes;
};

 

HelloWorldScene.cpp

#include "HelloWorldScene.h"

cocos2d::Scene* HelloWorld::createScene()
{
    auto scene = cocos2d::Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
   if (!LayerColor::initWithColor(cocos2d::Color4B::BLACK))
    {
        return false;
    }
    
   label = cocos2d::Label::createWithSystemFont("Press space to pause all, 1 to pause left", "Arial", 30);
   label->setPosition(cocos2d::Vec2(this->getBoundingBox().getMidX(), this->getBoundingBox().getMaxY() - 20));

   sprite = cocos2d::Sprite::create("Veyron.png");
   sprite2 = cocos2d::Sprite::create("Veyron.png");
   sprite->setPosition(250, this->getBoundingBox().getMidY());
   sprite2->setPosition(700, this->getBoundingBox().getMidY());

   auto rotate = cocos2d::RotateBy::create(1, 45);
   auto rotate2 = cocos2d::RotateBy::create(1, -45);

   auto repeat1 = cocos2d::RepeatForever::create(rotate);
   auto repeat2 = cocos2d::RepeatForever::create(rotate2);

   this->addChild(label,0);
   this->addChild(sprite, 0);
   this->addChild(sprite2, 0);
    
   sprite->runAction(repeat1);
   sprite2->runAction(repeat2);
   auto listener = cocos2d::EventListenerKeyboard::create();
   listener->onKeyPressed = [=](cocos2d::EventKeyboard::KeyCode code, cocos2d::Event * event)->void{
      // On Spacebar, Pause/Unpause all actions and updates
      if (code == cocos2d::EventKeyboard::KeyCode::KEY_SPACE){
         if (pausedNodes.size()){
            cocos2d::Director::getInstance()->getActionManager()->resumeTargets(pausedNodes);
            pausedNodes.clear();
            spritePaused = false; // In case user currently has 1 pressed too
         }
         else
            pausedNodes = cocos2d::Director::getInstance()->getActionManager()->pauseAllRunningActions();
         label->setString("Spacebar pressed");
      }
      // Pause/UnPause just sprite 1
      if (code == cocos2d::EventKeyboard::KeyCode::KEY_1){
         if (spritePaused)
            sprite->resumeSchedulerAndActions();
         else
            sprite->pauseSchedulerAndActions();
         spritePaused = !spritePaused;
         label->setString("1 pressed");
      }
      
   };

   this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
   return true;
}

 

And run it:

ActionManager

 

As you can see using ActionManager you are able to pause execution of Actions, either to a single Node or all Nodes at once.  In the event of a single Node it’s simply a matter of calling pauseSchedulerAndActions and resumeSchedulerAndActions.  You can also call pause() which is also result in the Node no longer receiving events too.

 

In the event of pausing all running actions by calling getActionManager()->pauseAllRunningActions() this returns a cocos2d::Vector off all the Nodes that were paused.  When resuming, you simply pass this Vector back in a call to resumeTargets().

 

Earlier on we called scheduleUpdate() with resulted in our update method being called every frame.  However you can also schedule any kind of function using the scheduler.  Let’s take a look:

 

#include "HelloWorldScene.h"

cocos2d::Scene* HelloWorld::createScene()
{
    auto scene = cocos2d::Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}


void HelloWorld::callOnce(float delta){
   cocos2d::MessageBox("Called after 10 seconds elapsed", "Message");
}

bool HelloWorld::init()
{
   if (!LayerColor::initWithColor(cocos2d::Color4B::BLACK))
    {
        return false;
    }
   
   this->scheduleOnce(schedule_selector(HelloWorld::callOnce), 10);
    return true;
}

 

This code will wait 10 seconds and then call our method callOnce().

 

So, even though the event loop is hidden away in a Cocos2d-x, there are plenty of ways you can control the action, be it using updates, scheduling functions to run or using Actions.

 

Programming , , ,




Cocos2d-x Tutorial Series: Handling the Keyboard

7. October 2014

 

 

In this part of the Cocos2d-x tutorial series we are going to take a look at what’s involved in handling keyboard events.  If you went through the mouse/touch tutorial, a lot of this is going to seem very familiar, as the process is quite similar.  That said, keyboard handling does have it’s own special set of problems to deal with.

Let's jump straight in to an example. Once again I assume you already know how create your own AppDelegate, if you can't, I suggest you jump back to this part first.

 

Handling Keyboard Events

 

Our first example is simply going to respond to WASD and Arrow keys to move the Cocos2d-x logo around the screen.  In this example I made no special modifications to a standard scene, so the header is unchanged from previous tutorials.

 

KeyboardScene.cpp

#include "KeyboardScene.h"

USING_NS_CC;

Scene* KeyboardScene::createScene()
{
    auto scene = Scene::create();
    
    auto layer = KeyboardScene::create();
    scene->addChild(layer);
    return scene;
}

bool KeyboardScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(this->getContentSize().width/2, this->getContentSize().height/2);

    this->addChild(sprite, 0);

    auto eventListener = EventListenerKeyboard::create();



    eventListener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event* event){

        Vec2 loc = event->getCurrentTarget()->getPosition();
        switch(keyCode){
            case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
            case EventKeyboard::KeyCode::KEY_A:
                event->getCurrentTarget()->setPosition(--loc.x,loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
            case EventKeyboard::KeyCode::KEY_D:
                event->getCurrentTarget()->setPosition(++loc.x,loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_UP_ARROW:
            case EventKeyboard::KeyCode::KEY_W:
                event->getCurrentTarget()->setPosition(loc.x,++loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_DOWN_ARROW:
            case EventKeyboard::KeyCode::KEY_S:
                event->getCurrentTarget()->setPosition(loc.x,--loc.y);
                break;
        }
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener,sprite);

    return true;
}

 

When run, you see the logo centered and can move it around using either WASD or arrow keys.

KeyboardSS

The code works almost identically to our earlier Touch examples.  You create an EventListener, in this case a EventListenerKeyboard, implement the onKeyPressed event handler.  The first paramater passed in is the EventKeyboard::KeyCode enum, which is a value representing the key that was pressed.  The second value was the Event target, in this case our sprite.  We use the Event pointer to get the target Node and update it’s position in a direction depending on which key is pressed.  Finally we wire up our scene’s _eventDispatcher to receive events.  Nothing really unexpected here.

 

Polling the Keyboard

 

You may however ask yourself… what If I want to poll for keyboard events?  For example, what if you wanted to check to see if the spacebar was pressed at any given time?

 

Short answer is, you can’t.  Cocos2d-x is entirely event driven.

 

Long answer however is, it’s relatively easy to roll your own solution, so let’s do that now.  I’ll jump right in with the code and discuss it after.

 

KeyboardScene.h

#pragma once

#include "cocos2d.h"
#include <map>


class KeyboardScene : public cocos2d::Layer
{
public:

    static cocos2d::Scene* createScene();
    virtual bool init();

    bool isKeyPressed(cocos2d::EventKeyboard::KeyCode);
    double keyPressedDuration(cocos2d::EventKeyboard::KeyCode);

    CREATE_FUNC(KeyboardScene);

private:
    static std::map<cocos2d::EventKeyboard::KeyCode,
        std::chrono::high_resolution_clock::time_point> keys;
    cocos2d::Label * label;
public:
    virtual void update(float delta) override;
};

 

KeyboardScene.cpp

#include "KeyboardScene.h"

USING_NS_CC;

Scene* KeyboardScene::createScene()
{
    auto scene = Scene::create();
    
    KeyboardScene* layer = KeyboardScene::create();
    scene->addChild(layer);
    return scene;
}

bool KeyboardScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }

    label = cocos2d::Label::createWithSystemFont("Press the CTRL Key","Arial",32);
    label->setPosition(this->getBoundingBox().getMidX(),this->getBoundingBox().getMidY());
    addChild(label);
    auto eventListener = EventListenerKeyboard::create();



    Director::getInstance()->getOpenGLView()->setIMEKeyboardState(true);
    eventListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event){
        // If a key already exists, do nothing as it will already have a time stamp
        // Otherwise, set's the timestamp to now
        if(keys.find(keyCode) == keys.end()){
            keys[keyCode] = std::chrono::high_resolution_clock::now();
        }
    };
    eventListener->onKeyReleased = [=](EventKeyboard::KeyCode keyCode, Event* event){
        // remove the key.  std::map.erase() doesn't care if the key doesnt exist
        keys.erase(keyCode);
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener,this);

    // Let cocos know we have an update function to be called.
    // No worries, ill cover this in more detail later on
    this->scheduleUpdate();
    return true;
}

bool KeyboardScene::isKeyPressed(EventKeyboard::KeyCode code) {
    // Check if the key is currently pressed by seeing it it's in the std::map keys
    // In retrospect, keys is a terrible name for a key/value paried datatype isnt it?
    if(keys.find(code) != keys.end())
        return true;
    return false;
}

double KeyboardScene::keyPressedDuration(EventKeyboard::KeyCode code) {
    if(!isKeyPressed(EventKeyboard::KeyCode::KEY_CTRL))
        return 0;  // Not pressed, so no duration obviously

    // Return the amount of time that has elapsed between now and when the user
    // first started holding down the key in milliseconds
    // Obviously the start time is the value we hold in our std::map keys
    return std::chrono::duration_cast<std::chrono::milliseconds>
            (std::chrono::high_resolution_clock::now() - keys[code]).count();
}

void KeyboardScene::update(float delta) {
    // Register an update function that checks to see if the CTRL key is pressed
    // and if it is displays how long, otherwise tell the user to press it
    Node::update(delta);
    if(isKeyPressed(EventKeyboard::KeyCode::KEY_CTRL)) {
        std::stringstream ss;
        ss << "Control key has been pressed for " << 
            keyPressedDuration(EventKeyboard::KeyCode::KEY_CTRL) << " ms";
        label->setString(ss.str().c_str());
    }
    else
        label->setString("Press the CTRL Key");
}
// Because cocos2d-x requres createScene to be static, we need to make other non-pointer members static
std::map<cocos2d::EventKeyboard::KeyCode,
        std::chrono::high_resolution_clock::time_point> KeyboardScene::keys;

 

And when you run it:

ControlKey

 

So, what are we doing here?  Well essentially we record key events as they come in.  We have two events to work with, onKeyPressed and onKeyReleased.  When a key is pressed, we store it in a std::map, using the KeyCode as the key and the current time as the value.  When the key is released, we remove the released key from the map.  Therefore at any given time, we know which keys are pressed and for how long.  In this particular example, in the update() function ( ignore that for now, I’ll get into it later! ) we poll to see if the Control key is pressed.  If it is, we find out for how long and display a string.

 

So, even though polling isn’t built in to Cocos2d-x, it is relatively easy to add.

 

Dealing with Keyboards on Mobile Devices

 

So, what about keyboards on mobile devices?  All Android phones and iOS devices are able to display a Soft Keyboard ( the onscreen keyboard ), can we use it?  The answer is… sort of.

 

What's about physical keyboards on mobile devices?


You may be wondering, how does a physical keyboard on a mobile device work with Cocos2d-x? In the case of an iPad, the answer is, it doesn't. When I hooked up a Bluetooth Keyboard, absolutely nothing happened. The same occurred when I paired the keyboard to my Android phone. However, I do not have an Android device with a physical keyboard, such as the Asus Transformer, but my gut says it wouldn't work either. At least, not with you doing a lot of legwork that is

 

Sort of isn't really a great answer so I will go into a bit more detail.  Yes you can use the soft keyboard, but in a very limited manner.  Basically you can use it for text entry only.  Truth is though, this should be enough, as controlling a game using a soft keyboard would be a horrid experience.

 

Let’s take a look at an example using TextFieldTTF and implementing an TextFieldDelegate:

 

KeyTabletScene.h

#pragma once
#include "cocos2d.h"

class KeyTabletScene : public cocos2d::Layer, public cocos2d::TextFieldDelegate
{
public:
    virtual ~KeyTabletScene();

    virtual bool onTextFieldAttachWithIME(cocos2d::TextFieldTTF *sender) override;

    virtual bool onTextFieldDetachWithIME(cocos2d::TextFieldTTF *sender) override;

    virtual bool onTextFieldInsertText(cocos2d::TextFieldTTF *sender, const char *text, size_t nLen) override;

    virtual bool onTextFieldDeleteBackward(cocos2d::TextFieldTTF *sender, const char *delText, size_t nLen) 
override; virtual bool onVisit(cocos2d::TextFieldTTF *sender, cocos2d::Renderer *renderer, cocos2d::Mat4 const &transform, uint32_t flags) override; static cocos2d::Scene* createScene(); virtual bool init(); CREATE_FUNC(KeyTabletScene); };

 

KeyTabletScene.cpp

#include "KeyTabletScene.h"

USING_NS_CC;

Scene* KeyTabletScene::createScene()
{
    auto scene = Scene::create();
    
    auto layer = KeyTabletScene::create();
    scene->addChild(layer);

    return scene;
}

bool KeyTabletScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }


    // Create a text field
    TextFieldTTF* textField = cocos2d::TextFieldTTF::textFieldWithPlaceHolder("Click here to type",
            cocos2d::Size(400,200),TextHAlignment::LEFT , "Arial", 42.0);
    textField->setPosition(this->getBoundingBox().getMidX(),
            this->getBoundingBox().getMaxY() - 20);
    textField->setColorSpaceHolder(Color3B::GREEN);
    textField->setDelegate(this);

    this->addChild(textField);

    // Add a touch handler to our textfield that will show a keyboard when touched
    auto touchListener = EventListenerTouchOneByOne::create();

    touchListener->onTouchBegan = [](cocos2d::Touch* touch, cocos2d::Event * event) -> bool {
        try {
            // Show the on screen keyboard
            auto textField = dynamic_cast<TextFieldTTF *>(event->getCurrentTarget());
            textField->attachWithIME();
            return true;
        }
        catch(std::bad_cast & err){
            return true;
        }
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, textField);

    return true;
}

KeyTabletScene::~KeyTabletScene() {

}

bool KeyTabletScene::onTextFieldAttachWithIME(TextFieldTTF *sender) {
    return TextFieldDelegate::onTextFieldAttachWithIME(sender);
}

bool KeyTabletScene::onTextFieldDetachWithIME(TextFieldTTF *sender) {
    return TextFieldDelegate::onTextFieldDetachWithIME(sender);
}

bool KeyTabletScene::onTextFieldInsertText(TextFieldTTF *sender, const char *text, size_t nLen) {
    return TextFieldDelegate::onTextFieldInsertText(sender, text, nLen);
}

bool KeyTabletScene::onTextFieldDeleteBackward(TextFieldTTF *sender, const char *delText, size_t nLen) {
    return TextFieldDelegate::onTextFieldDeleteBackward(sender, delText, nLen);
}

bool KeyTabletScene::onVisit(TextFieldTTF *sender, Renderer *renderer, const Mat4 &transform, uint32_t flags) {
    return TextFieldDelegate::onVisit(sender, renderer, transform, flags);
}

 

And when you run it:

TabletKeyboardShot

 

Essentially when the user touches the screen, we display the onscreen keyboard with a call to attachWithIME(), the rest is handled by the textfield.

 

I have a sneaking feeling this method is going to be depreciated at some point in the future, being replaced by cocos::ui classes, but for now it works just fine.  For the record, it is actually possible to force up the onScreen keyboard by calling Director::getInstance()->getOpenGLView()->setIMEKeyboardState(true), but it seemingly pushes your scene to the background, so isn’t a viable option for controlling a game.  I was going to look into a work around but then thought, really… this is a downright stupid thing to do.  Doing anything other than text entry with a soft keyboard is just a bad idea.

 

 

Programming , ,