A complete HTML5 game from scratch using Phaser video tutorial

15. January 2015

 

I’ve been doing a long running Phaser with TypeScript tutorial series that illustrates basically everything you need to know to create a complete game using the Phaser HTML5 game engine.  I decided to put together a video tutorial showing how to put it all together to make a complete game.

 

Now, keep in mind, the game is incredibly simple… I wanted to keep the entire thing to under an hour after all ( I failed, it’s an hour and a half ), so I had to keep the scope reasonable in size.  So as a result I’ve created Extreme Walking Simulator!  Yeah, you walk… and that’s it actually.  You walk forever and ever in front of the same looping background.  However, you are left with a complete but simple game but most importantly, a framework of code that can be expanded upon to build much more complex games.  It also illustrates program flow, drawing sprites, playing music, changing states, handling input and all the other things a “full” game has to do.

 

In addition to the code, all of the assets used are available on GitHub.  This includes my incredible title music, the Blend files used for the main character and the level scene.  Everything you need to create this game is there in the Assets folder.  Feel free to play around and use it however you want.  That said, the textures just came from Google Image Search, so no clue if there is a copyright on any of them, so don’t use them in a commercial project! I believe all of my Github stuff is set to Creative Commons license…  if it’s not, assume you can do anything you want with it and that you have absolutely no warranty.

 

Ok, enough preamble, let’s jump right in!  The tutorial is available in two parts on YouTube in 1080p ( or embedded below):

 

 

Following are the game itself, then the source listings, finally the two videos in an embedded player.

 

EXTREME WALKING SIMULATOR!

 

EDIT: Removed to prevent music auto playing, to play the game click here instead. Warning, loud

Controls:

  • Click the title screen to get started
  • Tap right arrow to start walking/speed up
  • Tap left arrow to slow down/stop
  • Press ESC to stop the torture!

 

 

The Source Code

 

index.html

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Hello Phaser</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="phaser.js"></script>
    <script src="GameObjects/MyScene.js"></script>
    <script src="GameObjects/Player.js"></script>
    <script src="States/TitleScreenState.js"></script>
    <script src="States/GamePlayState.js"></script>
    <script src="States/GameOverState.js"></script>
    <script src="app.js"></script>
</head>
<body>
    <div id="content"></div>
</body>
</html>

 

app.ts

module Game {
    export class ExtremeWalkingSimulator {
        game: Phaser.Game;


        constructor() {
            this.game = new Phaser.Game(1280, 720, Phaser.AUTO, 'content', {
                create: this.create, preload: this.preload
            });
        }

        preload() {
            // Graphics
            this.game.load.image("title", "Graphics/TitleScreen.png");
            this.game.load.image("scene", "Graphics/scene720p.png");
            this.game.load.image("gameover", "Graphics/GameOver.png");

            //Spritesheets
            this.game.load.atlasXML("HERO_WALKING", "Graphics/Hero_Walking.png", "Graphics/Hero_Walking.xml");
            this.game.load.atlasXML("HERO_IDLE", "Graphics/Hero_Idle.png", "Graphics/Hero_Idle.xml");

            // Audio
            this.game.load.audio("TitleSong", ["Sounds/TitleSong.mp3", "Sounds/TitleSong.ogg",
"Sounds/TitleSong.wav"]); } create() { this.game.state.add("TitleScreenState", GameFromScratch.TitleScreenState, true); this.game.state.add("GamePlayState", GameFromScratch.GamePlayState, false); this.game.state.add("GameOverState", GameFromScratch.GameOverState, false); this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; } } } window.onload = () => { var game = new Game.ExtremeWalkingSimulator(); };

 

Player.ts

module GameFromScratch {
    export enum PlayerState { IDLE, WALKING }

    export class Player extends Phaser.Sprite {
        game: Phaser.Game;
        playerState: PlayerState;
        RIGHT_ARROW: Phaser.Key;
        LEFT_ARROW: Phaser.Key;
        ESCAPE: Phaser.Key;
        walkingSpeed: number;

        public static MAX_SPEED: number = 30;

        constructor(game: Phaser.Game, x: number, y: number) {
            this.game = game;

            
            this.walkingSpeed = 0;

            //Wire up input handlers
            this.RIGHT_ARROW = this.game.input.keyboard.addKey(Phaser.Keyboard.RIGHT);
            this.RIGHT_ARROW.onDown.add(Player.prototype.MoveRight, this);

            this.LEFT_ARROW = this.game.input.keyboard.addKey(Phaser.Keyboard.LEFT);
            this.LEFT_ARROW.onDown.add(Player.prototype.MoveLessRight, this);

            this.ESCAPE = this.game.input.keyboard.addKey(Phaser.Keyboard.ESC);
            this.ESCAPE.onDown.add(Player.prototype.GameOver, this);
            super(game, x, y, "HERO_WALKING", 0);

            this.anchor.set(0.0, 1.0);
            this.StartIdle();
        }

        update() {
            if (this.playerState == PlayerState.WALKING) {
                this.x += (this.walkingSpeed / Player.MAX_SPEED) * (60 / this.game.time.elapsedMS);

                // This logic depends on scene being added first.
                var stageWidth = this.game.stage.getChildAt(0).getBounds().width;
                if (this.x > stageWidth * .75)
                    this.x = stageWidth * .25;
            }
            super.update();
        }

        // Worse function name ever!
        MoveLessRight() {
            if (this.playerState != PlayerState.IDLE) {
                this.walkingSpeed--;
                if (this.walkingSpeed > 0)
                    this.animations.currentAnim.speed = this.walkingSpeed;
                else
                    this.StartIdle();
            }
        }

        MoveRight() {
            if (this.playerState == PlayerState.IDLE) {
                this.StartWalking();
            }
            else {
            if (this.walkingSpeed < Player.MAX_SPEED)
                this.walkingSpeed++;
                this.animations.currentAnim.speed = this.walkingSpeed;
            }
        }

        StartWalking() {
            this.playerState = PlayerState.WALKING;
            this.walkingSpeed = 5;
            this.loadTexture("HERO_WALKING", 0);
            this.animations.add("walk");
            this.animations.play("walk", this.walkingSpeed, true);
        }

        StartIdle() {
            this.loadTexture("HERO_IDLE", 0);
            this.playerState = PlayerState.IDLE;
            this.animations.add("Idle");
            this.animations.play("Idle",15,true);
        }

        GameOver() {
            this.game.state.start("GameOverState");
        }

    }
}

 

MyScene.ts

module GameFromScratch {

    export class MyScene extends Phaser.Sprite {
        game: Phaser.Game;
        nextFrame: Phaser.Sprite;

        constructor(game: Phaser.Game, x: number, y: number) {
            super(game, x, y, "scene", 0);
            this.nextFrame = new Phaser.Sprite(this.game, this.width, 0, "scene", 0);
            this.game.add.existing(this.nextFrame);
        }
    }
}

 

TitleScreenState.ts

module GameFromScratch {
    export class TitleScreenState extends Phaser.State {
        game: Phaser.Game;
        music: Phaser.Sound;

        constructor() {
            super();
        }

        titleScreenImage: Phaser.Sprite;

        preload() {

        }
        create() {
            this.titleScreenImage = this.add.sprite(0, 0, "title");
            this.titleScreenImage.scale.setTo(this.game.width/this.titleScreenImage.width, this.game.height
/
this.titleScreenImage.height); this.music = this.game.add.audio("TitleSong"); this.music.volume = 100; this.music.loop = true; this.music.play(); this.input.onTap.addOnce(this.titleClicked, this); } titleClicked() { this.music.stop(); this.game.state.start("GamePlayState"); } } }

 

GamePlayState.ts

module GameFromScratch {
    export class GamePlayState extends Phaser.State {
        game: Phaser.Game;
        player: GameFromScratch.Player;
        myScene: GameFromScratch.MyScene;

        constructor() {
            super();
        }

        preload() {
        }
        create() {
            this.myScene = new MyScene(this.game, 0, 0);
            this.player = new Player(this.game, 0, this.game.height - 50);

            this.game.add.existing(this.myScene);
            this.game.add.existing(this.player);

            this.game.world.setBounds(0,0,this.myScene.width * 2, this.myScene.height);
            this.game.camera.follow(this.player);
        }
    }
}

 

GameOverState.ts

module GameFromScratch {
    export class GameOverState extends Phaser.State {
        game: Phaser.Game;
        gameOverSprite: Phaser.Sprite;

        constructor() {
            super();
        }

        preload() {
        }
        create() {
            this.gameOverSprite = this.add.sprite(0, 0, "gameover", 0);
            this.gameOverSprite.scale.setTo(this.game.width / this.gameOverSprite.width, this.game.height /
this.gameOverSprite.height); this.input.onDown.add(() => { this.game.state.start("TitleScreenState", true); }, this);; } } }

 

 

 

The Videos

 

Part One:

 

Part Two:

Programming , , , , ,




Creating a walk cycle spritesheet in under 15 minutes

9. January 2015

 

I needed to create a sprite sheet for an upcoming tutorial series and managed to throw one together in an amazingly short amount of time with almost no artistic ability.  Good looking art with no ability is something many indie game developers are screaming for, so I figured I would share the process.

 

During the tutorial we use the following programs:

Mixamo Fuse (Free version available, MSRP $99USD)

Blender (Free and Open Source)

TexturePacker (Free version available, MSRP $50USD)

 

If you want more details on Mixamo, I extensively review it here.

 

The ultimate output from this entire process is the sprite sheet powering this animation:

bigguy

 

And here is the resulting sprite sheet, click it for the full resolution version:

a

 

Now finally, the video.  You can watch it in full 1080p on YouTube.

 

Coincidentally, if you want more information on how I created the above animated gif, I used a program called Cryotek Animated GIf Creator, and document the process here.  It’s a very cool program and completely free.

Programming, Art, General , , , ,




Phaser HTML5 game engine release version 2.2.2

7. January 2015

 

It’s most a maintenance release, so the list of new features is fairly sparse:

 


New Features
  • Phaser.Loader now supports BLOB urls for audio files (thanks @aressler38 #1462)
  • Line.reflect will calculate the reflected, or outgoing angle of two lines. This can be used for Body vs. Line collision responses and rebounds.
  • Line.normalAngle gets the angle of the line normal in radians.
  • Line.normalX and Line.normalY contain the x and y components of the left-hand normal of the line.
  • Line.fromAngle will sets this line to start at the given x and y coordinates and for the segment to extend at angle for the given length.
  • BitmapData.drawGroup draws the immediate children of a Phaser.Group to a BitmapData. Children are only drawn if they have their exists property set to true. The children will be drawn at their x and y world space coordinates. When drawing it will take into account the child's rotation, scale and alpha values. No iteration takes place. Groups nested inside other Groups will not be iterated through.

 

You can read the complete release notes here.

 

The release 2.3 milestones are available here.

 

They also made this announcement regarding version 3:

We're hard at work on Phaser 3. Development on the brand new renderer began in earnest last year and we're already seeing exceptional results from it. You can follow our development in the forum and public repo. Even though we're working on taking Phaser 3 into ES6 and the next generation of web browsers, we haven't stopped with the 2.x branch.

 

If Phaser sounds interesting to you, of course, Gamefromscratch has an extensive tutorial series available here.

Programming, News , ,




LibGDX Video Tutorial: Scene2D Actors and Actions

6. January 2015

 

In this second part of multiple part video tutorial looking at using Scene2D we look at creating Actors and Actions.  Actors are the entities that compose your game, while Actions are the things that make those Actors do… stuff.  The code for this example is included below the video.  The video on YouTube is available in high definition.

 

 

Actor Demo

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport;

public class ActorDemo extends ApplicationAdapter {

   Stage stage;

   @Override
   public void create () {
      ScreenViewport viewport = new ScreenViewport();
      stage = new Stage(viewport);
      Gdx.input.setInputProcessor(stage);

      MyActor actor = new MyActor();
      stage.addActor(actor);
      stage.setKeyboardFocus(actor);
   }

   @Override
   public void render () {
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      stage.act(Gdx.graphics.getDeltaTime());
      stage.draw();
   }
}
 
package com.gamefromscratch;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.actions.MoveByAction;

public class MyActor extends Actor {
    Sprite sprite = new Sprite(new Texture(Gdx.files.internal("badlogic.jpg")));

    public MyActor(){
        setBounds(sprite.getX(),sprite.getY(),sprite.getWidth(),sprite.getHeight());
        setTouchable(Touchable.enabled);

        addListener(new InputListener(){
            @Override
            public boolean keyDown(InputEvent event, int keycode) {
                if(keycode == Input.Keys.RIGHT){
                    MoveByAction mba = new MoveByAction();
                    mba.setAmount(100f,0f);
                    mba.setDuration(5f);

                    MyActor.this.addAction(mba);
                }
                return true;
            }
        });
    }

    @Override
    protected void positionChanged() {
        sprite.setPosition(getX(),getY());
        super.positionChanged();
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        sprite.draw(batch);
    }

    @Override
    public void act(float delta) {
        super.act(delta);
    }
}

 

Action Demo

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport;

public class ActionDemo extends ApplicationAdapter {

   Stage stage;

   @Override
   public void create () {
      stage = new Stage(new ScreenViewport());
      stage.addActor(new MyActor(new Texture(Gdx.files.internal("badlogic.jpg"))));
      Gdx.input.setInputProcessor(stage);
      stage.setKeyboardFocus(stage.getActors().first());
   }

   @Override
   public void render () {
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      stage.act(Gdx.graphics.getDeltaTime());
      stage.draw();
   }
}
 
package com.gamefromscratch;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.actions.*;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.addAction;


public class MyActor extends Image {

    @Override
    public void draw(Batch batch, float parentAlpha) {
        batch.setColor(this.getColor());

        ((TextureRegionDrawable)getDrawable()).draw(batch, getX(),getY(),
                getOriginX(),getOriginY(),
                getWidth(),getHeight(),
                getScaleX(),getScaleY(),
                getRotation());
    }

    public MyActor(Texture texture){
        super(texture);

        setBounds(getX(),getY(),getWidth(),getHeight());

        addListener(new InputListener(){
            @Override
            public boolean keyDown(InputEvent event, int keycode) {
                switch(keycode) {
                    case Input.Keys.NUM_1:
                        MoveToAction moveToAction = new MoveToAction();
                        moveToAction.setPosition(200f,200f);
                        moveToAction.setDuration(5f);
                        MyActor.this.addAction(moveToAction);
                        break;

                    case Input.Keys.NUM_2:
                        MoveByAction moveByAction = new MoveByAction();
                        moveByAction.setAmount(-200f,0f);
                        moveByAction.setDuration(3f);
                        MyActor.this.addAction(moveByAction);
                        break;

                    case Input.Keys.NUM_3:
                        ColorAction colorAction = new ColorAction();
                        colorAction.setEndColor(Color.PURPLE);
                        colorAction.setDuration(5f);
                        MyActor.this.addAction(colorAction);
                        break;

                    case Input.Keys.NUM_4:
                        MoveToAction mta = new MoveToAction();
                        mta.setPosition(Gdx.graphics.getWidth() - 200f,Gdx.graphics.getHeight()-200f);
                        mta.setDuration(3f);

                        ScaleByAction sba = new ScaleByAction();
                        sba.setAmount(2f);
                        sba.setDuration(3f);

                        RotateToAction rta = new RotateToAction();
                        rta.setRotation(90f);
                        rta.setDuration(3f);

                        ParallelAction pa = new ParallelAction(mta,sba,rta);
                        MyActor.this.addAction(pa);
                        break;

                    case Input.Keys.NUM_5:
                        MoveToAction mta2 = new MoveToAction();
                        mta2.setPosition(Gdx.graphics.getWidth() - 200f,Gdx.graphics.getHeight()-200f);
                        mta2.setDuration(3f);

                        ScaleByAction sba2 = new ScaleByAction();
                        sba2.setAmount(2f);
                        sba2.setDuration(3f);

                        RotateToAction rta2 = new RotateToAction();
                        rta2.setRotation(90f);
                        rta2.setDuration(3f);

                        SequenceAction sa = new SequenceAction(mta2,sba2,rta2);
                        MyActor.this.addAction(sa);
                        break;

                    case Input.Keys.NUM_6:
                        RunnableAction ra = new RunnableAction();
                        ra.setRunnable(new Runnable(){
                            @Override
                            public void run() {
                                MyActor.this.setPosition(0f,0f);
                                MyActor.this.setRotation(0f);
                                MyActor.this.setScale(1f);
                            }
                        });
                        MyActor.this.addAction(ra);
                        break;

                    case Input.Keys.SPACE:
                        addAction(parallel(
                            moveTo(200f, 200f, 3f),
                            scaleTo(2f, 3f),
                            rotateTo(90f, 3f)
                        ));
                        break;
                }
                return true;
            }
        });
    }
}

Programming , , ,




A Closer Look at the Godot Game Engine

4. January 2015

 

Gamefromscratch has a long running series taking an indepth look at various game engines available.  Today we are going to look at the Godot game engine, an open source C++ based game engine with a complete Unity-esque world editor.  Godot runs on Windows, Linux and Mac platforms and can target all of those, plus iOS, Android, PS3 and PS Vita, NaCL with HTML5 and Windows Phone both being in development.

 

In Godot’s own words:

Godot is an advanced, feature packed, multi-platform 2D and 3D game engine. It provides a huge set of common tools, so you can just focus on making your game without reinventing the wheel.

 

That description is incredibly apt as well, you will certainly be surprised by just how many tools are included.  Now let’s jump right in!

 

There is a video on this post available here (and embedded at bottom of the page) which goes in to a bit more detail.

 

The Editor

 

You get started with Godot by downloading the executable from the downloads page.  You probably expect this to be an installer, but you would be wrong.  Instead when you run it you get the Project Manager:

image

 

Here you can either create a new project or load an existing one.  Finally once you’ve selected or created, you are brought to the editor:

image

 

This is where the magic happens.  The above screenshot is of the Platformer demo being edited.  The left hand window is where your scene is composed.  As you can see from the four tabs above, you can work in 2D, 3D, Script editing or browse the built in help in this window.  The top left icons are actually menus and a lot of the functionality and tools are tucked away behind Scene and Import.

 

The top right hand dialog is your scene graph:

image

 

Here you can see ( and create/instance ) the items that make up your world.  Simply click the New Icon to add a new items to the world, or the + Icon to add a new instance instead.  The other icons are for wiring scripts and signals (events) up to the objects in your world.

 

Below the scene graph, you’ve got the Inspector window, which enables you to set properties of objects in your scene.  As you can see from the screen shot, Godot takes a very modular/component approach which is quite popular these days:

image

 

This enables you to visual inspect and edit properties of your game objects.  It also represents one of the first flaws of Godot… some of the controls are just awkward to use.  For example if you don’t hit enter after editing a text property, the values are lost.  Additionally modifying numeric fields can be a pain in the ass at times.  It’s all stuff that can be fixed with time ( Godot was only open sourced about a year ago after all ) but for now its clunky and somewhat annoying.

 

Coding

 

So that’s the visual editor… what about code? 

 

Well the majority of your programming is going to be done in GDScript, using the included editor.  GDScript is a Python-esque proprietary scripting language.  I don’t generally like this approach as it makes all the existing tools and editors worthless, lose the years of bug fixing, performance improvements, etc…  while forcing a learning curve on everyone that wants to use the engine.  That said, the idea behind a scripting language is they should be easy to use and learn.

 

The authors explained their decision in the FAQ:

 

The short answer is, we'd rather a programmer does the small effort to learn GDScript so he or she later has a seamless experience, than attracting him or her with a familiar programming language that results in a worse experience. We are OK if you would rather not give Godot a chance because of this, but we strongly encourage you to try it and see the benefits yourself.

The official languges for Godot are GDScript and C++.

GDScript is designed to integrate from the ground to the way Godot works, more than any other language, and is very simple and easy to learn. Takes at much a day or two to get comfortable and it's very easy to see the benefits once you do. Please do the effort to learn GDScript, you will not regret it.

Godot C++ API is also efficient and easy to use (the entire Godot editor is made with this API), and an excellent tool to optimize parts of a project, but trying to use it instead of GDScript for an entire game is, in most cases, a waste of time.

Yes, for more than a decade we tried in the past integrating several VMs (and even shipped games using them), such as Python, Squirrel and Lua (in fact we authored tolua++ in the past, one of the most popular C++ binders). None of them worked as well as GDScript does now.

More information about getting comfortable with GDScript or dynamically typed languages can be found here.

 

That covers the why anyways, now let’s look at the language itself.  As I said earlier, it’s a Python like (whitespace based) scripting language.  Let’s look at an example from the included demos:

extends RigidBody2D


const STATE_WALKING = 0
const STATE_DYING = 1

var state = STATE_WALKING
var direction = -1
var anim=""
var rc_left=null
var rc_right=null
var WALK_SPEED = 50

var bullet_class = preload("res://bullet.gd")

func _die():
   queue_free()

func _pre_explode():
   #stay there
   clear_shapes()
   set_mode(MODE_STATIC)
   get_node("sound").play("explode")
   

func _integrate_forces(s):

   var lv = s.get_linear_velocity()
   var new_anim=anim

   if (state==STATE_DYING):
      new_anim="explode"
   elif (state==STATE_WALKING):
      
      new_anim="walk"
      
      var wall_side=0.0
      
      for i in range(s.get_contact_count()):
         var cc = s.get_contact_collider_object(i)
         var dp = s.get_contact_local_normal(i)
         
         if (cc):
         
            
            if (cc extends bullet_class and not cc.disabled):
               set_mode(MODE_RIGID)
               state=STATE_DYING
               s.set_angular_velocity(sign(dp.x)*33.0)
               set_friction(true)
               cc.disable()
               get_node("sound").play("hit")
               
               break
            

         if (dp.x>0.9):
            wall_side=1.0
         elif (dp.x<-0.9):
            wall_side=-1.0
            
      if (wall_side!=0 and wall_side!=direction):
      
         direction=-direction
         get_node("sprite").set_scale( Vector2(-direction,1) )       
      if (direction<0 and not rc_left.is_colliding() and rc_right.is_colliding()):
         direction=-direction
         get_node("sprite").set_scale( Vector2(-direction,1) )
      elif (direction>0 and not rc_right.is_colliding() and rc_left.is_colliding()):
         direction=-direction
         get_node("sprite").set_scale( Vector2(-direction,1) )
         
         
      lv.x = direction * WALK_SPEED
         
   if( anim!=new_anim ):
      anim=new_anim
      get_node("anim").play(anim)
            
   s.set_linear_velocity(lv)


func _ready():
   rc_left=get_node("raycast_left")
   rc_right=get_node("raycast_right")

 

As someone raised on curly braces and semi colons it can take a bit of time to break muscle memory, but for the most part the language is pretty intuitive and easy to use.  You can see a quick language primer here.

Remember earlier I said the code editor was built into the engine, let’s take a look at that now:

image

 

As you can see, the editor does provide most of the common features you would expect from an IDE, a personal favorite being auto-completion.  Features like code intention, find and replace and auto indention are all available, things often missing from built in editors.   Like dealing with the Inspector window though their can be some annoyances, like the autocomplete window appearing as you are trying to cursor around your code, requiring you to hit Escape to dismiss it.  For the most part though the editing experience is solid and hopefully some of the warts disappear with time.

 

Now perhaps the biggest deal of all:

image

Debugging!  This is where so many home made scripting languages really suck, the lack of debugging.  Not Godot:

image

You can set breakpoints, step into/over your running code and most importantly inspect variable values and stack frames.  Once again, it’s the debugging experience that often makes working in scripting languages a pain in the ass, so this is nice to see!

 

Hey, What about C++???

 

Of course, one of the big appeals of Godot is going to be the C++ support, so where exactly does that come in?  Well first and most obviously, Godot is written in C++ and fully open source under the MIT license ( a very very very liberal license ), so you can of course do whatever you want.  I pulled the source from Github and built without issue in Visual Studio 2013 in just a few minutes.  The build process however is based around Scons, which means you have to Python 2.7x and Scons installed and configured, but neither is a big deal.

 

What about extending Godot, that is what the majority of people will want to do.  Well fortunately it’s quite easy to create C++ extensions, although again you need Scons and have to do a bit of configuration, but once you’ve done it once assuming you’ve got a properly configured development environment the process should be quick and mostly painless.  From the wiki page here is a sample C++ module:

 

Sumator.h

/* sumator.h */
#ifndef SUMATOR_H
#define SUMATOR_H

#include "reference.h"

class Sumator : public Reference {
    OBJ_TYPE(Sumator,Reference);

    int count;

protected:
    static void _bind_methods();
public:

    void add(int value);
    void reset();
    int get_total() const;

    Sumator();
};

#endif

Sumator.cpp

/* sumator.cpp */

#include "sumator.h"

void Sumator::add(int value) {

    count+=value;
}

void Sumator::reset() {

    count=0;
}

int Sumator::get_total() const {

    return count;
}

void Sumator::_bind_methods() const {

    ObjectTypeDB::bind_method("add",&Sumator::add);
    ObjectTypeDB::bind_method("reset",&Sumator::reset);
    ObjectTypeDB::bind_method("get_total",&Sumator::get_total);
}

Sumator::Sumator() {
    count=0;
}

 

Then in your script you can use it like:

var s = Sumator.new()
s.add(10)
s.add(20)
s.add(30)
print( s.get_total() )
s.reset()

 

If you inherit from Node2D or a derived class, it will be available in the editor.  You can expose properties to the inspector and otherwise treat your module like any other Node available.  Remember though, for productivity sake, you should really only be dropping to C++ as a last resource.  It is however quite simple to do.

 

Nodes, Nodes and more Nodes

At the heart of Godot, the world is essentially a tree of Nodes, so I suppose it’s worthwhile looking at some of the nodes available and how they work.  From the scene graph window, you add a new node to the world using this icon:

image

 

Next it’s a matter of picking which type, which could of course include modules you created yourself in C++.

image

 

As you can see from the small portion I’ve shown above, there are a LOT of built in nodes already available.  From UI controls, to physics controllers, path finding and AI tools, bounding containers, video players and more.  Essentially you create your game by composing scenes, which then are composed of nodes.  Once you’ve created a node you can then script it.  Simply select your node in the scene graph and then click the script icon:

image

 

Then a New Script dialog will be displayed:

image

 

And your script will be created:

image

 

As you can see, the script itself inherets from the node type you selected.  You can now script any and all logic attached to this particular Node.  Essentaily your game logic is implemented here.

 

In addition to wiring up scripts to game nodes, you can also wire up Signals.

image

 

Signals can be thought of as incoming events:

image

 

HELP!

 

So, what about Help then?  Well this is both a strength and weakness of Godot.  As you saw earlier, there is actually an integrated help tab.  You can look it up any time, or use press SHIFT + F1 while coding to get context sensitive help:

image

It’s invaluable, but unfortunately the resulting help file is often just a listing of methods/parameters and nothing more.  I often instead just keep the class reference from the Wiki open in a browser window.  For an open source project, the reference is pretty good, but it could still certainly use a lot more love.

 

Next are the tutorials, there’s a fairly good selection available on the Wiki but I think a good in-depth beginner series is really needed.  To someone that just downloaded Godot and is thinking “now what”… that presents a challenge. That said, I will probably be creating one, so this shouldn’t be an issue in time!

 

Finally, and perhaps most valuably, there are several demos included:

image

 

Publishing

 

So, once you’ve finished your game, how do you publish it to the various available platforms?  Well for starters you click the Export button:

image

 

You also need an export template, which is available from the Godot downloads site.  Additionally you need to  configure the tool chain for each platform, such as the Android SDK for Android, XCode for iOS ( and a Mac!  You can’t end run around the needing a Mac for iOS development requirement unfortunately), etc.  But the process is spelled out for you and they make it pretty easy.

 

Summary

 

I haven’t even really touched upon the plethora of tools tucked away in the editor…  need to import a font, there’s a tool for that.  There’s an improved Blender COLLADA plugin which just worked when I tried it.  There’s a tool for mapping controls to commands, there are tools for compressing and modifying textures on import, for modifying incoming 3D animations, etc…  Basically if you need to do it, there is probably a tool for it shoved in there somewhere.

 

On the other hand, the process itself can be pretty daunting.  Figuring out how to get input, a script’s life cycle etc isn’t immediately obvious.  It really is an engine you have to sit down with and just play around.  Sometimes you will hit a wall and it can be pretty damned frustrating.  However, it’s also a hell of a lot of fun and once it starts to click it is a great engine to work in.

 

I definitely recommend you check out Godot, especially if you are looking for an open source Unity like experience.  That said, calling this a Unity clone would certainly be doing it a disservice, Godot is a great little game engine on it’s own accord that deserves much more exposure.

 

The Video Version

 

Click here for the full resolution 1080p version.

Programming , , ,