Implementing Custom Configuration in AVG Games with libGDX
The libGDX Utility Library
The com.badlogic.gdx.utils package provides essential utilities, including parsers for XML and JSON. While JSON is more compact, XML's readability makes it preferable for configuration files. The XmlReader class is used to parse XML data.
XmlReader parser = new XmlReader();
try {
Element root = parser.parse(Gdx.files.internal("game/scene_config.xml"));
String sceneName = root.getAttribute("id");
// Access child elements and attributes...
} catch (IOException ex) {
ex.printStackTrace();
}
Designing the Configuration Format
The configuration file should replace the hardcoded values from the previous implementation. It defines elements like the background image, dialog border, and conversation lines.
The following XML structure serves as an example:
<?xml version="1.0" encoding="UTF-8"?>
<scene>
<texturePack>scene_assets.pack</texturePack>
<background>scene_bg</background>
<dialogFrame>
<texture>dialog_frame</texture>
<padLeft>30</padLeft>
<padRight>30</padRight>
<padTop>35</padTop>
<padBottom>35</padBottom>
</dialogFrame>
<conversation>
<line>
<speaker>Alice</speaker>
<text>Hello, how are you?</text>
</line>
<line>
<speaker>Bob</speaker>
<text>I'm doing well, thank you.</text>
</line>
<!-- Add more <line> nodes for additional dialogue -->
</conversation>
</scene>
The padLeft, padRight, padTop, and padBottom values define the stretchable regions for the NinePatch dialog border.
To handle scene transitions, a <nextScreen> node can be added. This node specifies the next screen to display after the conversation ends. It can point to another AVG scene configuration or a standard Screen class.
For a standard screen:
<nextScreen type="standard">com.example.game.GameOverScreen</nextScreen>
For another AVG scene:
<nextScreen type="avg">data/scenes/next_scene.xml</nextScreen>
Implementation in libGDX
The core logic remains similar to the previous version, with the addition of configuration parsing and screen transition logic.
XmlReader configParser = new XmlReader();
try {
Element sceneConfig = configParser.parse(Gdx.files.internal(this.configPath));
String packFilePath = sceneConfig.get("texturePack");
String parentDir = Gdx.files.internal(configPath).parent().path();
assetAtlas = new TextureAtlas(Gdx.files.internal(parentDir + "/" + packFilePath));
backgroundRegion = assetAtlas.findRegion(sceneConfig.get("background"));
Element frameConfig = sceneConfig.getChildByName("dialogFrame");
// Parse NinePatch parameters
int leftPad = Integer.parseInt(frameConfig.getChildByName("padLeft").getText());
int rightPad = Integer.parseInt(frameConfig.getChildByName("padRight").getText());
int topPad = Integer.parseInt(frameConfig.getChildByName("padTop").getText());
int bottomPad = Integer.parseInt(frameConfig.getChildByName("padBottom").getText());
TextureRegion frameTex = assetAtlas.findRegion(frameConfig.getChild(0).getText());
dialogBorder = new NinePatch(frameTex, leftPad, rightPad, topPad, bottomPad);
conversationLines = new ArrayList<DialogLine>();
Element convConfig = sceneConfig.getChildByName("conversation");
for (int idx = 0; idx < convConfig.getChildCount(); idx++) {
Element lineElem = convConfig.getChild(idx);
String speaker = lineElem.get("speaker");
String text = lineElem.get("text");
conversationLines.add(new DialogLine(speaker, text));
}
// Handle the next screen configuration
Element nextScreenElem = sceneConfig.getChildByName("nextScreen");
if (nextScreenElem != null) {
String screenType = nextScreenElem.getAttribute("type");
String target = nextScreenElem.getText();
if (screenType.equals("avg")) {
followingScreen = new ConversationScreen(mainGame, target);
} else if (screenType.equals("standard")) {
try {
followingScreen = (Screen) Class.forName(target).newInstance();
} catch (Exception e) { e.printStackTrace(); }
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
After parsing the configuraton, the stage is constructed and rendered. The scene transition is triggered in the click listener of the dialog border actor.
dialogFrameActor.setClickListener(new ClickListener() {
@Override
public void click(Actor actor, float screenX, float screenY) {
if (currentLineIndex < conversationLines.size() - 1) {
currentLineIndex++;
updateDisplayedText();
} else {
if (followingScreen != null) {
mainGame.setScreen(followingScreen);
}
}
}
});
The complete implementation consolidates these steps into a ConversationScreen class that implements libGDX's Screen interface, handling asset loading, stage setup, input, and rendering.