Author: Brian A. Ree
Updates: Included jars in the project's local lib dir.
1: Why Program?
Programming is not for everyone, that's for sure. Nowadays there seems to be this sentiment that
everyone needs to learn to program or to learn how to write an app. I'm not sure that it really makes sense
in practice. But, if you've found your way across the vast void of the cyber universe to this page. Well then you
must be the type of person programming is for. Get ready for some fun tutorials. This is the first tutorial in a series
that covers the basics of game development by creating a foundation for a game engine and manipulating some graphics.
First thing's first, we need to setup our development environment. In order to code effectively in the modern era
you need a great IDE (integrated development environment). You'll also need a language of choice to code in.
Thankfully for us there are a ton of options out there. But since we must choose we shall choose the Java
programming language, and the © NetBeans IDE. You're welcome, jk, that should simplify things for you.
But in all honesty we chose Java and NetBeans because they are very highly developed cross platform solutions
and will run well on different operating systems.
2: Why Program Games?
So why program games? Well why not, what else is cooler to code than a video game? That's one reason I suppose,
another reason could be interest in video game creation as a career. But the main reason I see to make a game is
because is it a program that inherently is appealing to many people who just love video games. For that reason you can
potentially sell your game and earn some money or ad revenue for you efforts. Lastly but perhaps most importantly is you could
just love to code, and nothing is more challenging than writing a video game that runs at 60 frames per second and doens't have
a memory leak or trigger the garbage collector (for you managed coders). Well now that we've cleared that up let's get cracking
with our IDE Netbeans.
To download NetBeans click here. We recommend downloading the "All" option
on the NetBeans download page, however, you can probably get by with the the "Java SE" version for this set of tutorials.
To make sure you have Java on your system you can go to here and check your Java version or
install the latest version of Java. For those of you who are new to NetBeans we suggest you take a crash course
here.
3: Crash Course
If you've never programmed a computer before you are going to find this tutorial just a bit difficult. It might not be impossible
though so I encourage you to give it a shot. A good place to learn the basics of Java is here.
For those of you with some programming experience, you should do just fine. We're going to be getting into game loops soon but first we're going to
focus on the foundation of our software, the static main entry point.
package com.middlemindgames.Tutorial0Page0;
/**
* Java swing app shell. MAIN ENTRY POINT Created on
* January 30, 2017, 10:57 PM by Middlemind Games
*
* @author Victor G. Brusca
*/
public final class Tutorial0Page0App {
/**
* Static access method.
*
* @param args The command line arguments
*/
public static final void main(String[] args) {
System.out.println("Hello World");
}
}
Ahhh, such a thing of beauty. In case you don't know the String[] args array contains an array of the arguments
passed to your game from the calling program, usually a command shell or a script running in a shell. Our goal for this
tutorial is to create a static main that supports command line configuration and XML data driven configuration. This way we have a
game foundation that can accept dynamic information from the command line or a configuration file.
4: Static Main and Setup
So we have a basic static main entry to our game, we'll call it a game for now. Let's take the opportunity to learn a little bit about
the game API we'll be using for this tutorial series. An API, for those who are new to the term, is an Advanced Programming Interface. Or a fancy
term for a set of tools used to solve a programming problem. In our case it will be drawing and moving around images in our game. You can view the
documentation for the API here. Let's add the API library to our
list of libraries for the Tutorial0 NetBeans project.
To do so you'll need to get a copy of the API jar from here. A jar file is a way of passing around
compiled java classes easily. Think of the compiled classes as java beans, and the java beans are put into a jar, bam!. Once you've downloaded the jar file and saved it to a location near your project files, I like to make a libs folder at the workspace root folder.
You can add that jar file to your project like so. Right click your project and select properties from the menu.
Next you'll go to the libraries section on the left hand side. And select Add Jar/Folder from the buttons on the left. Browse through the file system to
where you saved the jar file. Now you can import namespaces from the jar file and use the API.
If you click the link above for the APIs javadocs, get used to browsing javadocs it's very useful, we're going to be using an API call for logging
instead of the system out call in the sample code below. For this change we're going to use the MmgApiUtils class.
package com.middlemindgames.Tutorial0Page0;
import net.middlemind.MmgGameApiJava.MmgBase.MmgApiUtils;
/**
*
* @author Victor G. Brusca, Middlemind Games
*/
public class Tutorial0Page0App {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
//System.out.println("Hello World");
MmgApiUtils.wr("Hello world!");
}
}
So you may be wondering what is the benefit of using the debugging call from the game API as opposed to the standard output.
The reason lies in the source code of the API, if you notice in the documentation for the MmgApiUtils class there is a LOGGING boolean
that allows you to turn logging on or off. This use of centralization means you can turn off all logging throughout your game from one location.
What's more, you can drive the value of this setting by using configuration files in your game. That brings us to our next step, adding some
settings variables and tying them to command line arguments.
/**
* The target window width.
*/
public static int WIN_WIDTH = 858;
/**
* The target window height.
*/
public static int WIN_HEIGHT = 600;
/**
* The game panel width.
*/
public static int PANEL_WIDTH = 854;
/**
* The game panel height.
*/
public static int PANEL_HEIGHT = 596; //416;
The use of the variables we've listed above are going to become more apparent as we move along in this series of tutorials.
The main focus of our code is to be a solid foundation for a video game. In almost every game you must design the video game
around a certain sized presentation window, i.e. the window your game is running in, or the dimensions of the phone, TV your
designing for. You can emulate those dimensions in our little Java game. I recommend using the
default values listed above for now because they will jive with future tutorials on an RPG engine. Our updated project code is as follows.
package com.middlemindgames.Tutorial0Page0;
import net.middlemind.MmgGameApiJava.MmgBase.MmgApiUtils;
/**
*
* @author Victor G. Brusca, Middlemind Games
*/
public class Tutorial0Page0App {
/**
* The target window width.
*/
public static int WIN_WIDTH = 858;
/**
* The target window height.
*/
public static int WIN_HEIGHT = 600;
/**
* The game panel width.
*/
public static int PANEL_WIDTH = 854;
/**
* The game panel height.
*/
public static int PANEL_HEIGHT = 596; //416;
/**
* The frame rate for the game, frames per second.
*/
public static long FPS = 16l;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
//System.out.println("Hello World");
MmgApiUtils.wr("Hello world!");
if (args != null && args.length > 0) {
MmgApiUtils.wr("Found command line arguments!");
String res = null;
res = ArrayHasEntryLike("WIN_WIDTH=", args);
if (res != null) {
WIN_WIDTH = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("WIN_HEIGHT=", args);
if (res != null) {
WIN_HEIGHT = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("PANEL_WIDTH=", args);
if (res != null) {
PANEL_WIDTH = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("PANEL_HEIGHT=", args);
if (res != null) {
PANEL_HEIGHT = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("FPS=", args);
if (res != null) {
FPS = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("ENGINE_CONFIG_FILE=", args);
if (res != null) {
ENGINE_CONFIG_FILE = res.split("=")[1];
}
}
}
}
Oh no! Did I sneak in some new code? Yes I did, introducing the next step in the evolution of our video game foundation.
Frame rate, and the ability to set values dynamically from the command line arguments passed to the game. Allow me to explain.
If we didn't add a way to set certain values in our compiled program then the program would only run at one size, kind of limiting.
So we're adding the ability to change values at runtime, when the game is executed, because we're awesome and we can, also because it makes sense.
So if you have a sharp eye you've noticed by now that there is a new variable called FPS. This represents our target frames per second that we want the
game to run at. If you don't know what FPS means, don't worry we will show you later on in this tutorial series. For now think of it as the number of times you want to
re-draw the screen to animate your video game smoothly. Part of what makes game programming so challenging is writing code that runs efficiently so that
you can achieve high frame rates and smooth animations.
Can you spot the mistake in the code above? Think about it for a little bit. Anywho, the new code in the main method takes in command line arguments
of a specific pattern and stores it to a static variable. The static modifier, in case you didn't know, means that the variable
or method can be accessed from the class itself and not an instantiation of the class.
//static class member
MmgApiUtils.wr("Some string.");
//instantiated class member
MmgApiUtils mmg = new MmgApiUtils();
mmg.wr("Some string");
So there are two mistakes in the code above, one is the missing method ArrayHasEntryLike, the second is the missing variable
ENGINE_CONFIG_FILE from our class definition. That's because we haven't talked about them yet. So let's do it. We want as much flexibility
as possible in our game engine. But using command line arguments would get tedius and make using our engine difficult. Our solution to this
is to use an XML file to drive a totally dynamic import of game engine variables and values. But before we look futher into this concept lets
fill in the missing method.
/**
* Method that search an array for a string match.
* @param v The string to find a match for.
* @param s The array of string to search through.
* @return
*/
public static String ArrayHasEntryLike(String v, String[] s) {
int len = s.length;
for (int i = 0; i < len; i++) {
if (s[i].contains(v) == true) {
return s[i];
}
}
return null;
}
You might have noticed that this method is also static, just like our main method. This is because static methods can only call
local static methods and local static variables in it's own class. This is because a static method is run without an instantiated class,
thus there is no local instance to access non static variables and methods from. But it's not the biggest deal, commonly used methods and values
are often stored in static variables and methods because it makes it easy to access and can make code design more flexible. That being said you don't want to
overuse it. Basically if it doesn't have to be static it probably shouldn't.
You can see that our ArrayHasEntryLike method takes care of searching through the arguments array for a set of expected entries. You can see how this could quickly get
out of hand if you wanted to support even as much as fifteen variables. So we need a better way to load variables into our game engine. Aaaand here comes our XML configuration file to the rescue.
You will need to create the directory cfg in your local project, this can be done via the IDE. I'll show you how the file engine_config.xml is formatted and I'll show you the
magic that is Java Reflection, and how I sprinkle it into our game foundation class.
/**
* Base engine config files.
*/
public static String ENGINE_CONFIG_FILE = "../cfg/engine_config.xml";
4: Dynamic Config
Download the basic engine configuration XML from here and save it to the cfg folder in your project's root directory.
The default value for ENGINE_CONFIG_FILE should point to the new engine config XML file. Our config file currently supports setting two values.
The logging flag that determines if logging is active, and the FPS value or the frames per second value. So how do we process this information in a fairly
flexible way? We'll use java's Reflection classes and some XML parsers. Let's get ready to code! Take a look at the code snippet below.
//LOAD ENGINE CONFIG FILE
try {
if (Tutorial0Page0App.ENGINE_CONFIG_FILE != null && Tutorial0Page0App.ENGINE_CONFIG_FILE.equals("") == false) {
GameSettingsImporter dci = new GameSettingsImporter();
boolean r = dci.ImportGameSettings(Tutorial0Page0App.ENGINE_CONFIG_FILE);
System.out.println("Engine config load result: " + r);
int len = dci.GetValues().keySet().size();
String[] keys = dci.GetValues().keySet().toArray(new String[len]);
String key;
DatConstantsEntry ent = null;
Field f;
for (int i = 0; i < len; i++) {
try {
key = keys[i];
ent = dci.GetValues().get(key);
if (ent.from != null && ent.from.equals("Tutorial0Page0App") == true) {
f =Tutorial0Page0App.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
} else if (ent.from != null && ent.from.equals("MmgApiUtils") == true) {
f = MmgApiUtils.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
}
} catch (Exception e) {
System.out.println("Ignoring dat constants field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type);
MmgApiUtils.wrErr(e);
}
}
}
} catch (Exception e) {
MmgApiUtils.wrErr(e);
}
MmgApiUtils.wr("Window Width: " + WIN_WIDTH);
MmgApiUtils.wr("Window Height: " + WIN_HEIGHT);
MmgApiUtils.wr("Panel Width: " + PANEL_WIDTH);
MmgApiUtils.wr("Panel Height: " + PANEL_HEIGHT);
Now let's go over this new code before we add it to our foundation. The first thing we do is check to see that the ENGINE_CONFIG_FILE
variable is set to something other than the empty string and null. This is because we're expecting a file name to be prevodied by this variable.
You should start to think, hey I can set this value with a command line argument, now you're seeing how the configuration files are dynamic and flexible.
Let's keep on keeping on and look at some more code. The next few lines are used to read in the XML file. The result is printed to standard out.
You may be wondering why I chose to use standard out instead of our API logging call? That's because sometimes you want a low-level but important function to send some
logging to the standard output. Not too much mind you, but some crucial information like a bad import variable or file path really can help users of your software.
if (Tutorial0Page0App.ENGINE_CONFIG_FILE != null && Tutorial0Page0App.ENGINE_CONFIG_FILE.equals("") == false) {
GameSettingsImporter dci = new GameSettingsImporter();
boolean r = dci.ImportGameSettings(Tutorial0Page0App.ENGINE_CONFIG_FILE);
System.out.println("Engine config load result: " + r);
int len = dci.GetValues().keySet().size();
String[] keys = dci.GetValues().keySet().toArray(new String[len]);
String key;
DatConstantsEntry ent = null;
Field f;
This is an important line of code in our data loading process, dci.ImportGameSettings(Tutorial0Page0App.ENGINE_CONFIG_FILE);.
This class will be provided by another API. I know soo many APIs, you should get used to it because sometimes you need a well implemented
feature from a 3rd party API. Notice how the loading of the XML is abstracted to a class. The class has a succinct and direct usage.
This is an example of encapsulation. Code should be presented in well organized classes (as much as possible anyway). Complexities should be abstracted
by classes that provide a simple interface for using said class. Wow, soap box much? Ok let's move on. The next line after printing out the variable load results
is to determine how many entries were loaded by the XML parser.
int len = dci.GetValues().keySet().size();
String[] keys = dci.GetValues().keySet().toArray(new String[len]);
String key;
DatConstantsEntry ent = null;
Field f;
The first line in the code block above gets the length of the values loaded from the XML file. The file is split into key value pairs with the key being a string
and the value a DatConstantsEntry, which you will shortly see is a nifty little wrapper for a Reflection call. The keys variable is an array of the keys
found in the XML file in no gauranteed order, if you want gaurantees, sort the array first. Following the array declaration and instantiation we have the declaration of
two variables key and ent these are place holder classes we're going to use in a loop. This way we only declare them once outside of the loop, less memory moving around, less garbage
collection, better game performance. In general, anyhow. So let's take a look at that loop.
for (int i = 0; i < len; i++) {
try {
key = keys[i];
ent = dci.GetValues().get(key);
if (ent.from != null && ent.from.equals("Tutorial0Page0App") == true) {
f = Tutorial0Page0App.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
} else if (ent.from != null && ent.from.equals("MmgApiUtils") == true) {
f = MmgApiUtils.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
}
} catch (Exception e) {
System.out.println("Ignoring dat constants field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type);
MmgApiUtils.wrErr(e);
}
}
The first thing you should notice is that the loop starts with a try catch. This means that each iteration of the loop can fail independently,
as opposed to having the try catch on the outside of the loop, where the entire loop will stop at the first exception. You'll notice that we store a local
copy of the key, and a copy of the value, an instance of the DatConstantsEntry class. You can view documentation on this API here.
I should explain a little bit about our strategy here, the XML files lists entries that tell our processing loop what values to set on a class's static variables.
We can do this using reflection, so the XML file can list any static variable by name, see how dynamic that is. We can, on the fly, change values or add new values, awesome.
The support API can be downloaded here
and added to your project in the same way the game API was added.
entry key="LOGGING" val="true" type="bool" from="MmgApiUtils"
The XML entry is loaded into an instance of a DatConstantsEntry, it tells our code the key or the name of the static class member
we want to set a value for, from the class who's static member we are setting the value for, and the type which tells us which reflection call to use
when setting values. You can see here that our XML load is limited to static class variables and only for the Tutorial0Page0App, and MmgApiUtils classes.
Using .class.getField(ent.key) lets us get an instance of the class and grab an instance of its field via reflection. One caveat here is that our XML keys must
have the same exact name and case as the class's static variable. So let's take a look at our most up to date code now.
package com.middlemindgames.Tutorial0Page0;
import com.middlemindgames.TyreGame.DatConstantsEntry;
import net.middlemind.MmgGameApiJava.MmgBase.MmgApiUtils;
import com.middlemindgames.TyreGame.DatConstantsImporter;
import com.middlemindgames.TyreGame.GameSettingsImporter;
import java.lang.reflect.Field;
/**
*
* @author Victor G. Brusca, Middlemind Games
*/
public class Tutorial0Page0App {
/**
* The target window width.
*/
public static int WIN_WIDTH = 858;
/**
* The target window height.
*/
public static int WIN_HEIGHT = 600;
/**
* The game panel width.
*/
public static int PANEL_WIDTH = 854;
/**
* The game panel height.
*/
public static int PANEL_HEIGHT = 596; //416;
/**
* The frame rate for the game, frames per second.
*/
public static long FPS = 16l;
/**
* Base engine config files.
*/
public static String ENGINE_CONFIG_FILE = "../cfg/engine_config.xml";
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
//System.out.println("Hello World");
MmgApiUtils.wr("Hello world!");
if (args != null && args.length > 0) {
MmgApiUtils.wr("Found command line arguments!");
String res = null;
res = ArrayHasEntryLike("WIN_WIDTH=", args);
if (res != null) {
WIN_WIDTH = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("WIN_HEIGHT=", args);
if (res != null) {
WIN_HEIGHT = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("PANEL_WIDTH=", args);
if (res != null) {
PANEL_WIDTH = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("PANEL_HEIGHT=", args);
if (res != null) {
PANEL_HEIGHT = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("FPS=", args);
if (res != null) {
FPS = Integer.parseInt(res.split("=")[1]);
}
res = ArrayHasEntryLike("ENGINE_CONFIG_FILE=", args);
if (res != null) {
ENGINE_CONFIG_FILE = res.split("=")[1];
}
}
//LOAD ENGINE CONFIG FILE
try {
if (Tutorial0Page0App.ENGINE_CONFIG_FILE != null && Tutorial0Page0App.ENGINE_CONFIG_FILE.equals("") == false) {
GameSettingsImporter dci = new GameSettingsImporter();
boolean r = dci.ImportGameSettings(Tutorial0Page0App.ENGINE_CONFIG_FILE);
System.out.println("Engine config load result: " + r);
int len = dci.GetValues().keySet().size();
String[] keys = dci.GetValues().keySet().toArray(new String[len]);
String key;
DatConstantsEntry ent = null;
Field f;
for (int i = 0; i < len; i++) {
try {
key = keys[i];
ent = dci.GetValues().get(key);
if (ent.from != null && ent.from.equals("Tutorial0Page0App") == true) {
f =Tutorial0Page0App.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
} else if (ent.from != null && ent.from.equals("MmgApiUtils") == true) {
f = MmgApiUtils.class.getField(ent.key);
if (f != null) {
System.out.println("Importing " + ent.from + " field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type + " from: " + ent.from);
SetField(ent, f);
}
}
} catch (Exception e) {
System.out.println("Ignoring dat constants field: " + ent.key + " with value: " + ent.val + " with type: " + ent.type);
MmgApiUtils.wrErr(e);
}
}
}
} catch (Exception e) {
MmgApiUtils.wrErr(e);
}
MmgApiUtils.wr("Window Width: " + WIN_WIDTH);
MmgApiUtils.wr("Window Height: " + WIN_HEIGHT);
MmgApiUtils.wr("Panel Width: " + PANEL_WIDTH);
MmgApiUtils.wr("Panel Height: " + PANEL_HEIGHT);
}
/**
* Sets the value of the field specified by the field reflection object.
* @param ent Entry object that wraps the XML entry.
* @param f Class member that needs to be updated.
* @throws Exception
*/
public static void SetField(DatConstantsEntry ent, Field f) throws Exception {
if (ent.type != null && ent.type.equals("int") == true) {
f.setInt(null, Integer.parseInt(ent.val));
}else if (ent.type != null && ent.type.equals("long") == true) {
f.setLong(null, Long.parseLong(ent.val));
} else if (ent.type != null && ent.type.equals("float") == true) {
f.setFloat(null, Float.parseFloat(ent.val));
} else if (ent.type != null && ent.type.equals("double") == true) {
f.setDouble(null, Double.parseDouble(ent.val));
} else if (ent.type != null && ent.type.equals("short") == true) {
f.setShort(null, Short.parseShort(ent.val));
} else if (ent.type != null && ent.type.equals("string") == true) {
f.set(null, ent.val);
} else if (ent.type != null && ent.type.equals("bool") == true) {
f.setBoolean(null, Boolean.parseBoolean(ent.val));
} else {
f.setInt(null, Integer.parseInt(ent.val));
}
}
/**
* Method that search an array for a string match.
* @param v The string to find a match for.
* @param s The array of string to search through.
* @return
*/
public static String ArrayHasEntryLike(String v, String[] s) {
int len = s.length;
for (int i = 0; i < len; i++) {
if (s[i].contains(v) == true) {
return s[i];
}
}
return null;
}
}
Take a look at the SetField method above. Notice that it uses information from the DatConstantsEntry class to decide what reflection call to make.
The information stored in our instance of the DatConstantsEntry comes from our XML config file. The above code is a working program that can take dynamic command line
information to change the dimensions of the game's display and load values from external XML files. Let's add some more control to our XML file and have it set the dimensions
of the app for us so we don't have to use a command line argument. The new XML entries look like the lines below, you can download an updated config file here.
entry key="WIN_WIDTH" val="860" type="int" from="Tutorial0Page0App"
entry key="WIN_HEIGHT" val="602" type="int" from="Tutorial0Page0App"
You should be able to run the program in your IDE using the debug options. You should see the following in your IDEs output window after running the program.
If you are new to NewBeans use the menu option run to run the project containing your code. The output window will popup when you run your project.
You should see something similar to the text below.
Hello world!
Found Key: LOGGING Value: true Type: bool From: MmgApiUtils
Found Key: FPS Value: 16 Type: long From: Tutorial0Page0App
Found Key: WIN_WIDTH Value: 860 Type: int From: Tutorial0Page0App
Found Key: WIN_HEIGHT Value: 602 Type: int From: Tutorial0Page0App
Engine config load result: true
Importing Tutorial0Page0App field: FPS with value: 16 with type: long from: Tutorial0Page0App
Importing Tutorial0Page0App field: WIN_WIDTH with value: 860 with type: int from: Tutorial0Page0App
Importing MmgApiUtils field: LOGGING with value: true with type: bool from: MmgApiUtils
Importing Tutorial0Page0App field: WIN_HEIGHT with value: 602 with type: int from: Tutorial0Page0App
Window Width: 860
Window Height: 602
Panel Width: 854
Panel Height: 596
If you get an exception like the one shown below you may have to change the default path for your config directory or set your execution directory.
I'll show you how to set the execution directory in the project settings next.
Hello world!
java.io.FileNotFoundException: /Users/victor/Documents/files/netbeans_workspace/MmgGameApiJavaTutorial0Page0/dist/./cfg/engine_config.xml (No such file or directory)
at java.io.FileInputStream.open(Native Method)
Engine config load result: false
Window Width: 858
Window Height: 600
Panel Width: 854
Panel Height: 596
at java.io.FileInputStream.(FileInputStream.java:131)
at java.io.FileInputStream.(FileInputStream.java:87)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:90)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:616)
at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:189)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:812)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:348)
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:205)
at com.middlemindgames.TyreGame.GameSettingsImporter.RunImportGameSettings(GameSettingsImporter.java:30)
at com.middlemindgames.TyreGame.GameSettingsImporter.ImportGameSettings(GameSettingsImporter.java:68)
at com.middlemindgames.Tutorial0Page0.Tutorial0Page0App.main(Tutorial0Page0App.java:91)
null
com.middlemindgames.Tutorial0Page0.Tutorial0Page0App.main(Tutorial0Page0App.java:93)
If you right click on your project in NetBeans and select properties from the drop down menu.
You can then select the Run section on the left hand side of the window. On the right hand side, you will
see an option to select the working directory. Set this to ./dist and the default path for the engine
configuration XML will work now. If you still get path errors just alter the default value for the configuration XML file
to a path that works in your environment. The error message in the output window should give you enough information to properly
set the cfg directory's path.
This brings us to the end of tutorial 0. Take a look at what we've got so far. We have a static main game entry that
uses command line arguments to set some local variable values. It also supports reading and processing an XML configuration file
to set static class variable values. This gives us a lot of flexibility in how we can use our engine and will enable us to change things
without having to recompile the program. We've learned a little bit about a couple of different topics including static members, reflection, different dynamic data strategies,
and some general coding experience. In the next tutorial we'll be adding a visual element to our project, you'll have a window and a blank canvas by the end of the next tutorial.