15. April 2014

One very common requirement for even the most simple game is a level editor.  The level of sophistication required varies massively from game to game but a lot of the functionality is pretty common.  At the very base level, you need a tool to layout the graphics that make up your world.  Slightly more advanced, you need to define layers, properties and collision volumes.  Often people roll their own solution but you certainly don’t have to.  One very popular 2D level editor is Tiled Map Editor which exports in TMX format, perhaps the most supported 2D game format ( Cocos2D, LOVE, LibGDX and many others all support TMX out of the box ).  As I am going to be writing a tutorial about using tiled maps in LibGDX, I figured I would give a quick introduction to Tiled first.  Keep in mind, we are only going to scratch the very surface of what Tiled is capable of.

First off, download and install Tiled.  It has binaries available for Windows and Mac and a source (and daily builds) release available for Linux.  You can download Tiled here.  The documentation is available here.

The Tiled UI is pretty straight forward, although it looks quite different across platforms.  Today I will be using the MacOS version.

A tiled map is fundamentally simple.  You are basically making a grid of tiles.  A tile is a fixed size image within a larger image.  The larger image is called a tile sheet.  It’s somewhat like working with legos to make a level.  Here is an example tilesheet ( taken from here ):

It’s a 512x512 image composed of a number of tiles that we are going to paint our level with.

Now that we have our tiles, let’s create our map.  Tiled has the ability to create Orthogonal ( straight on or “top down” perspective maps ) or IsoMetric ( angled perspective ).  In this example we are creating a Orthogonal map.  Next you need to decide how many tiles your map consists of in each direction.  Yes, tiled maps are always rectangular in shape.  Finally you need to decide your tile dimensions in pixels.  Looking at the tile map above it isn’t clear how large each tile is, but that is because some of the larger constructs are actually composed of a number of tiles.  You will see how that works in a few seconds, for now simply select 32x32 pixels for tile size and 32x32 tiles for map size.  In real pixel terms, this makes our map 1024 pixels by 1024 pixels.

Now we need to load our tile set into Tiled. Select Map->New Tileset

Now in the resulting dialog name it and otherwise we keep the defaults.  Our tile set is made up of 32x32 tiles, so those values work.  The background colour is used if you use a particular colour colour to mark transparency.  In this case we are using the alpha channel to determine transparency, so we don’t need to set a colour value.

Now if you look at the bottom corner of Tiled you will see a grid of tiles available.  You simply select a tile, then paint in the right window with it.

Let’s start by quickly paint our entire map with a grass tile.  Click a grass tile in the tile sets window, like so:

Now in the left hand window, select the Fill Tool ( or hit F ), then click in the window, and it will be filed with the tile selected filling our level with a nice grass base.

Now lets say we want to quickly fill in some roads.  The road tile is actually composed of four separate tiles.  This is easily handled in Tiled, simply click the top left tile in the tile set window, then holding SHIFT, click the bottom left, like so:

Now you can draw with all four tiles at once by simply clicking on the map.  First select the Stamp tool, then draw out the tiles as you desire:

So, what about the tiles with transparent sections like these ones?

Well these are designed to be layered over top of other tiles.  Which leads us nicely to layers.  If you have ever used Photoshop or GIMP, you probably already have a pretty good understanding of layering.  Basically layers are drawn over top of lower layers.  So for example, what you draw in Layer 2 is drawn over top of what you draw in Layer 1.

Right now, we only have a single layer, let’s add another one.  In the top menu, select Layer->Add Tile Layer.

Now in the Layers panel you should see a second layer available.  Clicking the checkbox to the side of the layer shows/hides it’s contents.  Clicking the layer itself makes it active.  Click Tile Layer 2.

You can now paint “over” the grass and road layer, like so:

Congratulations, you’ve just created your first map.  Now we simply save it.

Next we will take a look at using this map in code in LibGDX.

26. March 2014

I am a big fan of LibGDX and a big hater of Eclipse.  This always put me in a bit of an awkward situation, as in order to use LibGDX you basically had to use Eclipse, at least if you wanted cross platform support.  You could get IntelliJ to work, but it required a heck of a lot of jumping through hoops.  The reason Eclipse was basically forced upon you is because Google tied it’s tools pretty heavily to the Eclipse platform.  So if you want to use GWT ( for HTML target ) or Android, Eclipse was by far the easiest path.  Fortunately LibGDX recently announced they moved their build system to Gradle.

Now let’s take a look at how you create and execute a LibGDX project in Eclipse.  First and foremost, grab a new release of LibGDX.

Next we want to generate our project.  You need gdx-setup.jar, you can download it here or find it in the lightly release of LibGDX.  Simply double click the jar to run it.  You can also run it from the command line, but we will only use the GUI in this example.

It’s like a stripped down version of the old setup tool, enter the name, package and class name ( following Java naming rules ) and click generate.

## Configuring Android

You need to have the Android SDK installed to continue.  I recommend installing the stand-alone version of the SDK instead of the ADT bundle ( which is Android SDK + Eclipse ).  You can download the Android SDK here.  The download can be a bit tricky to located, Scroll down and locate “DOWNLOAD FOR OTHER PLATFORMS”, then locate SDK Tools only and download the appropriate package:

Extract that archive somewhere.

At this point ( I am not sure its required, but it’s a good idea ), set the environment variable ANDROID_HOME and point it to this folder.

Now open up this folder and located SDK Manager:

Run it.  Let it install/un-install whatever it wants to do to make sure you are current.

The next step is very important.  You need to install the build tools version that LibGDX expects.  Right now I believe that version is 19.0.3.  The Gradle build process will fail if these tools are not installed.

## Configuring IntelliJ

Make sure you have version 13 or higher.  You can download it here.  Community edition is fine, in fact, not much of the paid version is of use to a game developer.  Download and install IntelliJ 13.x if you haven’t already.

Anyways, now that we have fired up IntelliJ, we have a bit of configuration to do before we continue.  We need to setup our JDK and Android SDK in IntelliJ.  If you haven’t already, install and configure the Java 7 JDK.

This part is going to be a bit awkward, but it should only happen the first time.

Run IntelliJ.

In the welcome dialog, select Import Project

Navigate to the directory you created your project in earlier and select build.gradle from the root directory:

Click OK.

In the next dialog, accept the defaults and click OK.

Gradle is now going to build your project… or at least try.

Gradle downloads all the required components, so this could take a while.   Or it should, but truth is, its going to explode in a horrible state of explosionness.  Look for an error like this;

Don’t worry over much, it’s just that IntelliJ doesn’t have a clue where your JDK and ADK are.  Let’s fix that now.

In the Project View (ALT + 1 if missing), right click the root project and select Open Module Settings:

In the resulting dialog select SDKs then click +.

Select JDK

Navigate to the location you installed your JDK.

Next click + again, and this time select Android SDK.  Select the Android SDK location and click OK.

This time you also have to pick the version:

Now there is a possibility you have a wrongly configured JDK at this point too:

If you have an entry other than the ones you created, select it and then hit the – button.

OK, SDK’s are now configured.

Now lets re-import the project.  Select File->Import Project and pick the .build file in the root of your project again.  When prompted:

Select “This Window”.

Now your project should load without errors.  You should never have to perform the above steps again, unless you change JDK or Android SDK versions.

## Running the Desktop Project

You have a one time configuration to run each project type.  Let’s start with the Desktop project:

Select Run->Edit Configurations…

If an entry for Desktop doesn’t already exist, select the + icon.

Select Application:

Now configure the project like so.  Name it then select DesktopLauncher as the Main Class, the Android\assets folder for the working directory and desktop for the classpath value.

Once again, its very important when selecting the working directory, make sure to select the assets folder in the android project!

Now select Run->Run Desktop and voila:

## Running the Android Project

Once again select Run->Edit Configurations.

If an Android project doesn’t exist, select the + icon and select Android Application.

In the configuration, name it, select Android for module and optionally pick USB Device under target device.  Unless of course you are crazy enough to actually use the horrific Android emulator that is!

Now select Run->Run Android to run on an Android device.  The application will be deployed to your device and you should see Logcat information in IntelliJ

## Running the iOS Project

Ok… you need a Mac to do this part and I don’t currently have my Mac with me.  So… coming later.

## Running the GWT ( HTML ) Project

If there is an area you are going to have trouble with, this is the one.  It’s somewhat normal though, I’ve had a lot of fights with GWT tooling thus far.

The project is the same Run->Edit Configurations:

This time select Gradle as the project type.

Now configure the following, set the Name, select the gradle project ( details below ), click Single Instance Only and task to superDev:

When setting the gradle project, be sure to select the project in the gwt folder.

Now click Run->Run HTML.

It will churn away for a few moments, then you should see:

Look for the URL in the results.  Open this in a browser to run your app.

In all honesty, this currently simply doesn’t work for me right now.  Currently I get:

Frankly I have had nothing but trouble with GWT and superDEV, so I really cant be bothered fixing this at the moment.  If I do come up with a solution, I will post it here.

EDIT

Ok, I have a solution to the GWT problems, and it was mostly me, but partially a lack of intuitiveness.

To see your application, once you run the project open a browser window and go to localhost:8080/gwt

Then you should see:

There you now have a working project running in IntelliJ now.  Much of that was one time, so at the end of the day, after the first time, setting up a new project in IntelliJ is just as fast as an Eclipse one.  YAY, no more Eclipse!

11. March 2014

We are going to look at networking for your LibGDX application.  Networking in LibGDX is relatively primitive, supporting only socket communications.  In many cases though, that’s more than enough.  We are going to implement a very simple socket based chat application.  The code is heavily commented, so the discussion will actually be pretty sparse.  If I missed something please leave a comment and I will do my best to address it.

Alright, let’s jump right in with the code:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net.Protocol;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.ServerSocketHints;
import com.badlogic.gdx.net.Socket;
import com.badlogic.gdx.net.SocketHints;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextArea;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

public class Networking implements ApplicationListener {
private OrthographicCamera camera;
private SpriteBatch batch;
private Skin skin;
private Stage stage;
private Label labelDetails;
private Label labelMessage;
private TextButton button;
private TextArea textIPAddress;
private TextArea textMessage;

// Pick a resolution that is 16:9 but not unreadibly small
public final static float VIRTUAL_SCREEN_HEIGHT = 960;
public final static float VIRTUAL_SCREEN_WIDTH = 540;

@Override
public void create() {
camera = new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
batch = new SpriteBatch();

// Load our UI skin from file.  Once again, I used the files included in the tests.
// Make sure default.fnt, default.png, uiskin.[atlas/json/png] are all added to your assets
skin = new Skin(Gdx.files.internal("data/uiskin.json"));
stage = new Stage();
// Wire the stage to receive input, as we are using Scene2d in this example
Gdx.input.setInputProcessor(stage);

// The following code loops through the available network interfaces
// Keep in mind, there can be multiple interfaces per device, for example
// one per NIC, one per active wireless and the loopback
// In this case we only care about IPv4 address ( x.x.x.x format )
List<String> addresses = new ArrayList<String>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
for(NetworkInterface ni : Collections.list(interfaces)){
for(InetAddress address : Collections.list(ni.getInetAddresses()))
{
if(address instanceof Inet4Address){
addresses.add(address.getHostAddress());
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}

// Print the contents of our array to a string.  Yeah, should have used StringBuilder
String ipAddress = new String("");
for(String str:addresses)
{
ipAddress = ipAddress + str + "\n";
}

// Now setupt our scene UI

// Vertical group groups contents vertically.  I suppose that was probably pretty obvious
VerticalGroup vg = new VerticalGroup().space(3).pad(5).fill();//.space(2).pad(5).fill();//.space(3).reverse().fill();
// Set the bounds of the group to the entire virtual display
vg.setBounds(0, 0, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);

// Create our controls
labelDetails = new Label(ipAddress,skin);
labelMessage = new Label("Hello world",skin);
button = new TextButton("Send message",skin);
textIPAddress = new TextArea("",skin);
textMessage = new TextArea("",skin);

// Add them to scene
vg.addActor(labelDetails);
vg.addActor(labelMessage);
vg.addActor(textIPAddress);
vg.addActor(textMessage);
vg.addActor(button);

// Add scene to stage
stage.addActor(vg);

// Setup a viewport to map screen to a 480x640 virtual resolution
// As otherwise this is way too tiny on my 1080p android phone.
stage.setViewport(VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT,false);
stage.getCamera().position.set(VIRTUAL_SCREEN_WIDTH/2,VIRTUAL_SCREEN_HEIGHT/2,0);

// Now we create a thread that will listen for incoming socket connections
new Thread(new Runnable(){

@Override
public void run() {
ServerSocketHints serverSocketHint = new ServerSocketHints();
// 0 means no timeout.  Probably not the greatest idea in production!
serverSocketHint.acceptTimeout = 0;

// Create the socket server using TCP protocol and listening on 9021
// Only one app can listen to a port at a time, keep in mind many ports are reserved
// especially in the lower numbers ( like 21, 80, etc )
ServerSocket serverSocket = Gdx.net.newServerSocket(Protocol.TCP, 9021, serverSocketHint);

// Loop forever
while(true){
// Create a socket
Socket socket = serverSocket.accept(null);

// Read data from the socket into a BufferedReader
BufferedReader buffer = new BufferedReader(new InputStreamReader(socket.getInputStream()));

try {
// Read to the next newline (\n) and display that text on labelMessage
labelMessage.setText(buffer.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start(); // And, start the thread running

// Wire up a click listener to our button
button.addListener(new ClickListener(){
@Override
public void clicked(InputEvent event, float x, float y){

// When the button is clicked, get the message text or create a default string value
String textToSend = new String();
if(textMessage.getText().length() == 0)
textToSend = "Doesn't say much but likes clicking buttons\n";
else
textToSend = textMessage.getText() + ("\n"); // Brute for a newline so readline gets a line

SocketHints socketHints = new SocketHints();
// Socket will time our in 4 seconds
socketHints.connectTimeout = 4000;
//create the socket and connect to the server entered in the text box ( x.x.x.x format ) on port 9021
Socket socket = Gdx.net.newClientSocket(Protocol.TCP, textIPAddress.getText(), 9021, socketHints);
try {
// write our entered message to the stream
socket.getOutputStream().write(textToSend.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}

@Override
public void dispose() {
batch.dispose();
}

@Override
public void render() {
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined);
batch.begin();
stage.draw();
batch.end();
}

@Override
public void resize(int width, int height) {
}

@Override
public void pause() {
}

@Override
public void resume() {
}
}

And when you run the code you should see:

The top two values are the IP v4 addresses of the machine you are running on.  You need to enter these in the other copy of the application you want to chat with.  This address uniquely identifies your machine on the internet, while we specified the port (9021) using code.  Think of a port like a mailbox at an apartment building.  The street address of the building is comparable to the IP address, while PORT is akin to the apartment number.  Port values go from 1 to 65,536, although several are reserved.  Most of the reserved ports are > 100, so when picking a port for your application look up to see if that address is a dedicated port ( such as 80 for HTTP communication, or 21 for FTP ), or simply pick a random high value.  Your machine can have multiple addresses, one per network adapter and possibly more, especially if you run virtualization software like VMWare.  Then there is the value 127.0.0.1, this is a special address known as a loop back adapter, and address that points back at itself.

Where it currently says “Hello World”, this is where any incoming messages will be displayed.

In the first textbox, you enter the IP address of machine you want to send a message to.  In this example, you can actually send a message to yourself, simply enter your own IP address.  Finally the second textbox is the text of the message to send.  Of course you send the message by pressing the Send Message button.

One thing you may notice from the above example is I set it to run in the oddball resolution of 540x960.  Why did I do this?  For a couple reasons.  First, the native resolution of the phone I tested on ( HTC One ) is 1920x1080 and the default Scene2D Skin/Font are wayyyyyyyy too small at that resolution.  Of course I could have created a a new skin that was more appropriate but, well, I'm lazy.  The second reason is that resolution is a decent size and is the same aspect ratio as 1080p ( 16:9 ), so it scales well, both up and down, when displayed on a 16:9 screen.  On an iPad it’s going to look like absolute garbage.  Perhaps in the future I will do a post specifically about handling multiple device resolutions.

There is one more thing to be aware of, this example currently will not work on iOS.  The Scene2D TextField widget currently doesn’t bring up the iOS on screen controller.  There is a fix on the LibGDX contributions forum.  It’s an iOS specific hack/workaround, so I wouldn’t expect it to be merged into the main trunk.  Finally, LibGDX built in networking is fairly simple, limited to just socket programming.  For more robust networking support check out kryonet, it supported in LibGDX and is reported to work across all LibGDX platforms except HTML5.  Which isn’t really surprising, as the author Nathan Sweet is also a LibGDX contributor.

26. February 2014

Last month I wrote about creating and exporting a model from Blender to LIbGDX.  Part of the process involved exporting to FBX then running fbx-conv.  Wouldn’t it be nice if you could export directly from Blender?  Thankfully you can!  A week or so back @Dancovich told me about his Blender plugin on Twitter.  I intended to check it out right away, but truth told, recent experience had made me pretty sick of Blender, so I’ve taken my time.  Today we are going to look at that plugin.

First, download it from Github (direct zip download here)

Copy the folder io_scene_g3d to your Blender plugins folder.

In my case on my Windows 8 install, the plugin directory is: C:\Program Files\Blender Foundation\Blender\2.69\scripts\addons, like so:

Your location will depend on the operating system you use and how you chose to install Blender.  The github page linked above has more details.

Now fire up Blender 2.69 ( note, the plugin currently only supports Blender 2.69!

Select File->User Preferences

Then select the Addons tab

Now scroll down and locate Import-Export: LibGDX G3D Exporter and check it.

Now you are able to export directly from Blender to FBX.

Select File->Export->LibGDX G3D text format.

As you can see, currently there is no binary support.  During development I tend to stick with g3dj anyway.

Here is the scene from Blender:

And now that I run it in LibGDX?

Ahhh, crap.  It’s an easy enough problem though.  The exporter saved my texture as an absolute path, I instead want a relative path.  Opening up the generated g3dj file, I see:

Change that to:

And you are good to go.  You can probably change Blender to work in relative paths and avoid this problem all together.  If not, altering the script to strip the paths should be a no brainer.  Now with that change we run it and:

Hmmmm… that’s not what you were expecting is it?  What’s going on here?

Well, fbx-conv automatically flips the axis from Blender Z up to LibGDX Y up.  This exporter does not.  You can easily perform the same thing in code by rotating –90 degrees about the X axis, like:

modelInstance.transform.rotate(1, 0, 0, -90);

Then run the code and:

Woot, identical to Blender!

I havent got the chance to test the exporter all that extensively, Ive not really done any work with Blender in the last week or so, so I cant really tell you how well it performs with animations.  That said, especially during development, this could be a huge time saver for quick iterations.  The developer is actively looking for people to try it out and report bugs back.  So if you fancy exporting from Blender to LibGDX directly try it out and let @Dancovich on Twitter know if you encounter any bugs.

18. February 2014

So i’ve posted a lot lately about my recent experiences with working with bones exported from Blender to LibGDX.  I encountered two problems, first only the base of the bone was available once exported.  Second, bones that were external to the mesh weren’t being updated.  The first isn’t really a big deal, except the solution to it seemingly is impacted by the second problem.  There was a thread over on LibGDX forums I posted my experiences on, and Xoppa, the guy behind the 3D portions of LibGDX posted that my observations simply weren’t correct.  This post here is mostly a recap of that thread.  I am not sure if anything in this thread will be of value to any of you, but it does illustrate that sometimes… who knows, it might just be gremlins!

So I set about creating a minimal sample to illustrate the problem I was having.  I have literally done this a few dozen, perhaps hundred, of times the past week.

I created an ultra simple model in Blender, with a bone external to mesh.  In my experiences to this point, every time I try to get the position of this external bone, the results will always be 0,0,0.  All internal bones will work fine, but the external one won’t.

Here is the model:

Simple enough, a mesh with a simple 3 bone armature bound to it.  What you don’t see is I’ve also done a small animation sequence ( as Default Take ).

I then load it with the following code.  The idea is draw a sphere at the location of each bone.  Pretty much what I expected to see is two two spheres, with the third one missing ( as it will actually be at 0,0,0, the same location as the first one.

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;

import com.badlogic.gdx.Files.FileType;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.graphics.Color;

import com.badlogic.gdx.graphics.GL10;

import com.badlogic.gdx.graphics.GL20;

import com.badlogic.gdx.graphics.PerspectiveCamera;

import com.badlogic.gdx.graphics.VertexAttributes.Usage;

import com.badlogic.gdx.graphics.g3d.Environment;

import com.badlogic.gdx.graphics.g3d.Material;

import com.badlogic.gdx.graphics.g3d.Model;

import com.badlogic.gdx.graphics.g3d.ModelBatch;

import com.badlogic.gdx.graphics.g3d.ModelInstance;

import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;

import com.badlogic.gdx.graphics.g3d.loader.G3dModelLoader;

import com.badlogic.gdx.graphics.g3d.model.Node;

import com.badlogic.gdx.utils.JsonReader;

import com.badlogic.gdx.graphics.g3d.utils.AnimationController;

import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;

import com.badlogic.gdx.math.Vector3;

public class TankDemo implements ApplicationListener {

private PerspectiveCamera camera;

private ModelBatch modelBatch;

private AnimationController animationController;

private Model model;

private ModelInstance modelInstance;

private Model pivot;

private ModelInstance p1, p2, p3;

private Node bone1,bone2,bone3;

private Environment environment;

@Override

public void create() {

camera = new PerspectiveCamera(

75,

Gdx.graphics.getWidth(),

Gdx.graphics.getHeight());

camera.position.set(0f,0f,-8f);

camera.lookAt(0f,0f,0f);

camera.near = 0.1f;

camera.far = 300.0f;

modelBatch = new ModelBatch();

JsonReader jsonReader = new JsonReader();

G3dModelLoader modelLoader = new G3dModelLoader(jsonReader);

model = modelLoader.loadModel(Gdx.files.getFileHandle("data/demo.g3dj", FileType.Internal));

modelInstance = new ModelInstance(model);

animationController = new AnimationController(modelInstance);

animationController.animate("Default Take",-1,null,0);

bone1 = modelInstance.getNode("Bone");

bone2 = modelInstance.getNode("Bone_001");

bone3 = modelInstance.getNode("Bone_002");

ModelBuilder mb = new ModelBuilder();

pivot = mb.createSphere(0.5f,0.5f,0.5f,10,10,GL20.GL_LINES,new Material(ColorAttribute.createDiffuse(Color.RED)),Usage.Position | Usage.Normal);

p1 = new ModelInstance(pivot);

p2 = new ModelInstance(pivot);

p3 = new ModelInstance(pivot);

environment = new Environment();

environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.8f, 0.8f, 0.8f, 1.0f));

}

@Override

public void dispose() {

modelBatch.dispose();

model.dispose();

}

@Override

public void render() {

Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

Gdx.gl.glClearColor(1, 1, 1, 1);

Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

animationController.update(Gdx.graphics.getDeltaTime());

Vector3 pos = new Vector3();

bone1.globalTransform.getTranslation(pos);

p1.transform.set(bone1.globalTransform);

p2.transform.set(bone2.globalTransform);

p3.transform.set(bone3.globalTransform);

camera.update();

modelBatch.begin(camera);

modelBatch.render(modelInstance, environment);

modelBatch.render(p1, environment);

modelBatch.render(p2, environment);

modelBatch.render(p3, environment);

modelBatch.end();

}

@Override

public void resize(int width, int height) {

}

@Override

public void pause() {

}

@Override

public void resume() {

}

}

Then when I run this code I see:

Or, with the line rendering the main mesh itself commented out to more clearly show the bone locations:

For #[email protected]#[email protected] #@[email protected]#\$ @#[email protected]\$’s sake...

Why the swearing?  Because this is working EXACTLY how it is supposed to.  EXACTLY how I expected it to a week ago.  Exactly how Xoppa said it should.

What it isn’t doing is behaving EXACTLY how it has been for the last 50 or so times I tried the same thing!  Literally every time I did this before, that final bone didn’t update.  No bones in the armature that were external to the mesh itself were updated.  So I thought maybe that’s it…  maybe something about this export caused the third bone to be part of the geometry… maybe that’s it!

No such luck.  The exported g3dj file looks just like any dozen of others I have generated in the past.

This is unbelievably frustrating at this point, as a problem I have been trying furiously to work around simply seems to no longer exist.  At this point I simply have NO idea what I was doing in the past that caused the entire process to break.  And that doesn’t leave me with a warm fuzzy feeling.

Getting content out of Blender has never been the funnest process, but this last week has been an exercise in frustration… and at the end of the day, the source of the frustration seems to no longer exist.

There are a few differences between this example and some of my prior ones.  Generally I load two different Models from disk ( instead of building one using ModelBuilder ), so perhaps once I add another model to the mix I will start seeing the old behaviour ( although that really wouldn’t make much sense ).  Also, I am using a different computer than I normally use ( on my Mac today ), but it is the same version level of Blender and LibGDX, so that shouldn’t be a factor either.

At this point, I just don’t know what to say… maybe the fates simply hated me last week… that would explain the infernal cold they inflicted upon me!