General Overview

Raco edited this page Dec 6, 2017 · 132 revisions

A general feature overview, explaining the basics of each category, in a very concise manner (code snippets, descriptions that are short and to the point).

This is intended to serve as a "quick start" for somewhat experienced programmers; Others can (or would probably need to) watch the video tutorial series. Note that the referenced APIs relate to the bleeding-edge version of BDX, so if something doesn't work or exist in your local build, that might be why.

Also when we refer to settings or options in Blender, note that they are only visible when Blender Game is set as the render engine. To avoid potential confusion, ensure that the Render Engine is set to Blender Game, at the top of the window, where the main menu and preferences reside:

About BDX

BDX is a 3D game engine that leverages Blender (for its scene composition), LibGDX (for rendering graphics, taking input, playing audio, etc.), and Bullet (for physics). It also provides an API for carrying out common game tasks. Objects in Blender are turned into classes that extend a base class, GameObject.

When you export and run your game project, BDX seamlessly exports needed data from the project's .blend files and then imports that data when re-creating game objects and scene layouts. This import/export process is done for each scene in the .blend file, and can be done for every .blend file in the /blender directory. Feel free to rename the blend file from game.blend to whatever you prefer.

Workflow

You can export the necessary data and run your game project either by pressing the P key in the 3D view, or by pressing the "Export and Run" button from the BDX panel in the Render section. You also have the option to just export data, or just run the game, using the appropriately labeled buttons. This import/export process can take some time, depending on the complexity and number of the scenes exported. You can also run the command gradlew desktop:dist, using the gradlew (for OSX or Linux) or gradlew.bat (for Windows) executables in your project directory. This will build the .java classes in your source tree and open the game application, without the process of exporting data from Blender. You can, of course, put this command in a .bat file, or add it to a task or configuration in your chosen IDE (code editor) to make it easier to run quickly.

With "Multi-blend Export" on, BDX will export data from all blend files present in the blender directory. With this option off, only the current blend file will be used to export scene data.

When Multi-blend Export is on and Blender needs to export from multiple blend files, Blender will need to save and reload the original blend file after export to clear out orphan data left by the other blend files. Note that this will also destroy your undo-data (so only leave multi-blend export on if you're not doing very critical operations and tests, or if you only need to export from the current scene).

BDX Panel

Pictured above is the main BDX panel in the Render tab. This holds the main controls for starting and using BDX.

With "Differential Export" on, BDX will only export data from blend files that have been updated since the last time you ran an export. With it off, BDX will run a full re-export.

By combining these two options, you gain a couple of powerful advantages:

  1. With the game split up into multiple blend files, Blender loads less, making the actual development process much smoother.

  2. BDX handles multiple blend files by simply exporting data from blend files that have been changed since the last export. This means that the export process should be relatively speedy (since you'll probably only be working on one scene at a time). However, it will always export the current blend file, and will export all blend files when Blender first runs (not when a BDX project is first opened).

If "Auto-Export On Save" is checked, then BDX will automatically trigger an Export whenever you save the blend file. This can be very powerful when combined with Bdx.restartOnExport, as this can allow you to see the changes made in Blender in your game live.

The "Starting Scene" option is available to allow you to set which scene the game opens with. If the Starting Scene isn't set, then it will be set to whichever scene is the current one in Blender. Note that the camera resolution is still exported from the current scene (because of simplicity in the code-base).

Lastly, the Post-Export Program field is there to specify the name of a program to run after exporting data from Blender. This allows you to piggyback off of the export process to do other things. For example, you can write a program that outputs a list of all available exported scenes for perusal. The program should be placed relative to the project's home directory. On Linux, it needs its executable bit set (which you can do from a file manager or by using chmod +x program_name). If the field is blank, no program is run.

Execution Model

Behavior for an object in the Blender scene is defined via a Java class of the same name, or by specifying the desired class in the object panel in Blender. That class must extend from GameObject (directly or indirectly). For example, the class for an object named "Sacky" could look like this:

package com.yoursite.yourgame;

import com.nilunder.bdx.*;

public class Sacky extends GameObject{

	public void init(){
		// runs only once, when this object is created
	}

	public void main(){
		// runs every frame
	}

	public void onEnd(){
		// runs only once, when this object is ended
	}
}

In addition to running the GameObject's main method on every frame, the system will also run the main method of the currently active state on each Component you choose to add to the GameObject's components list.

With more complicated logic, you may be able to better organize/manage functionality across components.

The Bdx class itself also has a components list, enabling you to run "global" compartmentalized functions that can persist across scenes.

A GameObject can be set to run first by enabling the "Use Priority" button in Blender (next to the class name, in the object panel). This will make the Object execute first in the game loop. Note that only one GameObject can be set to have execution priority in a Scene.

User Input

BDX supports input via keyboard, mouse, and multiple gamepads. The state of commonly used input devices can be accessed via relevant objects on the Bdx class:

if (Bdx.keyboard.keyHit("space")){
	// code that should run once,
	// when space is initially pressed
}
	
if (Bdx.mouse.btnHit("left")){
	// code that should run once,
	// when the left mouse button is initially clicked
}

if (Bdx.gamepads.get(0).btnHit("ls-left")){
	// code that should run once,
	// when the left stick of the first gamepad is initially pushed left
}

There are corresponding "Down" methods:

if (Bdx.keyboard.keyDown("space")){
	// code that should run as long as space is held down
}
	
if (Bdx.mouse.btnDown("left")){
	// code that should run as long as the 
	// left mouse button is held down
}

if (Bdx.gamepads.get(1).btnDown("up")){
	// code that should run when up on the d-pad
	// of the second gamepad is held down
}

This distinction between "hit" and "down" methods is consistent throughout the API.

On touch-screen devices, Bdx.mouse.btnHit("left") will correspond to a single touch event. If you require multi-touch functionality, there is a collection of Finger objects you can use:

for (Finger finger : Bdx.fingers){
	if (finger.hit()){
		// code that should run once,
		// when the given finger touches the screen
	}
}

There are also "up" methods for things like key release (keyUp), mouse button release (btnUp), gamepad input release (btnUp), and finger lift (finger.up).

Details can be found in the API reference.

Note that while you can manually check for inputs through these methods, the more flexible and abstracted way would be to use InputMaps. You would define the inputs as named actions, and then check for those actions being triggered:

Bdx.imaps.put("left", "k:left", "g:left");  // Define the "left" action as left on the keyboard, or left on the Gamepad
Bdx.imaps.put("right", "k:right", "g:right");
Bdx.imaps.put("jump", "k:x", "g:a");

// And then later...

Vector3f moveVec = new Vector3f();

moveVec.x = Bdx.imaps.idown("right") - Bdx.imaps.idown("left");   // Move on the X-axis if you're pressing left or right

// Or, alternatively...

if (Bdx.imaps.down("right"))
    moveVec.x = 1;
else if (Bdx.imaps.down("left"))
    moveVec.x = -1;

velocity(moveVec);

In order to map a type of input, other than those provided in BDX, a custom FnHDU can be created and put into customDescriptorsHDU, so hit, down and up events can be evaluated internally.

InputMaps also support automatic input logging to create replays through its Logger class.

Object Physics

Physics menu

The "Physics Body Type", and the "Collision Bounds" (in the Blender physics properties panel when Blender Game is selected as an engine type at the top of the main window) will largely determine how a given object is simulated.

NOTE: If you get a crash involving ending static triangle mesh objects that are touching another object, it might be due to the collision shape (so try different collision shapes if it's not necessary that a certain shape is used).

Physics Body Types

Physics Body Type Description Can Check For Collisions With Physics Simulated
No Collision Completely unsimulated objects. This type is useful for objects that don't require physics simulation, like particle effects or a game state handler. None No
Static Static, non-moving objects. Unaffected by gravity, or any other forces, the Static body type is appropriate for walls, floors, and other general level geometry. Sensor, Dynamic, Rigid Body No
Sensor An unsimulated collision sensing body type. Unaffected by gravity and other forces (like collision impacts). Similar to static ghost objects. All Except No Collision No
Dynamic Moving objects that are affected by gravity and other applied forces, but with orientation fixed in place (a player object, a ball that doesn't roll, for example). All Except No Collision Yes
Rigid Body Fully physically simulated body type. Behaves similarly to dynamic, but without the orientation constraint (so a rigid body ball can roll, while a dynamic one will simply slide). All Except No Collision Yes

BDX also supports the Ghost status, in which objects may be one of the above body types, but not be able to physically collide with other objects. However, you can still check to see if the objects are touching / colliding.

Collision Bounds Types

The kind and shape of collision bounds a GameObject has, influences how it reacts to the world around it.

Collision Bounds Type Description Center of Gravity Primitive Scaling
Triangle Mesh The physics engine will create a mesh that directly corresponds to the mesh of the GameObject it's used on. Most accurate, but also takes more to simulate. Should not be used for dynamic or rigid body objects. Basically, your level geometry would be Triangle Mesh-type. inapplicable No Yes
Convex Hull The physics engine will create a physics mesh that closely corresponds to the mesh of the GameObject, using only convex (not concave) faces. Mesh's Position No Yes
Box A box that contains the mesh of the GameObject. Object's Position Yes Yes
Sphere The sphere's radius is calculated from the overall size of the object's mesh, such that the sphere contains the mesh. Object's Position Yes Yes
Cylinder A cylinder that's Z-oriented (so the flat sides point +Z and -Z, by default). The cylinder contains the mesh of the GameObject. Object's Position Yes No
Capsule A Z-aligned capsule (so it "rounds off" at the top and bottom (+Z and -Z) of the GameObject's mesh). The radius of the capsule is set by the width or depth of the object's mesh (whichever is larger). Object's Position Yes No
Cone A cone that contains the GameObject, and that points upwards, +Z. The bottom radius of the cone will be either the width or depth of the object's mesh (whichever is larger). Object's Position Yes No

It is good practice to use simple meshes for physics simulation to save processing power. Generally, you would create an invisible collision mesh that's separate from the display mesh, and then parent the display mesh to the collision mesh. This won't be necessary with the use of primitive bounds types. Note that when the mesh is transformed or when vertices are replaced it will be necessary to update the median point before updating the body. It should also be noted that scaling objects with bounds of type CYLINDER, CAPSULE and CONE will not scale the body.

BDX supports compound objects, which incorporate parented children into their collision shape. Parenting works as you would expect, with BDX automatically re-calculating objects' bounding shapes to incorporate their new children.

Note that physically solid objects interact only on their surfaces - if an object resides fully within another (and its mesh is hollow), the physics engine won't detect a collision.

Collision Groups and Masks

BDX also supports collision groups and masks, either edited through Blender's physics panel (where you can set the collision group and mask bitmasks), or through code, in the GameObject's class. The collision group and mask influences which objects an individual GameObject can collide with. These bitwise values need to match between GameObjects to trigger a collision. For an example, as represented in Blender's physics panel, if one GameObject has the first box pressed for the Collision Group and the second doesn't have that box pressed for the Collision Mask, they won't be able to collide with each other.

Leaving these values as default in Blender's physics panel means that every collision-enabled object can collide with every other collision-enabled object (as every object has every group as its mask, and every object belongs to the first group).


All objects can be moved:

position(x, y, z);
move(dx, dy, dz);

Dynamic or rigid body objects can be given a specific velocity, or an applied force:

velocity(x, y, z);
applyForce(x, y, z);

All these methods can take vectors, in addition to individual floating point component values.

To debug physics, enable "Show Physics Visualization" in the Game menu.

Collision Detection

if (hit("Platform")){
	// code that should run once, 
	// when an object named "Platform" is initially hit
}

if (touching("Platform")){
	// code that should run as long as
	// an object named "Platform" is touching
}

There is also a list of all objects currently in contact:

for (GameObject g : touchingObjects){
	// whatever you need to do
}

See the GameObject class for a bit more information on collision testing, including for objects with specific components or properties.

Spawning and Removing

New objects can be spawned via the scene reference, which is available on any GameObject.

// a clone of the original object named "Cat"
GameObject newCat = scene.add("Cat");          
newCat.position(7, 7, 7);

// a clone of newCat
GameObject newCatClone = scene.add(newCat);
newCatClone.move(1, 0, 0); // newCatClone.position() -> [8, 7, 7]

To remove an object, you simply call its end method:

// make everything I touch dissapear:
for (GameObject g : touchingObjects)
	g.end();

Spawnable objects can be objects that are currently in-game, or are in a hidden layer in the current scene. Group instances that are placed in Blender are swapped out for copies of the "original" objects, which means that placing a group instance in a hidden layer enables all objects in that group to be spawned in-game, as well. See the section on instances.

Substeps

The graphical framerate is now unlocked from the logical and physics tickrate through the addition of substeps. If the framerate drops such that the game cannot maintain its graphical framerate, the physics world will be stepped through again, and logic re-calculated, as well. This keeps the game world up to speed with the graphical representation. So, if the game normally runs at 60 FPS, but is slow at rendering and now is rendering at 20 FPS, then BDX can try using substeps to keep the game running at full speed. (60 FPS target / 20 FPS minimum = 3 substeps needed to keep game running at full speed)

Put more simply, a substep is basically just an event where BDX calculates physics and logic. Before, BDX was built to use only one substep for each rendered frame. Now it can use more, and will automatically use the appropriate number between the minimum and maximum substep counts. Physics and logic are both executed in a substep, and with more substep execution comes a finer physics simulation. By default, the minimum number of substeps is 1, but if you're making a precise physics-focused game, you could consider using higher minimum substep counts for a more precise physics simulation.

Components

A Component is a simple state machine, used to encapsulate a bit of (usually independent) behavior that can be applied to one, or many different GameObject derivatives.

There are a few general-purpose components in the com.nilunder.bdx.components package. Of those, the Halo component is the smallest, and it simply modifies the orientation of the GameObject, to make the object's Z axis point at the location of the currently active camera.

Here's the implementation:

public class Halo extends Component {

	public Halo(GameObject g){
		super(g);
		state = track;
	}
	
	private State track = new State(){
		public void main(){
			g.orientation(g.scene.camera.orientation());
		}
	};
}

Each component must extend Component, and the constructor must take an object that extends GameObject, which is then passed to super(). Also note that Component employs generics (Component<T extends GameObject>), so if you need access to custom parent methods via the g reference, you can specify the parent class type (Component<Player>), and then g will be of that class, instead of just GameObject.

The state variable references the currently active state, which we set to be the track state, initially.

Defining the track state is pretty simple, as you can see - We simply write an anonymous State class, with an overridden main method that contains the operations for that state.

A more involved component would typically contain multiple states, with logic to switch from one to the other, based on certain conditions.

To be processed by the system, a component must be added to the components list on the GameObject:

public void init(){
	components.add(new Halo(this));
}

In addition to running the GameObject.main method, the system will now also run Halo.state.main.

Components can be retrieved later using the get() function of the list, the argument being the name of the class of the component:

Halo haloComponent = (Halo) components.get("Halo");

Sprite Animation

The SpriteAnim component can be used to play animation sequences on a given piece of textured geometry (typically a plane). Your sprite sheet (texture) needs to represent sequences in rows, or columns of frames (the system assumes rows by default).

For example, given this 128x64 texture:

One could define sequences like this:

public void init(){
	sa = new SpriteAnim(displayPlane, 32, 32);
	sa.add("normal", 0, new int[]{0, 1, 2, 3});
	sa.add("buzzed", 1, new int[]{0, 1, 2, 3});
	components.add(sa);
}

The displayPlane is an object (typically a plane) with a texture that represents the sprite sheet. The two arguments that follow are the width and height of a single frame (32x32 in this case).

We add the relevant sequences via the add method; First argument is the name of the sequence, the second is the row on which the relevant frames reside, and the third is an array of frames to played, in the order specified.

The component is then added to the components list, so that it can be processed by the system.

Note that frames and rows are relative to the original texture coordinates set in blender. So, if our texture coordinates were originally set to be on the second frame of the buzzed animation:

The same sequences would have the following definitions:

sa.add("normal", -1, new int[]{-1, 0, 1, 2});
sa.add("buzzed", 0, new int[]{-1, 0, 1, 2});

With your sequences defined, you can then play them like this:

public void main(){
	Keyboard kb = Bdx.keyboard;

	if (kb.keyHit("a"))
		sa.play("normal");
	else if (kb.keyHit("d"))
		sa.play("buzzed");
}

The MeshAnim component works much the same way, but replaces the actual display mesh of the GameObject with another specified mesh for each frame of the animation (as necessary).

Sound and Music

On the main Bdx class, there is an object that represents all audio device and playback utilities - Audio. Supported audio files (wav, ogg, mp3) stored in android/assets/bdx/audio/sounds, and android/assets/bdx/audio/music, are loaded into HashMap-like containers (AudioStores) on the Audio class - Bdx.audio.sounds, and Bdx.audio.music.

Given sound_name.wav, stored in the sounds directory, you can play it like this:

Bdx.audio.sounds.get("sound_name").play();

Similarly, for a music_name.ogg, stored in the music directory:

Bdx.audio.music.get("music_name").play();

The distinction between BDX's sound class (BDXSound) and BDX's music class (BDXMusic) is the distinction between libgdx Sound and Music objects.

Altering global volume is similarly easy, as those same containers also have volume and panning controls (as well as pitch for sounds):

Bdx.audio.music.volume(0.5f);
Bdx.audio.music.get("Village").play();        // Plays back at 50% volume
Bdx.audio.music.get("Village").volume(0.5f);  // Playback is now set to 25% volume

Properties

BDX has access to blender game properties, visible in the logic panel or in the BDX section of the object panel:

They can be retrieved on the game object via the props reference:

int health;
int coins;

public void init(){

	health = props.get("health").asInt();
	coins = props.get("coins").asInt();
}

They can also be set via the same reference and using LibGDX's JSonValues:

import com.badlogic.gdx.utils.JsonValue;

public void init(){

    props.put("enemy", new JsonValue(true));
    props.put("health", new JsonValue(20));

}

Note that properties loaded in from Blender are shared amongst GameObject copies to save on memory. If you want to edit a property, you can by replacing the existing property with another.

public void init() {

    GameObject copyA = scene.add("Cube");
    // Assuming "Cube" has a property in Blender 
    // called "id", set to 1,

    copyA.props.get("id").set(2);
    // copyA.props.get("id") == 2

    GameObject copyB = scene.add("Cube");
    // copyB.props.get("id") == 2

    copyB.props.put("id", new JsonValue(1));    
    // copyB.props.get("id") == 1
}

Just be aware of this depending on the nature of your usage of properties (basically, try not to directly use them if possible; instead, store their value in instance variables).

Instances

While you can create new instances of an object via scene.add("ObjectName"), at runtime, it is sometimes more useful/convenient to place instances of a given master object in Blender, at design time.

For example, if I had an object named "Cloud" in the scene, and I wanted to create a few additional instances of that cloud, I can put the original Cloud object in a "cloud" link group (link group objects have a green outline):

The link group will then be visible in the Add menu, and by selecting it, you will create an instance of the original master Cloud (at the position of your 3D cursor, as with other added objects).

Each instance can have it's own transform, along with properties that will either add to, or override those already present on master. Changes made to master will automatically propagate to instances, of course, which is the core benefit of this technique. Instances can refer to groups composed of objects in other scenes, or even other blend files, as well as objects in the "current" scene.

Scenes

A scene in BDX corresponds to a scene in Blender, which is a collection of objects to be rendered, in order. Bdx.scenes is a list of active scenes, which will be rendered back to front. So, if that list looks like this:

[A, B, C]

A is rendered first, B is rendered on top of A, and C is rendered on top of B.

By default, the scene you're editing in blender, at export time, will be the only scene in the Bdx.scenes list at runtime. You can specify a specific scene to start the game with by writing in the name of the scene in the "Starting Scene" text box in the BDX panel of the Render pane / section.

To add another scene, you'll need to instance a new Scene, and then add it to Bdx.scenes:

Bdx.scenes.add(new Scene("SceneName"));

You can also just write the name of the scene to add (e.g. Bdx.scenes.add("SceneName")). To replace a scene at index 0:

Bdx.scenes.set(0, new Scene("SceneName"));

There's also "slotting" functions to move a scene to a position and move the other scenes around in the list, such that no objects are removed from the list, but rather rearranged. You also may use functions to easily move scenes up or down one position in the list. These functions are available on the ArrayListNamed class, which ArrayListScenes (The Bdx.scenes list) extends.

Note that scenes preserve their state, even after they're removed from Bdx.scenes, so if you keep the reference:

Scene old = Bdx.scenes.set(0, new Scene("NewSceneName"));

You can continue the old scene, from the last preserved state:

Bdx.scenes.set(0, old);

Please note, however, that to properly end and clean up scenes, you'll need to call Scene.end(). If you simply remove a scene from the Bdx.scenes list without calling the function to end it, it won't be able to remove unused resources and will result in a memory leak.

A Note About Scenes' Layers and Cameras

The current camera in Blender is the camera that is used for the scene in BDX, as well. You may need to ensure the camera for the scene is correctly specified and is in an active layer; if this isn't the case, Blender will spit out an exception to let you know. To make sure your camera is correctly specified for the scene, make sure the "Lock Camera and Layers" button next to the layer buttons in the 3D view is clicked, and that the 3D view is looking through the camera you want to use by default in the scene. The active layer(s) is also scene-specific. Pressing the Lock button will ensure that the active layer you see is the active layer for the Scene that BDX uses.

Viewport

Every Scene will have it's viewport, which can be sized and positioned. The viewport type is initially set by the "Framing" in the Display tab of Blender's Render options, but can be changed in BDX to LETTERBOX, EXTEND, SCALE or SCREEN.

framing

SCALE will stretch the view to fill the display screen without preserving the aspect ratio. LETTERBOX will stretch the view while preserving the aspect ratio by adding bars to fill the display screen. And EXTEND will stretch the view while preserving the aspect ratio by extending (or contracting) the width and fitting the height to fill the display screen. SCREEN will only stretch when manually changing the resolution or size, and is only settable through code, on the Viewport itself (e.g. scene.viewport.type()).

Text

Text objects in Blender will be converted to BDX Text objects. This conversion process is not perfect, so, with certain fonts, you will probably notice position and/or scale discrepancies (position/scale of text at BDX runtime won't correspond to that visible in blender).

To compensate for those discrepancies, you can adjust the font size and paragraph offset values in text properties, until what you see in blender is close enough to what you see in BDX.

Beyond that, things should be fairly simple - a Text object is simply an extended GameObject, and you can manipulate it in the same way, with the additional ability to get and set the currently displayed text string, via the text methods. Newline escape characters (\n) are supported now.

The number of characters that a given Text object can display is determined by the length of the original text displayed by that object in blender. This means that your text objects in blender should be set to display the longest string they could potentially display at runtime, because anything longer will simply be cut down to fit into that originally defined capacity.

Text Properties

Text appearance can be customized through the "BDX" panel in the Text section of the Properties pane.

The "BDX" font panel

On run, BDX will utilize an external application to create textures for each font used in the blend file. The font size option in the panel indicates how large the text will appear on the texture, which is tied to how high-resolution the font will appear in-game. You can customize the font color outputted to the texture in this panel, as well. Of course, you can always customize the final multiplicative or additive color of the text object's material itself, as well.

Shadow can be enabled and tweaked by altering the shadow offset value. When the shadow offset is specified, the other shadow options will appear (and similarly for outlines). The "Rounded Outlines" option influences if the outlines of the text is rounded or not. Try it yourself to see the differences - this option was available in LibGDX, so we made the option available here as well.

Usually, BDX will only export new font textures if the texture and associated font settings file don't exist. With the "Always Export Fonts" option on, BDX will always export fresh textures and settings using the options you specify. This means that you can more easily make tweaks to your text object's font settings and see the changes in action quickly.

Text objects can also have their alignments altered, either in Blender (using Left, Center, or Right), or in-game using the Text class's alignment() setter.

Lighting

Lighting is accomplished using BDX Light objects. Currently, ambient, point, spot, and sun lights are supported, as well as shadeless (unlit) materials. Lights are created simply by creating the relevant light in Blender. Lights can also be added in-game by using the Scene's add() function with the name of the light object in Blender as the argument. See the Materials section below to see what elements of a material can influence the lighting of a game object.

Under the hood, lighting now by default uses per-pixel calculations, which means smoother, but also more costly, lighting. Each light type has a maximum light count, which can be tweaked. If there are more lights than maximum, the lighting shader attempts to add the excess lights into the vertex lighting equation.

Reduce the maximum light count or turn off per-pixel lighting (thereby falling back to a set of slightly more advanced versions of LibGDX's default vertex-based lighting shaders) to reduce the load on the GPU and CPU.

Ambient lighting can be adjusted to alter the "dark level" of the scene; an ambient level of complete white will make the entire scene appear unlit. Note that Blender shows this differently from how it appears in-game. To adjust the ambient lighting, one simply changes the ambient lighting color in the World panel with Blender Game set as the render mode.

You can also use the scene's ambientLight() functions to alter it in-game.

The background color (clear color) of the game window is automatically set by the "Horizon Color" shown above. If you wish, you can also set the color in-game using the Bdx.display.clearColor() function call.

Fog

Fog support has been added into BDX, which means a new set of fog-related functions on the Scene class as well as the ability to turn on and alter fog settings from Blender. The fog options available under the "Mist" heading in the World tab are the ones you'll tweak. You can turn it on or off, of course, and set the start and depth of the fog. The different falloffs and minimum intensity aren't supported as of yet, though.

Materials

Materials are the means through which you customize how objects are rendered or show up in-game. A lot of the material set-up can be done through Blender's material panel, while some also have functions available for in-game alteration (color changes through a GameObject, custom Material shaders, etc).

With "Blender Game" set as the render engine, the following material properties are supported in BDX:

  • Diffuse color
  • Diffuse texturing (applied by adding an Image texture to the texture panel for the material)
  • Specular color
  • Specular hardness
  • Emit value (allows you to set the base light level for the material)
  • Shadeless-ness
  • Backface Culling
  • Transparency (On / Off)
  • Opacity
  • Friction
  • Restitution
Textures

Textures are applied through materials in much the same way as they are under Blender - you add a Texture resource of Image type to the Material, and then select the image file that you want to read from. Unwrap your object in the usual manner, and that's it. Textures should be placed in the associated directory in the android/assets/bdx/textures directory, of course.

Textures are automatically set to Multiply blending mode, not Mix, as in Blender. So, the diffuse color of the material (which is also the color that is altered through material color-setting function calls) modulates the color of the texture.

Objects, Materials and You

In Blender, meshes have material slots. You can add or remove a slot by pressing the + and - buttons in the material panel, and can move materials between slots by simply pressing the Up or Down arrow buttons. You place materials into these slots, and assign vertices to these slots in Edit Mode to have the mesh render with specific materials in specific places; by default, the entire mesh is assigned to the first slot.

Well, these slots are also implemented in BDX. When you run your game, BDX will export the material data for each material used automatically, and of course, link that material to the vertices on the GameObject's Mesh. Each mesh is split up into multiple pieces depending on the material. As a side-note, it might be a good idea to use as few materials as possible to keep the engine from switching between Materials unnecessarily when rendering.

In any case, each slot in the material panel corresponds to a material slot (a MeshPart, in LibGDX terminology) on the GameObject's Mesh. Usually, the slots in the Material panel correspond directly to the sequence of materials on the mesh; you can see which materials are currently "equipped" on a GameObject by using the Mesh.materials ArrayList. However, under some circumstances, the list will differ. Here's an example.

Blender Material Example

In the provided picture, there are four material slots for the object depicted. Going from the top down, we have the following materials: "White", "Green", "Blue", and "White" again. If you printed out the Platform Mesh's materials ArrayList, you might expect an ArrayList with the strings: ["White", "Green", "Blue", "White"], in that order. Well, there are a couple of notes that explains what the actual result would be:

  1. If there's a material in a slot that doesn't have vertices assigned to it in Blender, that material will not be used, and that slot will not exist in BDX. In the example picture, the Blue material is not used on the mesh, so it won't be used on the mesh in BDX, and there won't be a slot for it.

  2. If a material in one slot matches names with a material in a previous slot, the slot will "merge" with the original slot. All vertices from the second slot will then be assigned to the first slot. This will all happen so that the first slot with the material will remain. So basically, BDX will automatically "compile" multiple slots with a single material down into a single slot.

With these two notes in mind, the GameObject in the picture above will have two slots in BDX. The materials in the slots would be the White, and then the Green one.

You can change a material in a slot by using one of the Mesh.materials.set function calls, and a slot number to assign the material to.

NOTE: It used to be possible to set material properties (like the color) of GameObjects through functions present on the GameObject itself. This has been updated to be possible through the Material class, as that provides a more logical entry point for changes to the visual appearance of GameObjects.

About Transparency

Currently, BDX supports transparency both in Blender's Material menu and in-game through the material color setting functions. BDX does not (reliably) currently support transparent per-face sorting (so having an object with a transparent face in front of another face on the same object may not display the transparency correctly.

BDX uses LibGDX's built-in back-to-front sorting to determine in what order transparent objects should be rendered, and this approach is based on object positions. So basically, if your transparent objects, like particles, are rendering oddly, not blending correctly with objects behind them, ensure that the transparent mesh is centered on the object origin in Blender.

Blending Factors

Blending modes can be set with Material.blendMode(int src, int dest). OpenGL Blending Factors are explained in this table below. S, D, C, R, G, B, A stand for Source, Destination. Color, Red, Green, Blue and Alpha.

com.badlogic.gdx.graphics.GL20 constant RGB Blend Factors Alpha Blend Factor
GL_ZERO (0,0,0) 0
GL_ONE (1,1,1) 1
GL_SRC_COLOR (Rs,Gs,Bs) As
GL_ONE_MINUS_SRC_COLOR (1,1,1) – (Rs,Gs,Bs) 1 – As
GL_DST_COLOR (Rd,Gd,Bd) Ad
GL_ONE_MINUS_DST_COLOR (1,1,1) – (Rd,Gd,Bd) 1 – Ad
GL_SRC_ALPHA (As,As,As) As
GL_ONE_MINUS_SRC_ALPHA (1,1,1) – (As,As,As) 1 – As
GL_DST_ALPHA (Ad,Ad,Ad) Ad
GL_ONE_MINUS_DST_ALPHA (1,1,1) – (Ad,Ad,Ad) 1 – Ad
GL_CONSTANT_COLOR (Rc,Gc,Bc) Ac
GL_ONE_MINUS_CONSTANT_COLOR (1,1,1) – (Rc,Gc,Bc) 1 – Ac
GL_CONSTANT_ALPHA (Ac,Ac,Ac) Ac
GL_ONE_MINUS_CONSTANT_ALPHA (1,1,1) – (Ac,Ac,Ac) 1 – Ac
GL_SRC_ALPHA_SATURATE min(As, 1-Ad), min(As, 1-Ad), min(As, 1-Ad) 1

Custom Normals

Meshes in Blender can also have customized normals. This is activated like so:

import bpy
ob = bpy.context.scene.objects.active
me = ob.data
me.use_auto_smooth = True
bpy.ops.mesh.customdata_custom_splitnormals_add()
me.normals_split_custom_set_from_vertices([v.normal for v in me.vertices])

Normals could now be edited, for instance by using the Data Transfer modifier:

cn = ob.modifiers.new(name="Copy Custom Normals", type="DATA_TRANSFER")
cn.object = ob_base
cn.use_loop_data = True
cn.data_types_loops = {"CUSTOM_NORMAL"}
bpy.ops.object.modifier_apply(modifier=cn.name)

Now it would appear that the mesh is updated completely as the viewport shows a fully updated mesh. However, the normals attribute of the mesh in Blender is not updated yet. BDX will update this attribute before exporting the mesh, so you shouldn't have to do this:

me.calc_normals_split()

Shaders and Post-Processing

One can customize the rendering of their game through utilizing ScreenShaders, and Shaders.

Just like it sounds, ScreenShaders are shader programs that are used specifically to create layers of post-processing over the screen. They change how a scene's render is drawn by applying a shader program to the rendered output. Bloom, blur, depth of field - all of these are examples of ScreenShaders. One adds a ScreenShader to the game through the Scene.screenShaders ArrayList.

Shaders, on the other hand, are general and are what ScreenShaders inherit from. "Ordinary" Shaders are used to customize the render result of individual GameObjects. One applies a Shader by loading or creating the Shader and setting it to a Material's shader field.

Debugging and Profiling

BDX has a couple of options available for debugging and profiling your game - the profiler and the physics visualization.

The Physics Visualization

The physics visualization is a visual representation of the physical state of your game world. The visualization is presented through a colored wireframe version of the game, layered over the view of each scene.

If an object doesn't have a wireframe visualization, that means that it has no physical form, and will basically be intangible. The red cube surrounding meshes are their bounding boxes, and the wireframe mesh represents their collision (physical) mesh. If it's white, that means it's active, and physically responding to impulses. If it's green, that means it's inactive, or sleeping. The physics engine, Bullet, causes objects that aren't undergoing influences from forces or moving to go to sleep to save on CPU cycles. Bullet or BDX will automatically wake them up as necessary.

You enable the physics visualization from the Game menu, at the top of Blender's main menu.

An example picture showing the physics visualization, overlaid on the scene, and the profiler, visible in the upper-left corner.

The Profiler

The profiler is the method through which you can both see how the game is performing, as well as profile your own code. The profiler is enabled through the "Show Framerate and Profile" option in the Game menu (at the top of Blender's main menu).

The profiler automatically profiles the engine's performance through different categories, which are determined by which element of the game that the CPU spent time on. The headings are listed with their names for each category, followed by the amount of milliseconds spent on that category. The next value is the percentage of the time the CPU spent on this frame for that category, and finally, a small bar graph showing the relation between the categories.

The description of the headings are as follows:

Profile Category Description
Render Time spent running CPU render functions (not time spent actually rendering)
Logic Executing game logic through classes and components
Scene Internally updating the camera and locations of GameObjects and their children
Physics Simulating the physics engine, detecting collisions
Outside Time the CPU did not spend this frame (i.e. that should be open to the OS for other applications "outside")
GPU Wait The amount of time the CPU spends waiting for the GPU to finish rendering

GPU Wait is a bit of a special category. While its percentage adds up along with the other categories, the milliseconds listed here are shown in addition to the milliseconds from the other categories. This is because the CPU is waiting for the GPU to finish its rendering jobs, which means that the CPU would still have free time to calculate. Under normal circumstances, the GPU Wait value would most likely be 0. Any amount of time in this category indicates that your game is GPU-bound. This means that for optimization, you could firstly look into lessening the load on the GPU (lowering the quality or detail of active screen shaders, for example). Feel free to check out this blog post Microsoft put out talking on the subject of how the GPU works in relation to the CPU.

If you're looking to optimize your code, enable the profiler and take a look at these categories. If something stands out (like, say, spending 95% of the CPU time on physics), then look into that particular category. You'll probably find the issue, which can hint to how you can improve things (i.e. using lower poly collision meshes, or using a built-in mesh for physics simulation).

For further optimizations (particularly in terms of memory usage or CPU drain from executing game logic), it's worth looking into using an external Java profiler, like VisualVM, to locate particularly heavy functions.

As can be seen by the extra field, "X", in the profiler screenshot above, you can customize the profiler to add values for debugging and time your own code. You can also enable OpenGL profiling to display additional render-related information, such as the number of draw calls, or the number of shader switches. Check the Profiler API page for more information.

Saving and Loading

Being that BDX is built on top of LibGDX, you may use the same data handling systems that LibGDX affords you, like Preferences or File Handling, to save and load your game data. Note that Preferences are generally easy to use, but store in a fixed location, and are the only saving mechanism available for the HTML5 target. If you want to use local saving in a different location for the desktop target (like, for example, in the game's application directory), Local FileHandles should be helpful.

Here's a fairly quick-and-dirty example using LibGDX's included json package:

public void save() {

    HashMap<String, Object> data = new HashMap<String, Object>();

    // Add the music volume to the data to be saved
    data.put("musicVolume", Bdx.audio.music.volume);

    // Actually save the data out to a file named "Options.txt", and overwrite whatever might already be there
    Gdx.files.local("Options.txt").writeString(new Json().toJson(data), false);

}

public void load() {

    FileHandle f = Gdx.files.local("Options.txt");

    // Only load if the file exists
    if (f.exists()) {

        // Parse the save file into JSON
        JsonValue saveData = new JsonReader().parse(f);

        // Retrieve and set the value
        Bdx.audio.music.volume = saveData.get("musicVolume").getFloat("value");

    }

}

Packaging and Distribution

To distribute your BDX game, the process is the same as deploying and distributing a LibGDX title. You use the gradlew or gradlew.bat applications, depending on your OS, to export your game project. When running the gradlew application, you use command line arguments to specify which kind of distribution you want to create. For example, gradlew desktop:dist will create a distributable .jar file for desktop. By default, it will be called desktop-1.0.jar and be in the desktop/build/libs directory of your project. See the LibGDX wiki on the subject for more information.

One is also able to package a Java Development Kit (JDK) in with the game project and create a native executable by utilizing packr. When using packr, you can provide the arguments through the command-line or use a configuration file that provides the packaging configuration. Note that for the classpath, you only need to specify the location of the game jar file (so you don't need to, for example, also package your assets, since they're contained in the jar already). Here's a simple example packr configuration, saved in a .json file (which is fed into packr when ready):

{

  "platform": "linux64",
  "jdk": "/home/yourusername/JDKs/zulu8.19.0.1-jdk8.0.112-linux_x64/",
  "executable": "YourGameName",
  "classpath": [
    "desktop/build/libs/desktop-1.0.jar"
  ],
  "mainclass": "com.yourusername.yourgamename.desktop.DesktopLauncher",
  "vmargs":[
    "Xmx2G"
  ],
  "minimizejre": "soft",
  "output": "packed/linux64/"

}

The result is written, as indicated, in the linux64 folder, inside of a folder called packed (of course, you may name it what you wish). Note that packr only supports zip files for compressed files containing JDKs - if you have them compressed in another file (like .tar.gz or .7z), you'll need to extract them to a folder, and then point to that folder instead of the file. You may also point to a URL of a zip file containing the JDK.

As for actually getting ahold of pre-built runtime environments to package, Azul Systems has made their 64-bit builds of OpenJDK (Zulu) freely available. Distributing Oracle's official JREs or JDKs also seems to be a fine option (see the FAQ on JRE / JDK distribution). Note that packr specifically expects to find JDKs, not JREs.

You could also simply package the JDK or JRE of your target platform along with your game .jar file manually.

Music files

Note that for whatever reason, WAV music files do not play back correctly when a BDX project is distributed. This isn't a huge deal, though, as WAV files can be huge. OGG is a compressed file format that plays back just fine in distributed games. An easy way to convert your music directory's WAV files to .OGG files is to use the following Python 3 script with FFMPEG installed on your computer (where FFMPEG is installed on your path, and you can type ffmpeg into a terminal / command prompt to run FFMPEG from wherever).

When run from your root project directory, the script will convert WAV and MP3 files in the specified target directory (the music folder, by default) to OGGs, silently overwriting destination OGG files if they already exist, and deleting the original (now unnecessary) source WAV or MP3 files.

import os, subprocess

j = os.path.join

targetDir = "android/assets/bdx/audio/music"

p = j(os.getcwd(), targetDir)

print("Converting")

for f in os.listdir(p):

    fileInfo = os.path.splitext(f)

    if fileInfo[1] == ".wav" or fileInfo[1] == ".mp3":  # You can change the file extensions searched for here

        n = j(p, fileInfo[0])

        print(fileInfo[0])

        subprocess.check_output(["ffmpeg", "-i", n + fileInfo[1], n + ".ogg", "-loglevel", "quiet", "-y"])

        os.remove(n + fileInfo[1])

print("Conversion Finished.")

For GWT (HTML), only certain Java functions that have Javascript emulated equivalents, or custom packages / files that have sources can be used when building. In short, if you have trouble distributing a GWT build due to functions in Java that aren't emulated when creating the GWT build, check this page to see the list of Java functions that are supported.

If you have trouble due to gradle reporting as follows:

No source code is available for type ______________; did you forget to inherit a required module?

and you are using code from outside your main game source folder (like if you put some code in src/com/compname/otherproject that you're using and importing into your main source), you need to add the folder source path to BdxApp.gwt.xml in your core/src folder. By default, there will be a <source path="blah/blah/yourgame" /> tag; add another one above it that points to the other folder. Here's a simple example of my BdxApp.gwt.xml for my project with another source folder next to my game's:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit trunk//EN" "http://google-web-toolkit.googlecode.com/svn/trunk/distro-source/core/src/gwt-module.dtd">
<module>
	<inherits name='com.Bdx' />
	<source path="com/solarlune/bdxhelper" />  < Another folder, which you're using
	<source path="com/solarlune/pdp" />  < Your game folder
</module>

Icons

The icon for the game window and the game's icon in the taskbar are set automatically using the icon files present in the android/assets/bdx folder. There are three different sizes of icons - 128x128, 32x32, and 16x16, as different operating systems require different sizes (for some reason). Of course, you may modify these icons to match the look and feel of your game.

You also are able to add more icons yourself in your DesktopLauncher.java class (in the desktop directory). You can read up a bit more on this in LibGDX's writeup on adding icons here.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.