It’s an A* pathfinder (see source code). The implementation uses packed integer coordinates as an optimisation (ie. nodes in the path are identified by a single int which holds the x and y coordinates) to avoid excessive object allocation, though I still create a lot of actual Nodes.
The pathfinder executes a maximum of 512 steps per frame to keep a lid on execution time. This is done with the entirely pragmatic method of a static step count in the GidrahMovement class which I reset to 0 every frame.
The pathfinder finds its way around the map using a Topology which describes the cost of moving from one grid location to another for a particular alien. Certain aliens move diagonally, some fly, etc. There are a few “tricks” in the topology like making the cost of moving through a bunch of other gidrahs more expensive, which has a subtle declumping effect.
Beyond the pathfinder the AI is braindead simple. Each gidrah has a “target” building which it heads for. There are 3 basic brains for choosing targets. The dumb one just heads for a random “apparently valuable” building (the base or a decoy). Clever brains choose the nearest building and attack it. Tactical brains apply various weights to refineries, turrets, and bases, and choose the nearest. Then they simply collide with anything in their way along their chosen path and attack it.
The paths are influenced by the danger level - some turrets increase the “danger” on grid tiles nearby. Gidrahs that die also increase the danger level on the tile upon which they die. This means if they are walking headlong into slaughter they will eventually realise it’s unsafe and might pick a different path.
Tuning is all by trial and error. The behaviour you see is all basically emergent, unplanned, and the result of months of subtle tweaking.
The basic idea behind the main loop in my games is, it ticks the game logic at 60Hz, and I update the screen at the same rate. However, I keep a hi-res timer going, and work how many logic ticks I’m supposed to have done in total since the last clock reset. When that drifts, sometimes I do 0 ticks, or sometimes a few extra logic ticks, up to a maximum of 5 (when basically the game is running flat out and can’t cope). The game loop in its entirety is here:
private void run() {
int ticksToDo = 1;
long then = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL;
long framesTicked = 0;
long timerResolution = Sys.getTimerResolution();
while (!finished) {
if (Display.isCloseRequested()) {
// Check for O/S close requests
exit();
} else if (Display.isActive() || alwaysRun) {
// The window is in the foreground, so we should play the game
long now = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL;
long currentTimerResolution = Sys.getTimerResolution();
if (currentTimerResolution != timerResolution) {
// Timer resolution change -- all bets off
if (DEBUG) {
System.out.println("Timer resolution change from "+timerResolution+" to "+currentTimerResolution);
}
timerResolution = currentTimerResolution;
then = now;
}
if (now > then) {
long ticksElapsed = now - then;
double shouldHaveTickedThisMany = (double) (getFrameRate() * ticksElapsed) / (double) timerResolution;
ticksToDo = (int) Math.max(0.0, shouldHaveTickedThisMany - framesTicked);
if (ticksToDo > 5) {
// We're overrunning!
if (DEBUG) {
System.out.println("Frame overrun! "+ticksToDo);
}
ticksToDo = 1;
then = now;
framesTicked = 0;
}
} else if (now < then) {
if (DEBUG) {
System.out.println("Clock reset: "+Long.toHexString(now)+" vs "+Long.toHexString(then));
}
ticksToDo = 0;
then = now;
framesTicked = 0;
} else {
ticksToDo = 0;
}
if (ticksToDo > 0) {
if (DEBUG && ticksToDo > 1) { // Warning suppressed
System.out.println("Do "+ticksToDo+" ticks");
}
for (int i = 0; i < ticksToDo; i ++) {
if (i > 0) {
Display.processMessages();
catchUp = true;
} else {
catchUp = false;
}
// If any tick takes longer than the allotted time, reset counters and stop ticking. We're basically running flat out.
long tickThen = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL;
tick();
long tickNow = Sys.getTime() & 0x7FFFFFFFFFFFFFFFL;
long tickElapsed = tickNow - tickThen;
if (tickElapsed < 0 || tickElapsed > (double) timerResolution / getFrameRate()) {
ticksToDo = 0;
then = tickThen;
framesTicked = 1;
catchUp = false;
if (DEBUG) {
System.out.println("Running flat out! "+tickElapsed+" vs "+(double) timerResolution / getFrameRate());
}
}
Thread.yield();
}
framesTicked += ticksToDo;
render();
Display.update();
}
if (DEBUG || forceSleep) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
} else {
Thread.yield();
}
// Ensure we have sound
targetMasterGain = 1.0f;
} else {
// The window is not in the foreground, so we can allow other stuff to run and
// infrequently update
// Silence the game
targetMasterGain = 0.0f;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Display.isVisible() || Display.isDirty()) {
// Only bother rendering if the window is visible or dirty
render();
}
Display.update();
if (Mouse.isGrabbed()) {
Mouse.setGrabbed(false);
}
}
// Master gain
if (Math.abs(targetMasterGain - masterGain) < 0.1f) {
masterGain = targetMasterGain;
} else if (targetMasterGain > masterGain) {
masterGain += 0.1f;
} else {
masterGain -= 0.1f;
}
if (isSFXEnabled()) {
AL10.alListenerf(AL10.AL_GAIN, masterGain);
}
}
}
So basically: I use fixed timing at 60Hz but only update the display at a maximum of 60Hz. This keeps everything really simple and means I don’t need to worry about float time deltas and so on - the game is simply hard coded / designed to run at 60 frames per second.
I do use multithreading but only for background tasks. The JVM is already making some good use of threads with garbage collectors and background compilation; I use them to stream music in the background, and for async remote calls, and async preference saving. I’d use another thread to do calcs such as AI but when I started this game less than half of all PCs had 2 cores; now well over half of them have 2 or more cores so the next game will probably feature multithreading to perform AI more efficiently. The architecture of a multithreaded design is pretty complex - especially as it has to work precisely as well on a single-core machine as a multi-core machine.
We no longer use fixed resolution graphics; Revenge is the first title of ours that simply uses the desktop resolution, and applies a scale to it (can specify scale=2.0 etc on commandline to experiment). By default we divide the smallest dimension by 320 and round down to get the scaling factor. All our UIs are designed around the notion of a logical 320x320 display, but the widgets are anchored to edges of the screen or the middles, and can move accordingly. There’s nothing really in theory stopping me from using an AWT window and allowing dynamic UI resizing actually. Hm. But that’s how we can cope with any game resolution. The scale factor is always rounded to an integer unless manually specified on the commandline as stretching things to 1.5x gives slightly crappy artifacts and ruins the crispness of our pseudoretro graphics.
Notes we can specify the framerate to be anything in that code but 60 is best. Also note that we use a busy yield() loop on dual-core or better processors and a sleep() loop on single core processors. It can also be forced to sleep() on the commandline.
This might be a dumb question because you just posted the source code on this thread. But as indie how much do you worry about people cracking the game and running it with out paying. Is there any sleep lost to try to obfuscate code since Java is a lot easier to decompile then say C++.
What are your thoughts on waterfall vs agile i.e. up-front design vs iteration?
Are there any examples where some more design would have saved you a lot of rework and heartache later on? Do you have counter examples where you spent too much time planning and ended up having to rework because you wrote the wrong thing? Any specific areas where you would recommend one approach over another?
I lose no sleep at all over it. The game’s already torrented to hell and back and I figure that anyone who goes out of their way to find a crack really isn’t going to bother giving us fifteen lousy bucks. Maybe I don’t begrudge them as they’ll lose far more than that when their bank account is hacked from the keylogger that the torrented version installs but that’s their business*
Our next game is going to be online so I’ll sleep even more soundly at night. Or at least I will when I find a Java API for dynamically manipulating Linux firewalls. I expect I’ll write one that just shells out to ufw.
Cas
actually it turns out warezbb actually manages to install a keylogger just by surfing to their home page. Pretty impressive stuff. WARNING do not go there unless you have a spare VirtualBox you don’t mind losing.
[quote=“loom_weaver,post:69,topic:36432”]
A full post-mortem would be a good article to write for the blog to talk about all that, so I’ll get cracking on that I think. Basically, we do have a very iterative approach to development, and release frequently and as early as we can without it looking utterly rubbish. Early feedback is good. Droid Assault and Revenge of the Titans were heavily shaped by player feedback.
That said, we totally scrapped Revenge three times in a row. I just couldn’t get a fun game out of it until fourth time lucky. It started off as a single-screen, non-scrolling, 320x320 game like Titan Attacks, set in the dark with lots of lighting, and a very simple mechanic that now I think about it would probably work quite well on iOS. However we scrapped it because it probably wasn’t going to make much money, being the kind of entertainment found in Flash games all over the internet. Then we tried a game with graphics fairly similar to the current ones, but with no “terrain”, and just a constant spawn of aliens, and you manually aimed the turrets with the mouse. We got really far with that and were almost going to release a demo… except the gameplay boiled down to simply rotating the mouse around the screen and watching the monsters die till you got bored. Then I got cold feet about the whole graphics thing because everyone told us that’s why our games don’t sell, so I tried medieval wizards in towers casting spells at gobboze, but that was kinda pants and didn’t fit the rest of our games. So that got ditched, and I tried another prototype, again without scrolling, where the turrets did all the shooting themselves. That one grew and grew into what you see today as the current game through some particularly torturous continual extreme refactoring. So you could say waterfall or agile or not, it’s definitely the product of XP mantras. I’m not sure this approach would work with a bigger team as they’d get really annoyed.
Various dumb implementations of algorithms that didn’t scale well such as turrets targeting gidrahs every frame, etc. Quadtree was a crappy choice of collision manager as nearly everything is roughly the same size or smaller - changed that to a cell based collision manager, got loads more performance. Had to switch to VBOs to get necessary sprite performance (pushing thousands of sprites per frame now). Various tweaks and hacks required to make sprites invisible when offscreen (things like emitters which are way offscreen are turned off for example). There’s a bit of poorly conceived code that colours the entities according to their distance from the centre of the map which should really have been precalculated. In fact I may still do that. That’s the biggest performance bottleneck I think!
The biggest bottleneck I’ve been unable to control is Java bounds checking. Java 7 gets some performance boost in this area but nowhere near enough. This is a guess based on what I think the average C++ application would manage, sprite-wise.
We should probably have a sprite shootout sometime eh? I’ve got quite a nifty sprite engine.
One more question because it’s nagging me for years now:
The sound that plays when the player ship appears in Alien Flux always reminds me of the beginning of the extended version of The Cure’s “A walk”. Is that just me or was the sound borrowed/inspired by it?
Given your avatar I’d bet you have some good ol’ The Cure records in your shelves
Oh and btw: I would love to buy Alien Flux and Super Dudester nowadays cause I loved both games when I played the demos years ago (but couldn’t afford them)…
It is indeed a slightly granularised sample from A Walk (And the Jellie’s maniacal laugh is from Lucretia by Megadeth). I’m not sure what to do with Alien Flux and Dudester at the moment as they have succumbed to code entropy for years and need a bit of refactoring and bashing with a hammer.