Archive for the 'picnic-invasion' Category
Back on track?
After seeing this game, which seems close to a lot of the ideas I’ve been working on, I’m trying to get back into writing mine. Here’s what I think needs to change from my previous work:
Graphics
I got seriously hung up over graphics. I kept flip-flopping between trying to do it in 2d and doing it in 3d. Could I make graphics that wouldn’t suck in either? I’m a programmer, not an artist. 3d models are a lot of work, and the game idea I have is inherently not three dimensional. This should have been a simple point, huh? Anyways, unless I can coerce my wife into doing something better, expect programmer-quality crap artwork.
No “Starter Games”
One idea I was pursuing was to try and do small games that encapsulated one portion of the functionality I was working on for Starbound. Since I wanted to have a bunch of ships semi-autonomously flying around the screen I came up with the idea of Picnic Invasion!, where a bunch of ants semi-autnomously crawl around a board.
In the right scenario, this could work out well. Unfortunately I couldn’t really stir up much enthusiasm for the smaller game, so pretty much nothing got done. I’m going to focus on Starbound and just that. Picnic Invasion! is pretty much dead, although I’ll hang on to the idea if I want to start doing simple applet games.
The Clojure/Java bridge
When I started working with Clojure, I pretty much assumed it would be easier to do my computation in Clojure and use Java for storage. This let me completely pass up learning all of the stuff in Clojure for handling mutable data by hiding all of my mutations in Java.
The end result was that I had a bunch of Java classes that constantly needed maintenance. The runtime modification that was the whole point of me using a Lisp dialect for this was pretty well lost. It also meant a lot of cumbersome stuff with objects that were little more than data holders, and code like this:
(defn update-game-obj
"Update the information provided to Java about the game object."
[#^GameObject obj]
(let [p (get-pos obj)
v (get-vel obj)]
(set! (. obj x) (. p (getX)))
(set! (. obj y) (. p (getY)))
(set! (. obj ang) (+ 90 (. v (getTheta))))))
Where a game object has to be told twice something it already knows just so the Java-based rendering function can know how to render the object. Except it’s not that simple, as the render loop is actually built from Clojure code, so there’s a lot of unnecessary indirection and duplication going on here.
The point is that I really should be wholly embracing one or the other. Clojure has a lot of beautiful parts that I’m missing out on because of this organization. The persistant data structures are pretty much useless in this approach as any of my Java-based objects directly violate their core assumptions, so any of the cool things I could do with them (like easily rewinding time) simply aren’t possible.
Slick?
Kevin Glass’ Slick library is really great. He’s spent a lot of time on it and it handles a lot of stuff I’d rather not deal with. Slick tries really hard to just do what you need and stay out of your way the rest of the time.
That said, I still am considering doing the whole thing “from scratch” using lwjgl. It would be nice to learn a bit more about OpenGL and some of the other technologies, and I would essentially be able to do all of the game in Clojure, which is a real plus. That said, I’d be taking on a lot more responsibility, and re-doing a bunch of things that Slick handles pretty admirably. The jury is still out on this one.
What Went Right?
With all of this complaining, there were a few things that went really well. When I didn’t have to shut everything down to monkey with one of my Java classes, using a Lisp for runtime development was awesome. It’s easy to get lost in tweaking something until it is perfect, which can be a bit of a time sink, but simply having that ability means that when it is time to start perfecting things the work will go a lot faster.
1 commentDear God, Why Lisp?
I realized that I sort of stumbled off into Lisp land without explaining why. For those who have worked with a modern Lisp environment, some of the advantages are probably obvious. For the other 98% of the programming world I probably look like a nut. Here I’d like to make a case for why Lisp is a good choice, inside certain parameters, for game development.
Syntax
The first advantage I see in Lisp is also the most cited problem with it. Lisp’s syntax is a powerful tool for extending the Lisp language. Speaking in terms of compilers Lisp doesn’t so much have syntax as a directly written abstract syntax tree. The upshot of this is that, since Lisp’s syntax is so regular, Lisp itself can be used to write macros for your program. This makes building a domain specific language for writing your game much easier.
If you’re going to go through the effort of using a scripting language for your game you might as well make sure it’s actually useful for the task. There’s two ways to do this, either luck into finding one that already has the tools you need or take an existing one and build up to that point. My assumption here is that there aren’t going to be too many scripting languages built explicitly to support writing a space shooter or RTS, so we’re stuck building on top of something that does exist. Lisp’s abstraction capabilities let you turn your scripting language into something specifically tailored for writing your game more quickly and less painfully than anything else.
Dynamic Language
Obviously if I’m going to be doing some scripting the goal is to save time over writing in straight C. There’s a few advantages people typically look for here:
- No Compilation
- More powerful abstractions
The first advantage is the biggest. One big advantage of scripting is that it allows you to modify the game’s behavior without having to recompile. Lisp lets me take that one step further, which is something I’ll get back to in just a bit.
The other big advantage people look for in scripting is in the realm of abstractions. The idea is that you push game objects around with logic written in your scripting language and let the “system” language deal with rendering, representing vectors, loading resources, etc. Since my specific Lisp dialect (Clojure) interacts well with my system languge (Java), I’m all set in this regard.
Interactivity
One feature of Lisp dialects that few other languages have caught up to is the read-eval-print loop (from now on: repl). This is a powerful idea where a minimal Lisp program is loaded that does nothing but take in and execute new definitions and statements. The programmer can type their definitions in on the command line (or more comfortably direct their editor to do so from within a file) and have the repl execute them.
This is the heart of my theory about Lisp’s productivity boosts. I have set my game up so that it can be loaded in a debugging mode. Within this mode the game watches for new Lisp definitions or statements to be sent in and executes them. If I don’t like how some part of my game logic works I can change it and see that change reflected in the game as it is running. I don’t have to shut down, I don’t have to reload a script, and I usually don’t even have to reload the game environment. This is development at breakneck speeds, especially in the later stages of a game where fixing bugs might involve quite a while to wait on resource loading and playing through a game until the point where the bug is encountered.
Right now I’m working on building a replay system into the game. The idea is to record all meaningful user input so that can be played back through the game and I can fix buggy behavior by jumping back to before that behavior happened, modifying the relevant code, and testing it to make sure it now works correctly. On paper this is even more powerful, but we’ll see how it happens in practice.
4 commentsPicnic Invasion: limited updates
Considering I’m trying to spend a little more time working to keep up with bills for my wedding this summer, and still taking school, something has to give. I obviously haven’t been keeping to the daily updates I wanted, and realized I just can’t afford to spend two to three hours each night on this game. Hopefully some time constraints will encourage me to use the time I do dedicate to this a little more meaningfully.
No commentsPicnic Invasion: Optimization Explorations
After getting the basic behavior of my game up and running, the first thing I tried to do was create a whole ton of objects. This was a little disheartening, as creating around four hundred moving ants pushed my macbook below 30fps. Looking at Activity Monitor told me that the slowdown was CPU bound, without too much memory usage.
So, I set about trying to figure out what was going on there. My initial thought was that the upcasting I had been doing in Java render game objects pulled from Clojure was the culprit. I had been contemplating switching that to Clojure anyways to make futzing with the render pipeline easier. So, I switched to the Java code simply passing the data map that stores my game data back and forth between the various functions for handling logic updates, rendering, and user input. This helped a little, but didn’t really do too much.
This is probably a good point to mention that one result of the hack I’ve had to do to get the Clojure environment running in the game is that I can’t see the output from Clojure. So the ridiculous amount of reflection my game was doing went completely unnoticed. Anyone familiar with Clojure probably already knows where the rest of this article is headed.
Anyways, one crash course in the basics of Java profiling later and I was shocked at how much time my code was spending using reflection. It was bad enough that running hprof with the default stack depth (4) told me the program spent the majority of it’s time in java.lang.Object.
As a demonstration, here’s my initial render funciton. This function is called as often as the game can manage, punctuated on occassion with calls to the update function:
(defn render [data container g]
"Render the data map to the given graphics context."
(doseq f (get-beacons data) (. f (render container g)))
(doseq f (get-ants data) (. f (render container g)))
(when debug
(. g (drawString (str "Number of beacons: " (count (get-beacons data))) 10 20))
(. g (drawString (str "Number of beacons: " (count (get-ants data))) 10 30))))
This worked as expected, but was really slow. Coming from Scala, which handled somewhere around six thousand “ants” without breaking a sweat (albiet with much simpler behavior), seeing the system crawl trying to do three hundred was pretty upsetting. Some profiling told me that this function was one of the culprits, which I expected anyways due to the call volume. Just to help understand what’s going on “data” is a PersistentHashMap containing all of the game data. “container” is an object from Slick that gives me access to things like window properties. “g” is another object from Slick, that represents the graphics rendering context. The object “f” is either an Ant or Beacon Java object taken from a list stored in a similarly named key in the data hash.
My first optimization attempt was to put some type hints on the function’s arguments. Here’s what I ended up with:
(defn render [#^PersistentHashMap data #^GameContainer container #^Graphics g]
"Render the data map to the given graphics context."
(doseq f (get-beacons data) (. f (render container g)))
(doseq f (get-ants data) (. f (render container g)))
(when debug
(. g (drawString (str "Number of beacons: " (count (get-beacons data))) 10 20))
(. g (drawString (str "Number of beacons: " (count (get-ants data))) 10 30))))
This helped, but I was still getting a lot of reflection in the render function. Can you tell where? Here’s the final code:
(defn render [#^PersistentHashMap data #^GameContainer container #^Graphics g]
"Render the data map to the given graphics context."
(doseq #^Beacon f (get-beacons data) (. f (render container g)))
(doseq #^Ant f (get-ants data) (. f (render container g)))
(when debug
(. g (drawString (str "Number of beacons: " (count (get-beacons data))) 10 20))
(. g (drawString (str "Number of beacons: " (count (get-ants data))) 10 30))))
After a sequence of profiling the game, annotating the worst offender, and repeating I’m to the point where the top two time using functions are a Slick library function and a function out of boot.clj. Both of these use ~1% of the CPU time at most, so I’m happy. More importantly the game handles as many ants as I’m interested in sitting around creating. Being able to hash out an initial implementation of my game without type annotations was a huge productivity boost. I still have plenty of infrequently called functions that work dynamically because they aren’t worth optimizing. As an added bonus, here’s a comparison between an initial implementation of steering a game object and the annotated example:
(defn steer [obj dir]
"Steer the given object into the given direction"
(let [steer-force (truncate dir (get-max-force obj))]
(. steer-force (scale (/ 1 (get-mass obj))))
(. (get-vel obj) (add steer-force))
(truncate! (get-vel obj) (get-max-speed obj))
(. (get-pos obj) (add (get-vel obj)))
(update-game-obj obj)))
(defn steer [#^GameObject obj #^Vector2f dir]
"Steer the given object into the given direction"
(let [#^Vector2f steer-force (truncate dir (get-max-force obj))
#^Vector2f v (get-vel obj)
#^Vector2f p (get-pos obj)
#^float m (get-mass obj)
#^float s (get-max-speed obj)]
(. steer-force (scale (/ 1 m)))
(. v (add steer-force))
(truncate! v s)
(. p (add v))
(update-game-obj obj)))
Note that I could have simply added the annotations inline without pushing everything up into a let binding, but I think it looks cleaner with the let bindings and technically saves me two superfluous lookups of the velocity of an object.
2 commentsPicnic Invasion: Lost days
Wow, I can’t believe I’ve let posting about this slip so far. I have been working on the game, just not as much as I would have liked. Obviously I haven’t been keeping up with my nonexistant readers about it.
The bits of time I could snatch to work on my game have mostly been spent building up a decent logging infrastructure. Simple things like hunting down a null pointer exception become grossly complicated now that I have two different language environments passing code between each other. Logging helps, and thanks to the power of Lisp I can write some pretty useful logging code. Here’s a sample:
(with-log "Failed to update an ant position"
(update-pos ant))
This expands into a bunch of stuff, but basically any exception thrown in the code run by with-log is trapped, the message “Failed to update an ant position” is logged (as an error by default) and the exception is rethrown. Pretty basic I guess. Here’s where this really comes into play. When the game detects an exception that I could presumably fix without needing to restart it pauses. I can then modify any of the game code and my new code will run when I unpause the game. This combined with the logging code above lets me hunt through my code to pinpoint where the exception is being thrown. From there I can start working bacwards through the function call stack and instrument each function call to test for and log null arguments. Eventually I’ll pinpoint where my null argument originates and can fix that.
I’m planning on expanding the logging facility so it will only track certain errors. The idea is that I could write something like this:
(with-log
[(NullPointerException "Null pointer encountered in foo")
(BoredAntException "An ant has nothing to do")
(OverthoughtExampleException "This example is gratuitous")]
(code...))
One of the beautiful things about working with Lisp is that you can imagine how your program would be written in an ideal psuedocode, write it that way, and build up from your existing language to that one.
Anyways, in addition to writing logging code I’ve started implementing the steering and seeking behavior described here: http://www.red3d.com/cwr/steer/gdc99/
When I started it was working pretty well, but I managed to introduce some bugs that were pretty hard to track down. So, now that I have a better bug handling infrastructure I can get back to the fun part.
Update:
After I finally finished setting up my logging system I identified and solved my problems pretty quickly. The steering behavior didn’t take very long to perfect so I now have a good portion of the game implemented. At the least the ants are moving from A to B pretty consistently, although I’ll probably have to come up with a good mix of the steering and arrival code to simulate the ants picking at a piece of food.
No commentsPicnic Invasion Day 10 and 11
I spent a lot of time the last two days doing a very poor job of chasing bugs in my game. I’ve tracked them all down, discovering and resolving an issue with .jar distributions in the process, but the process highlighted how difficult handling type information between Clojure and Java is going to be. A relatively simple mistake (like making a two-element form of an object and a list instead of consing it onto the list) produced some difficult to interpret error messages. This experience will probably help fixing such problems in the future. Being able to inject code straight into the running game has let me produce my bugs at never before seen speeds, which is what I wanted.
Anyways, I’ve got a basic rendering pipeline down, so now I can move on to the fun part of writing game logic.
No commentsPicnic Invasion! Day 9 : Decision time
Well, finals are officially over, which means I have most of a full week to dedicate to this little hobby of mine. All of the past few posts detail what I’ve accomplished in about two hours or so a night. Now I can spend as much time as I want on this and really get things done.
First things first though, I need to make a decision. I started out working with Scala for this project, and have a decent bit of it done there. The only problem is that I’m not super happy with how the environment works. The Scala compiler takes a long time to run at about six seconds to build a relatively small project. Since I have to drop out of the game to make changes that means I have to:
- Wait for the game to shut down
- Make my changes
- Wait for the project to recompile
- Wait for the game to load
- Make my way back to the point I was testing
So, giving Scala the benefit of the doubt and assuming most of it’s compile time penalty is in JVM startup, let’s estimate a bit. Game shutdown can be really quick, so that’s ~0 seconds. Making changes will happen either way, so that weighs in at ~0 seconds for the comparison. I’ll assume 10 seconds for project compilation (again, giving the benefit). Then it’s another 30 seconds for the game to load and who knows how long to get back to the test point. Since I’m going to have some pretty complex behavior that last one might be a doozy. Either way just the assumed times add up to 40 seconds per iteration to try and fix a bug.
Running the same numbers for Clojure is almost pointless, as I don’t have to shut the game down to make changes. Granted I’ve managed to kill the game a few times by creating a NullPointerException, but every instance that I can remember happened in the render pipeline, which (hopefully) won’t change too much after I really get going.
There are some distinct advantages to Scala though. One of the most obvious ones is that it supports OOP, and thus plays a little better with the game library I am using. So far the bit of playing around I’ve done with Clojure has been in the realm of transferring data from Java to Clojure and back. As I discover new tricks this gets easier, but the majority of my time has been spent in that area. Granted, the little bit of code that I’ve written to actually resemble a game has been pretty powerful, but there you have it.
Another point in Scala’s favor is documentation. I think the community around Scala is a little bigger, and the available documentation reflects this. Clojure has a useful reference in it’s website, and since much of it can be found in the boot.clj file I can always go read the source if I really want to know how something work, but I’m missing a lot of things through simple ignorance. For instance I’ve spent a while accessing elements of a map using ”(get map key)”. Today I learned that maps are functions of their keys so I could have simply called ”(map key)”. Granted the difference is small, but what else is there that I don’t know about?
In the end I think it comes down to me just liking playing with Clojure more than Scala. Scala seems like a pretty solid language, but it’s really hard to pass up the instant satisfaction of the REPL. That and a much slimmer runtime is really nice.
No commentsPicnic Invasion! Day 8 … A break for some Clojure
More fun playing around and not getting much done. I’m in the middle of finals week, so I’m trying not to get too involved with this.
Today’s current bit of playing around was at embedding the Clojure language in a Slick application. It wasn’t too hard to copy from the language’s repl implementation and make sure it wouldn’t block the thread waiting for input. After a bit of tinkering I came up with a class that does what I need, although I highly suspect that it isn’t thread-safe. Here it is:
import clojure.lang.*;
import clojure.lang.Compiler;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class ClojureInteract {
static final Symbol REFER = Symbol.create("clojure", "refer");
static final Symbol QUOTE = Symbol.create("quote");
static final Symbol CLOJURE = Symbol.create("clojure");
Object EOF = new Object();
public ClojureInteract() {
try {
RT.init();
}
catch(Exception e) {
e.printStackTrace();
}
}
// Evaluate the current contents of system input
public Object eval() {
Object ret = null;
try {
if(System.in.available() != 0) {
java.io.PushbackReader rdr = new java.io.PushbackReader(new InputStreamReader(System.in));
Object r = LispReader.read(rdr, false, EOF, false);
if(r != EOF)
ret = Compiler.eval(r);
}
}
catch(Throwable e) {
e.printStackTrace();
}
return ret;
}
// Evaluate the given string
public Object eval(String s) {
Object ret = null;
try {
java.io.PushbackReader rdr = new java.io.PushbackReader(new java.io.StringReader(s));
Object r = LispReader.read(rdr, false, EOF, false);
if(r != EOF)
ret = Compiler.eval(r);
}
catch(Throwable e) {
e.printStackTrace();
}
return ret;
}
// Load the file specified by s
public void load(String s) {
try {
Compiler.loadFile(s);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Not too bad, eh? By the way, aren’t checked exceptions ugly? Anyways, this works out pretty well in that I can pass in direct strings, tell the class to read straight from standard input, or load a file. Running my game as an inferior lisp under emacs is a simple matter of typing ”C-u 1 C-c C-z ant -e -f /Users/ajones/programming/games/clojure-picnic/build.xml run”, what could be simpler? Seriously though, I need to figure out how to set this automagically.
At this point though, I’m happy that I can pipe new function definitions straight into my game as it’s running and have them rebound on the fly. Now modifying game behavior is a simple matter of pausing, updating a definition, and unpausing.
For anyone who’s been following this series you might have noticed a conspicuous absence of information about Scala. I’m seriously considering switching from it to Clojure just for this behavior. I’m sure Scala is capable of what I want somehow, but Clojure has the distinct advantage of being a Lisp and having built-in Emacs support for this since the dawn of time. The relatively tiny .jar file (360K vs 2M) is a bonus too. I like Scala, but I don’t think it’s a great fit for this project and the direction my development practices are heading.
1 commentPicnic Invasion! Day 6 & 7
I forgot to post anything yesterday … didn’t really get a whole lot done anyways. I played around for a while with setting up a command-line interface so I could pipe new Javascript definitions into the game as it is running. Eventually I realized I was wasting a lot of time on something that would be great for my bigger game, but wouldn’t help me get the rest of it done.
So, today I stripped out every bit of testing code and started implementing the game for real. It feels good to be getting work done. I have ants (still being played by triangles) that are moving across the game board to the latest beacon created by the player. Right now the get to the newest one and stop, but I could just as easily do other behaviors.
No commentsPicnic Invasion! Day 5
Worked on integrating the Mozilla Rhino javascript engine today. Here’s a brief example (in Java) of using it with slick:
/*
This file demonstrates using the Rhino Javascript engine within the
Slick 2d game engine.
Rhino can be found at: http://www.mozilla.org/rhino/
Slick can be found at: http://slick.cokeandcode.com/
*/
import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;
public class Rhino extends BasicGame {
/*
The ScriptProxy class defined here is one half of the interface
between Java code and the Javascript environment. It is used to,
among other things, provide access to Java objects within Rhino.
The ScriptableObject class implements the majority of the
Scriptable interface. The Scriptable interface is used to define
functions and objects that will be available to the javascript
environment.
*/
class ScriptProxy extends ScriptableObject {
String test1;
/*
This is the only method required to fully implement the
abstract class ScriptableObject. I'm not sure what it is
supposed to do. The "global" value is used in at least one
example in the Rhino documentation.
*/
public String getClassName() {
return "global";
}
/*
Access to the "test" object in the javascript environment is
implemented through calls to the setTest and getTest
methods.
*/
public void setTest(String str) {
test1 = str;
}
public String getTest() {
return test1;
}
/*
A test function that can be called within the JavaScript
environment.
*/
public void testfunc(String s) {
// System.out.println("test function called");
System.out.println(s);
}
}
/*
A Context is an instance of the javascript environment. The
context is used to load and interpret .js files and interpret
Javascript code as strings, among other things. In this example
it is used exclusively to interpret Javascript strings.
*/
protected Context scriptContext;
protected ScriptProxy gameProxy;
public void init(GameContainer container) throws SlickException {
/*
The static method Context.enter is, apparently, the easiest
way to obtain a javascript environment.
*/
scriptContext = Context.enter();
gameProxy = new ScriptProxy();
/*
We'll set a string for the "test" property that will be
exposed in Javascript now so we know it is calling across
the bridge.
*/
gameProxy.setTest("This was set within java, but called from javascript.");
/*
This must be called before scripts can be evaluated in this
Context. It creates some of the basic Javascript objects.
*/
scriptContext.initStandardObjects(gameProxy);
/*
Functions provided by a ScriptableObject (such as our
example 'testfunc') are initialized in the following manner.
*/
String[] scriptAvailableFunctions = { "testfunc" };
gameProxy.defineFunctionProperties(scriptAvailableFunctions, ScriptProxy.class, ScriptableObject.DONTENUM);
/*
A "property" is a Javascript object that is exposed through
getters and setters in the ScriptableObject. Rhino
automatically prepends the "set" and "get" terms and
uppercases the first letter, so exact naming is important.
*/
gameProxy.defineProperty("test", ScriptProxy.class, ScriptableObject.DONTENUM);
}
public void render(GameContainer container, Graphics g) {
/*
Here we are using the context (javascript engine) to
evaluate a string. The first parameter is our
ScriptableObject, which is used to provide definitions for
the engine. The second argument is the code to be evaluated.
In this case simply writing "test" evaluates the Javascript
object named "test", which is looked up in our gameProxy,
which passed the result of it's getTest() method to the
Javascript engine.
Since this is the only instruction in the string to be
evaluated it is used as the return value for
evaluateString. To provide flexibility in return type
handling evaluateString returns a java.lang.Object, which is
why the result must be cast back into a String.
The third parameter is a string that describes the source
for the Javascript code being evaluated. This may be a
filename if that is where the string comes from.
The fourth parameter is the starting line number for this
script, which might be useful if a script is being pieced
together from a variety of components.
The last parameter is used to provide a security domain for
the script to run under. I *think* this is used to ensure
that the Javascript code cannot execute certain methods or
instantiate some objects, but haven't done enough research
to be sure. Passing a null value here does not restrict the
evaluation of Javascript code.
*/
String result = (String) scriptContext.evaluateString(gameProxy, "test;", "js", 1, null);
g.drawString("Javascript result: " + result, 40, 120);
}
public void update(GameContainer container, int delta) {
}
public void keyPressed(int key, char c) {
if(key == Input.KEY_ESCAPE) {
/*
The script context will most likely be shut down
properly, but it's a good idea to be neat and tidy,
right?
*/
scriptContext.exit();
System.exit(0);
}
if(key == Input.KEY_SPACE) {
/*
This example is largely similar to the above, except
that now we are calling the javascript function
"testfunc()". This is found and executed on gameProxy,
printing a string to the console."
*/
scriptContext.evaluateString(gameProxy, "testfunc(\"testing console output\");", "js", 5, null);
}
}
public Rhino(String s) {
super(s);
}
public static void main(String[] Args) {
try {
AppGameContainer container = new AppGameContainer(new Rhino("Picnic Invasion!"));
container.setDisplayMode(600,480,false);
// container.setShowFPS(false);
container.setMinimumLogicUpdateInterval(30);
container.start();
} catch (SlickException e) {
e.printStackTrace();
}
}
}
No comments