Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
18. May 2018


It’s not very often a game engine takes me completely by surprise.  Especially a full featured, open source, C++ based, cross platform, heavily documented, feature rich, high performance 3D game engine.  Well that’s exactly what happened with the G3D Innovation Engine.  The primary maintainers are Morgan McGuire (@CasualEffects) who is currently an educator as well as a VR scientist at NVIDIA and previously worked on games such as Skylanders, Titan Quest and the Unity game engine, as well as Michael Mara at Standard University and Oculus Research.

The G3D Engine is self described as:

The G3D Innovation Engine is a commercial-grade C++ 3D engine available as Open Source. ss

G3D supports hardware accelerated real-time rendering, off-line rendering like ray tracing, and general purpose computation on GPUs. Its design emphasizes rapid prototyping and innovation, particularly of rendering and game algorithms.

G3D provides a set of routines and structures so common that they are needed in almost every graphics program. It makes low-level libraries like OpenGL, network sockets, and audio channels easier to use without limiting functionality or performance. G3D is a carefully designed, feature-rich base on which to prototype your 3D application.


Beyond being a capable engine it is also an incredible learning resource.  The engine is bundled with over 6GB of assets to experiment with, as well as over a dozen robust ss2samples with thoroughly documented source code.  One of the samples is even a full blown first person shooter, while another demonstrates a Minecraft-esque voxel based level.  There are also examples that show you how to work at the lowest level directly with OpenGL as well as advanced examples showcasing functionality such as real-time raytracing, lighting effects, procedural geometry and even VR.

Additionally each example can easily embed a suite of tools directly, enabling you to screen shot or video capture, change camera settings on the fly or launch the built in profiler.  There is even a complete scene editor built in, allowing you to place entities directly in your scene via simple drag and drop, turning your application into a minimalistic level editor.


Remember back at the beginning I mentioned that the maintainer was also an educator?  He has also authored a companion called the Graphics Codex which goes hand in hand with the G3D game engine.  For a mere $10 you gain access to an advanced reference that may just be one of the single best ways of learning computer graphics GIF2topics such as ray casting, BSDF, rendering and more.  You can see a full chapter list here.  So if you are trying to learn more advanced graphics programming, G3D is certainly a great resource.  Keep in mind however, this material was used with a 300s level graphics course, so you are going to need a solid foundation in math to follow along.

Purchasing the Graphics Codex is by no means a requirement however.  One thing open source projects often suffer from is poor documentation.  Thankfully this certainly isn’t the case with the G3D engine.   There is an extensive manual available here, as well as a comprehensive set of API references.  As mentioned earlier, the engine is also loaded with well documented samples.

If you are looking for a low level foundation to build your game on, a framework to do some graphical experiments or simply are looking for a way to learn more about modern graphics programming, I can think of little reason not to suggest checking out the G3D Innovation Engine. 


If you are interested in learning more about the G3D Innovation Engine, be sure to check out our hands-on video available here and embedded below.  I am almost certain you will be amazed.

GameDev News, Programming , ,

14. May 2018

Details


orx-project.org

ZLib License

View On YouTube

  

Orx is a cross platform open sourced 2D game framework written in C and available on Github under the permissive ZLib source license.  What makes Orx somewhat unique among it’s peers is the data driven approach that Orx takes.  Instead of storing game data using a collection of objects in code, it is instead split out into data files.  This enables you to quickly change the content of your game without having to do a recompilation.   So for example you make have an entry like the following for defining the camera in your scene:


[Viewport]
Camera            = Camera
BackgroundColor   = (255, 180, 0)

[Camera]
FrustumWidth  = 1024
FrustumHeight = 768
FrustumFar    = 1.0
FrustumNear   = 0.0
Position      = (0.0, 0.0, -1.0)
;Zoom          = 3.0

By editing this config file you can make major alterations to the camera without having to recompile your code.  Chance the FrustrumWidth in the ini file, and the next time you run your game’s window will be smaller.  Now let’s look at the code required to consume the Viewport/Camera data in your C/C++ application:

orxViewport_CreateFromConfig("Viewport");

Yep, that’s it.  This will create the viewport, notice the camera property and create one of them as well, entirely driven by data. 


This approach has a couple of advantages.  First it creates a clean separation of code and data which should in theory make for easier to maintain code.  It also takes these values out of the compilation process, meaning changes do not require you to recompile your code.  Additionally, this data driven approach makes creating tools for you game a cinch, as you are simply writing out simple easily understood text files. 


There are of course disadvantages too.  Since you moved the code out from the compiler, you lose features such as intellisense as well as compile time error checking.  Moving more details off into data files can make hunting down bugs even more difficult.


Let’s take a quick look at one of the samples from the Orx tutorials, with the comments stripped down for brevity.

#include "orx.h"
orxOBJECT *pstSoldier;

orxSTATUS orxFASTCALL EventHandler(const orxEVENT *_pstEvent)
{
  orxANIM_EVENT_PAYLOAD *pstPayload;

  /* Gets event payload */
  pstPayload = (orxANIM_EVENT_PAYLOAD *)_pstEvent->pstPayload;

  /* Depending on event type */
  switch(_pstEvent->eID)
  {
    case orxANIM_EVENT_START:
    {
      /* Logs info */
      orxLOG("Animation <%s>@<%s> has started!", pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));

      break;
    }

    case orxANIM_EVENT_STOP:
    {
      /* Logs info */
      orxLOG("Animation <%s>@<%s> has stopped!", pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));

      break;
    }

    case orxANIM_EVENT_CUT:
    {
      /* Logs info */
      orxLOG("Animation <%s>@<%s> has been cut!", pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));

      break;
    }

    case orxANIM_EVENT_LOOP:
    {
      /* Logs info */
      orxLOG("Animation <%s>@<%s> has looped!", pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));

      break;
    }

    case orxANIM_EVENT_CUSTOM_EVENT:
    {
       /* Logs info */
       orxLOG("Animation <%s>@<%s> has sent the event [%s]!", pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)), pstPayload->stCustom.zName);

      break;
    }
  }

  /* Done! */
  return orxSTATUS_SUCCESS;
}

/** Update callback
 */
void orxFASTCALL Update(const orxCLOCK_INFO *_pstClockInfo, void *_pstContext)
{
  orxVECTOR vScale;

  /* Is walk right active? */
  if(orxInput_IsActive("GoRight"))
  {
    /* Sets walk right as target anim */
    orxObject_SetTargetAnim(pstSoldier, "WalkRight");
  }
  /* Is walk left active? */
  else if(orxInput_IsActive("GoLeft"))
  {
    /* Sets walk left as target anim */
    orxObject_SetTargetAnim(pstSoldier, "WalkLeft");
  }
  /* No walk active */
  else
  {
    /* Removes target anim */
    orxObject_SetTargetAnim(pstSoldier, orxNULL);
  }

  /* Is scale up active ? */
  if(orxInput_IsActive("ScaleUp"))
  {
    /* Scales up the soldier */
    orxObject_SetScale(pstSoldier, orxVector_Mulf(&vScale, orxObject_GetScale(pstSoldier, &vScale), orx2F(1.02f)));
  }
  /* Is scale down active? */
  if(orxInput_IsActive("ScaleDown"))
  {
    /* Scales down the soldier */
    orxObject_SetScale(pstSoldier, orxVector_Mulf(&vScale, orxObject_GetScale(pstSoldier, &vScale), orx2F(0.98f)));
  }
}


/** Inits the tutorial
 */
orxSTATUS orxFASTCALL Init()
{
  orxCLOCK       *pstClock;
  orxINPUT_TYPE   eType;
  orxENUM         eID;
  orxINPUT_MODE   eMode;
  const orxSTRING zInputWalkLeft;
  const orxSTRING zInputWalkRight;
  const orxSTRING zInputScaleUp;
  const orxSTRING zInputScaleDown;

  /* Gets input binding names */
  orxInput_GetBinding("GoLeft", 0, &eType, &eID, &eMode);
  zInputWalkLeft  = orxInput_GetBindingName(eType, eID, eMode);

  orxInput_GetBinding("GoRight", 0, &eType, &eID, &eMode);
  zInputWalkRight = orxInput_GetBindingName(eType, eID, eMode);

  orxInput_GetBinding("ScaleUp", 0, &eType, &eID, &eMode);
  zInputScaleUp   = orxInput_GetBindingName(eType, eID, eMode);

  orxInput_GetBinding("ScaleDown", 0, &eType, &eID, &eMode);
  zInputScaleDown = orxInput_GetBindingName(eType, eID, eMode);

  /* Displays a small hint in console */
  orxLOG("\n- '%s' & '%s' will change the soldier's animations\n- '%s' & '%s' will scale the soldier", zInputWalkLeft, zInputWalkRight, zInputScaleUp, zInputScaleDown);

  /* Registers event handler */
  orxEvent_AddHandler(orxEVENT_TYPE_ANIM, EventHandler);

  /* Creates viewport */
  orxViewport_CreateFromConfig("Viewport");

  /* Creates soldier */
  pstSoldier = orxObject_CreateFromConfig("Soldier");

  /* Gets main clock */
  pstClock = orxClock_FindFirst(orx2F(-1.0f), orxCLOCK_TYPE_CORE);

  /* Registers our update callback */
  orxClock_Register(pstClock, Update, orxNULL, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);

  /* Done! */
  return orxSTATUS_SUCCESS;
}

/** Run function
 */
orxSTATUS orxFASTCALL Run()
{
  orxSTATUS eResult = orxSTATUS_SUCCESS;

  /* Should quit? */
  if(orxInput_IsActive("Quit"))
  {
    /* Updates result */
    eResult = orxSTATUS_FAILURE;
  }

  /* Done! */
  return eResult;
}

/** Exit function
 */
void orxFASTCALL Exit()
{
  /* We're a bit lazy here so we let orx clean all our mess! :) */
}

/** Main function
 */
int main(int argc, char **argv)
{
  /* Executes a new instance of tutorial */
  orx_Execute(argc, argv, Init, Run, Exit);

  return EXIT_SUCCESS;
}


#ifdef __orxMSVC__

// Here's an example for a console-less program under windows with visual studio
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  // Inits and executes orx
  orx_WinExecute(Init, Run, Exit);

  // Done!
  return EXIT_SUCCESS;
}

#endif // __orxMSVC__

This example illustrates a simple animation.  The game loop itself consists of implementing 3 simple functions, Init(), Run() and Exit() for handling various stages of the lifecycle.  The actual game loop itself is the Update callback function which is called continuously as your game is run.  Speaking of which, here is the code running.  The left and right arrow keys toggle between the animations, while up and down scale accordingly.

GIF


Now the “guts” of this example is actually in the ini file containing the data.  Let’s take a look:

[Display]
; In this example, we use the same size for the screen display than our camera's frustum so as to obtain a 1:1 ratio
ScreenWidth   = @Camera.FrustumWidth
ScreenHeight  = @Camera.FrustumHeight
Title         = Anim Tutorial
Smoothing     = false
[Resource]
Texture = ../data/object 

[Input]
SetList = MainInput

[MainInput]
KEY_ESCAPE = Quit

KEY_LEFT   = GoLeft
KEY_RIGHT  = GoRight
KEY_UP     = ScaleUp
KEY_DOWN   = ScaleDown

[Viewport]
Camera = Camera

[Camera]
FrustumWidth  = 640
FrustumHeight = 480
FrustumFar    = 1.0
FrustumNear   = 0.0
Position      = (0.0, 0.0, -1.0)

[Pivot]
Pivot = (15.0, 31.0, 0.0)

[[email protected]]
Texture = soldier.png

[Soldier]
Graphic             = Graphic
AnimationSet        = AnimSet
Scale               = 4.0

[AnimSet]
Direction   = right # down
StartAnim   = IdleRight
KeyDuration = 0.1
Digits      = 1
FrameSize   = (32, 32, 0)

Texture     = soldier_full.png
Pivot       = @Pivot

IdleRight   = 1 ; <= We only want one frame
IdleLeft    = 1
WalkRight   = -1 ; <= We want as many frame that can fit in the texture defined by WalkRight
WalkLeft    = -1

IdleRight-> = IdleRight 
IdleLeft->  = IdleLeft 
WalkRight-> = WalkRight 
WalkLeft->  = WalkLeft 

[IdleLeft]
Flip        = x

[WalkLeft]
Flip        = x

[IdleRight]
Direction   = left

[IdleLeft]
Direction   = left # up

[WalkRight1]
KeyEvent    = !!Left!!

[WalkRight4]
KeyEvent    = !!Right!!

[WalkLeft1]
KeyEvent    = !!Right!!

[WalkLeft4]
KeyEvent    = !!Left!!

As you can see, just about every aspect of the game is data driven here, from the animation names, the source graphic files, to the camera, viewport and even keys pressed and the corresponding binding to call for each key.


This post only scratched the service of how Orx works, but should give you an idea of the data driven approach they have taken.  It certainly wont be for everyone, but it could be a great fit for many people.  If you are interested in giving Orx a shot, be sure to check out our video, which is also embedded below, to walk through the installation process and get you up and running.  Then it’s time to jump into the extremely thorough getting started guide and tutorials available on the Orx website.

Programming , ,

21. November 2017


Up till this point in our ongoing Allegro Tutorial Series we have covered creating a window, handling a game loop, drawing sprites, handling input and playing audio, pretty much every aspect of creating a basic game.  In this final tutorial we are going to look at some of the other graphics capabilities in Allegro, specifically graphics primitives.


If you’ve been following the tutorial series till this point, it should come has no surprise that primitives are implemented as an add-on as well.  In this case it’s using the Primitives add-on:

image


Without further ado, let’s jump straight into the code example:

#include "stdafx.h"
#include <allegro5\allegro.h>
#include <allegro5\allegro_primitives.h>

int main()
{
	ALLEGRO_DISPLAY * display;

	al_init();
	display = al_create_display(640, 480);
	al_init_primitives_addon();
	float points[8] = {0.0f, 0.0f, 100.00f, 100.00f, 200.00f, 100.00f, 640.00f, 150.00f};

	float polygon[8] = { 640.0f, 100.0f, 640.0f, 300.0f, 380.0f, 350.0f, 200.0f, 200.0f };

	bool running = true;
	while (running) {
		al_draw_line(0, 0, al_get_display_width(display), al_get_display_height(display), al_map_rgb(255, 0, 0),5.0);
		al_draw_rectangle(100, 100, 300, 300, al_map_rgb(0, 255, 0), 1);
		al_draw_ellipse(300, 300, 120, 50, al_map_rgb(0, 0, 255), 3);

		al_draw_spline(points, al_map_rgb(128, 128, 0), 8);

		//al_draw_polygon(polygon, 8, ALLEGRO_LINE_JOIN_BEVEL, al_map_rgb(255, 15, 15),3,1);
		al_draw_filled_polygon(polygon, 8, al_map_rgb(255, 0, 0));
		al_flip_display();
	}

	al_destroy_display(display);
	
	return 0;
}

When you run this example you should see:

image


Granted, a pretty chaotic mess in the end, but illustrating a number of concepts in a single example.  First we start off by calling the appropriate init() method for the Primitives add-on.  When then draw a line in red ( R = 255, G = 0, B=0 ) from the top left to the bottom right corner of our display.  Next we illustrate drawing a rectangle, followed by an eclipse.  Finally we illustrate a slightly more advanced example of drawing a spline, then a polygon, by providing an array of points/vertices to draw.  As you can notice from the commented function, there are also functions specifically for drawing primitives fill with a solid colour.


We have only scratched the surface of the drawing functionality the Primitive add-on is capable of in this example, but the basic concept applies to all functions.


That concludes the tutorial series and should hopefully give you a good foundation on getting started with Allegro.  If you wish to jump into more depth with Allegro, be sure to check out the official manual available here.


Back To Table Of Contents

Programming , ,

13. November 2017


In the first tutorial we looked at creating our initial application, while in the second tutorial we looked at events and a cleaner game loop, in the third tutorial we looked at drawing bitmaps on screen.  We then moved on to handling the keyboard and mouse while in this tutorial we are going to cover handling audio in Allegro.


Once again, lets jump straight into the code example.

#include "stdafx.h"
#include <allegro5/allegro.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>

int main()
{
	ALLEGRO_DISPLAY *display = NULL;
	ALLEGRO_SAMPLE *sample = NULL;
	ALLEGRO_SAMPLE_INSTANCE *sampleInstance = NULL;

	al_init();
	al_install_audio();
	al_init_acodec_addon();
	
	al_reserve_samples(1);

	sample = al_load_sample("explode.wav");

	// The commented out version below is much easier
	//al_play_sample(sample, 1.0, 0, 1, ALLEGRO_PLAYMODE_LOOP,NULL);

	//However if you need access to additional control, such as currently playing position, you need
	//to attach it to a mixer, like the following example
	sampleInstance = al_create_sample_instance(sample);
	al_attach_sample_instance_to_mixer(sampleInstance, al_get_default_mixer());
	al_play_sample_instance(sampleInstance);

	// Loop until sound effect is done playing
	while (al_get_sample_instance_playing(sampleInstance)) {}

	al_destroy_sample_instance(sampleInstance);
	al_destroy_sample(sample);
	al_uninstall_audio();
}


At this point it should come as no surprise that audio support in Allegro is implemented as an add-on.  In fact it’s implemented as a pair of add-ons.

image


The audio add-on implements the audio playback functionality, while the Codec add-on is what supports loading the various different audio file formats.  Speak of file formats, Allegro supports the following file formats: .wav, .flac, .ogg, .opus, .it, .mod, .s3m, .xm and .voc.  Since they are implemented as add-ons, we once again need to call the corresponding init or install function before we can use audio functions.


Audio files are loading into memory as samples.  Before we go to far, we need to reserve resources for the samples we are going to use.  In this simple example we have only a single example, thus we call al_reserve_samples(1).  Now that our resources are allocated, we actually load the sample from disk with a call to al_load_sample(), in this case I am passing in a sample named “explode.wav”, which I have copied into my debug directory. 


Now that our sample is loaded into memory there are two ways to access it.  One is very simple, a fire and forget function you can see commented in the example above.

	al_play_sample(sample, 1.0, 0, 1, ALLEGRO_PLAYMODE_LOOP,NULL);

This is the easiest way by far to play audio in Allegro.  This example will play the sample “sample” at normal volume (gain), centered, at regular 1x speed, in a constant loop.


If you want a bit more control over the audio playback however, this function will no longer suffice.  So for example if you want to be able to determine when a sample is done playing, you instead need to create a SAMPLE_INSTANCE, which is done by passing our sample to al_create_sample_instance().  We also need a mixer to actually play the instance, which thankfully there is one already available for us, accessible using the function al_get_default_mixer(). 


Notice that both the sample and instance need to be cleaned up after use or they will leak resources.  Not shown in this tutorial, there are also streaming functions for breaking larger audio files up into smaller chunks for streaming.  In this day of abundant RAM, these functions are less used, so I have not covered them here.


Back to Table Of Contents

Programming , ,

10. November 2017


In the first tutorial we looked at creating our initial application, while in the second tutorial we looked at events and a cleaner game loop then in the third tutorial we looked at drawing bitmaps on screen.  Today we are going to look at handling keyboard and mouse input.  We covered this slightly in earlier tutorials when we looked at event queues and enabled the application to exit when the user pressed a key.  This tutorial will go into a great deal more depth.  Without further ado, let’s jump into the code sample:

#include "stdafx.h"
#include <allegro5\allegro.h>
#include <allegro5\allegro_image.h>

int main()
{
	ALLEGRO_DISPLAY * display;
	ALLEGRO_EVENT_QUEUE *queue;
	ALLEGRO_BITMAP * bitmap = NULL;

	al_init();
	display = al_create_display(640, 480);
	queue = al_create_event_queue();

	al_install_keyboard();
	al_install_mouse();

	al_register_event_source(queue, al_get_keyboard_event_source());
	al_register_event_source(queue, al_get_display_event_source(display));
	al_register_event_source(queue, al_get_mouse_event_source());

	al_init_image_addon();
	
	bitmap = al_load_bitmap("image64x64.jpg");
	assert(bitmap != NULL);

	bool running = true;
	float x = 0, y = 0;

	int width = al_get_display_width(display);
	while (running) {
		al_clear_to_color(al_map_rgba_f(0, 0, 0, 1));
		al_draw_bitmap(bitmap, x, y, 0);
		al_flip_display();

		ALLEGRO_EVENT event;

		if (!al_is_event_queue_empty(queue)) {
			al_wait_for_event(queue, &event);
			if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
				running = false;


			if (event.type == ALLEGRO_EVENT_MOUSE_AXES) {
				x = event.mouse.x;
				y = event.mouse.y;
			}

			if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) {
				x = y = 0;
				al_set_mouse_xy(display, 0, 0);
			}
		}

		// Actively poll the keyboard
		ALLEGRO_KEYBOARD_STATE keyState;

		al_get_keyboard_state(&keyState);
		if (al_key_down(&keyState, ALLEGRO_KEY_RIGHT))
			if (al_key_down(&keyState, ALLEGRO_KEY_LCTRL))
				x += 0.1;
			else
				x += 0.01;
		if (al_key_down(&keyState, ALLEGRO_KEY_LEFT))
			if (al_key_down(&keyState, ALLEGRO_KEY_LCTRL))
				x -= 0.1;
			else
				x -= 0.01;
	}

	al_destroy_display(display);
	al_uninstall_keyboard();
	al_uninstall_mouse();
	al_destroy_bitmap(bitmap);

	return 0;
}


When you run this application, the sprite will be drawn in the position of the mouse cursor.  Clicking and mouse button will reset the sprite’s position back to (0,0).  You can also control the position of the sprite by using the left and right arrow keys.

GIF

As we can see mouse and keyboard functionality is optional, so we need to call the appropriate init() function for both devices, then later the appropriate uninstall() function to clean them up.  Just like earlier we create an event queue, and register additional event sources for both keyboard and mouse.  As you can see, multiple event types can exist in a single event queue.

Mouse handling is extremely straight forward, when the mouse is moved it will fire a ALLEGRO_MOUSE_AXES_EVENT with the mouse location stored in the events mouse.x and mouse.y properties.  You can also see that mouse button activity fire a ALLEGRO_MOUSE_BUTTON_UP_EVENT when a mouse button is released.  In this case we set the position of the mouse using al_set_mouse_xy().

We take a slightly different approach with keyboard handling.  We could use a similar approach, however it would only handle one key event at a time.  What we instead want to do is poll the current status of the keyboard every pass through our game loop.  We take this approach so we can detect multiple concurrent key presses.  This is done by populating a ALLEGRO_KEYBOARD_STATE structure with a call to al_get_keyboard_state().  We can then poll the status of individual keys by calling al_key_down().


Back to Table Of Contents

Programming , ,

See More Tutorials on DevGa.me!

Month List

Popular Comments