Archive for the 'games' Category
Left 4 Dead: Tank Misconceptions…
This is a repost of something I wrote for the forums. Now that it’s lost to that refuse heap I thought it could also wait out its days in solitude here. If you haven’t played the video game “Left 4 Dead”, this will probably be just about unintelligible.
It’s understandable that a lot of people don’t really understand the Tank. He’s a rare spawn in general, and splitting the chance to play as him with three other people means little chance to practice. This affects the survivors too, as his scarcity makes getting experience dealing with him more difficult. That said, here’s a bunch of things about the Tank that people either don’t know, don’t think about, or are just flat out wrong on:
- We should run like headless chickens:
This is just about never the solution, especially on VS. In most Tank encounters the real problem isn’t the Tank, it’s the other infected. Don’t panic and run like nuts, carefully stay out of his range and concentrate on keeping the area clear of other infected. They move faster than you do, the Tank (usually) doesn’t. A single normal infected can mean the difference between escaping unscathed and as that big wall of muscle turns you into a pothole.
The other problem here is that, while everyone is busy running away, they aren’t shooting him. The real approach to handling a Tank is to work as a group to keep your distance from him, and whoever isn’t being chased by him is shooting him in the ass. There’s two reasons for this: A) it does damage, which will eventually kill him (that’s the point, remember), and B) being shot temporarily slows him down, so being shot continuously will give his pursuee breathing room. This is key for saving someone who is < 50 health, or who runs into an unexpected obstruction. Try it, it works.
- Set him on fire, then run like headless chickens:
In addition to all of the problems above, (in co-op only) setting the Tank on fire makes him run faster, which also means he runs faster than you. If your teammates are busy running in three separate directions he can now easily catch up to you and turn you into a pothole. Fire is great insurance against the Tank, but it won’t kill him fast enough to make a difference on all but the easiest difficulties. Fire+scatter is just as lethal in VS as scattering is.
- As Tank, the rock throw is useless:
This just isn’t true. In some situations where the survivors are grouped together somewhere that puts you in easy range of a molotov and/or spray of bullets, it’s better to hang back and go for some rock throws from a safe distance. Picking up a rock tends to make anyone you aren’t pointed at brave, and makes them forget that the direction of the throw isn’t determined until just before it happens. Learn to grab a rock and do a last second turn and throw at someone pelting you in the back. It’s pretty much the only option to handle survivors that are kiting you well if your buddies let you down.
- As a Tank, I should just charge in and do some DAMAGE!!!:
No, you shouldn’t. Well, maybe, but it isn’t that simple. You start out with a lot of rage time (I think 40 seconds). Take advantage of it. If the survivors keep advancing, look for the best spot to hit them (tight hallways) and bide your time. At the least, give your teammates time to spawn in and help you. If need be, try to get a distance rock throw in and retreat with your freshly regenerated rage timer. Like any other class in the game, the Tank depends on teamwork to get the job done. The only reason this doesn’t seem true is also the point of this post: statistically, no one knows how to deal with a Tank.
The other problem with charging in is that it’s predictable, and can be costly. On the NM5 roof the absolute worst thing to do is charge right through minigun fire at the survivors. Congrats on losing half of your health before you even get close to anyone. You’ll probably get set on fire too. If you hang back, especially if you run around the roof for an alternate route, you can hit them from a better position and give your teammates time to distract them. A survivor ready to throw a molly looks pretty distinctive, and there’s nothing funnier than owning up as a Tank because you waited long enough for a hunter to pounce the guy trying to set you on fire.
Parking a tank is pretty much sitting somewhere and letting your rage run out, so the tank remains stationary until triggered by the survivors, like in Campaign. The most notable place to do this is in NM4, in the final safe room. Not very honorable, but if the survivors are vent camping (behind the elevator) with a full stock of mollies and auto shotties, it’s just the way to stick it to them.
(Portion in *’s is courtesy of OmNomNomNom, thanks)
- As a Tank, I should hit anything I can reach:
Usually, this is true, but if one of your teamates has pounced or grabbed a survivor your new priority is to protect that situation at all costs. In practice, a Hunter pounce does more DPS than Tank punches unless you consistently land every single punch as fast as you can swing (hint: no). If one of your teammates has a survivor, do your best to keep the rest busy and away from him. At the very least, don’t knock him free. Given that you have the ability to hit anyone else, you’re cutting into the team’s DPS by doing so.
- As a Tank, I should hurry up or else I’ll lose control:
This can be a good thing. IMO they shouldn’t have put individual scores for infected in L4D, it just encourages people to play for their score instead of for the team’s benefit. Sometimes you need to wait for the right moment to strike, and sometimes that moment is after your rage timer expires. In my opinion it’s usually better to run in rather than let the AI take over, so if you’re the second one with it take your best chance within the rage timer. The only time this doesn’t work is if you’re trying to park him somewhere, but that’s obvious.
Other important facts:
The Tank’s swing takes a little time to connect. Try to lead your swing a bit if you’re running up to a survivor. Otherwise you give them a little time to get out from in front of you before they get hit.
The rock throw doesn’t auto fire if it’s held down after it charges. You have to spam the button if you’re trying to bombard the survivors.
In co-op, fire makes the Tank run faster. Be sure you have plenty of space to run around him, and are able to kite him around something while everyone else shoots him. In VS, it’s just about a win-win, just make sure you don’t have to run through the fire too much.
Vents and windows seriously slow down the Tank. Abuse/avoid this as appropriate. In co-op NM5, jumping through the window gives you a good 10-20s of shooting gallery time while he squeezes his fat ass through (portion in *’s courtesy of thomasng1989)
The AI Tank likes to climb stuff. If you’re kiting him around something watch for him to try and shortcut over it.
Meleeing an AI Tank in the back will cause him to focus on you. Use this to get his attention away from a downed survivor. Melee him and run away shooting to keep his attention. Only do this if the other two survivors are there to fire on him as well, otherwise it’s probably suicide. This is just about the only way to save someone on Expert.
You can shoot the rock. If you’re out in the open, do this. If you’ve got any kind of cover, that’s probably the better alternative. It takes a few hits to break, so don’t be surprised if this fact doesn’t always give you rock immunity.
Explosions (propane tanks, pipe bombs) stagger the Tank. They also do a decent bit of damage. Hitting him with a pipe bomb probably won’t happen, but if you can get him to run over a propane tank or three when you shoot them it can do a lot to help.
Unless you’re in green (correction: above 40 health), he runs faster than you. So if you’re in anything less than green the Tank music is your cue to heal up, get your pills ready, or put some distance between you and him and make peace with your maker. As a Tank, be aware that there’s a ten point range where someone having a yellow outline does not mean you can catch them.
Sections in *’s courtesy of madcap, among others.
Hitting survivors with an object (car, tree trunk, generator, anything outlined in red) is the fastest way to take them from green to incap. Go for it when possible, but watch out for situations (like the NM2 room below the generator room) where survivors can easily duck behind obstructions.
If you aren’t the Tank, he needs you to help slow the survivors down. They run faster than he does, but not if they’re Boomered and surrounded by commons. Likewise it’s pretty easy to meet the survivors at their target when they’re running to help a buddy who’s been pinned or tongued … or hit them with a rock while they’re doing so.
Avoid fire at all costs. Many survivors will throw their molotov ahead of you a bit early to give themselves more room to run. Watch their outlines; if they have the molly out look for the opportunity to stop short of running into flames. If you are on fire, you’ve only got a limited amount of time to go do some damage, make everything you do count.
Don’t be discouraged if you don’t do well. Sometimes you get outplayed, or your teammates don’t help, or you just get unlucky. If you tried to do what I outlined in this guide and still didn’t do much damage, examine how you (or your teammates) deviated from the suggestions or maybe how the survivors really did outplay you. I very probably do not know everything about the Tank, maybe you can learn something from your defeat and have something to share. Above all, anyone balling you out for not doing well is a punk or just a poor sport, don’t bother listening. Take constructive criticism and spend some time thinking about what (if anything) went wrong.
Memorize the finale sequence, and be prepared to help the Tank after the second infected wave. If you get the Tank, make sure to spawn your current infected in before he gets control, a bot infected is better than none at all.
If the survivors are doing a great job staying out of your reach, look for a good way to quickly get behind cover. Give your teammates a chance to distract them or give them a chance to slip up and give you an opening.
As survivors, be sure to have at least one person with an automatic weapon (Uzi/M16). Since every bullet slows the tank down on impact, these guns do a great job of keeping him away from everyone else. Also, take advantage of the minigun any time you can. It does great damage and also works to slow him down.
In stages where it is applicable (namely, NM4/NM5), look for opportunities to knock survivors off the edge of the building. This is a one-hit death for them, and can often be achieved by strafing as you move to position yourself opposite the edge of the building from the survivor. As a survivor, prefer taking a hit from the Tank to putting yourself between him and the ledge. Get off the top of the building (with the mini-gun nest) in NM5 as soon as you can to avoid being punted off. Any of the elevated areas in this level are dangerous, but it is usually difficult for a Tank to position himself to knock you from the lower levels.
I’ve lost my mind
After spending some time thinking about what is going wrong with this game, I came to two conclusions:
Repl-based development doesn’t work for me. I ended up spending a lot of time working my way into an inconsistent game state without know it, so most of my time savings were spent figuring out how I’d borked things on the next restart.
Haskell is just too damn interesting. Every time I tried to work on the project I found myself goofing off there.
Obviously, these points say much more about me than the tools I was using, but the simple fact was that I still wasn’t getting much done. I actually really liked how clean the more functional bits of my original game code were, so I’m taking the plunge and switching to Haskell for my game.
Since I’ve been wanting to dig deep into Haskell for a while this should really push me there. So far just trying to express something as simple as “a game unit can possibly target something targettable” has been a deeper rabbit hole than I thought, and now that I think about it my current solution won’t work. Luckily the new one is simpler.
I’m well aware that this will probably mean the death of this game, but it already seemed to be in critical condition, and either way the learning experience would be good. Stay tuned to see what happens.
No commentsBack 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 commentsA “dancing quadtree” based scene-graph
In thinking about how I want to manage my games, I’ve spent a bit of time mulling over the options for scene management. Specifically I want to handle collision detection efficiently, as most of my recent ideas start out with “what if we had a swarm of X”. I’d also like to have something useful to efficiently simulate an object interacting with neighboring objects.
One immediate idea is to base my scene graph on Quadtrees. These are relatively simple to implement (since they are two-dimensional binary trees) and would make scene subdivision easy. A quadtree could easily divide the scene up into whatever depth I want, and objects in the tree can discover their neighbors through simple tree traversal mechanisms.
The first problem I have with it is overloading. If I have fifty of item in one quadrant and relatively few in the rest I’m still not doing very well. Taking a page from Hans Reiser’s “Dancing Trees” algorithm I could provide a threshold mechanism so that quadrants rebuild themselves in various sizes after they become imbalanced. I’ll have to kick the idea around some more, and probably should just start with a relatively simple management system first and build up more complicated ones as I find I need them.
No comments