Category: programming

2D Targeting for AI in Game Maker 8

After several weeks of effort, I have finally nailed an effective set of 2D targeting scripts for AI in Game Maker 8.

The story for this is worth telling sometime, but for now I’ll just be posting a video demo:

Source .gmk is available on Releases.

I’ll be refactoring this into a Game Maker Extension (.gex) soon as well, which will also be available along with full source.

4 quick GML coding tips

When writing scripts in Game Maker Language projects, I have come to realize a couple things that I want to share for anyone else who might be working on GML projects:

GML parsing is a bit loose. This makes the language fault tolerant, which is nice for newbie programmers who don’t need to get beaten up for not following strict syntax. But by enabling you to get away with imprecise syntax, it lets you get in to trouble that isn’t easy to detect.

Strict syntax is your friend, if you have it as an option in the language you’re using, I recommend following it as soon as you’re comfortable. Strive to get comfortable as quickly as possible. Unfortunately, GML does not have a strict mode…so I have the following tips to offer:

  1. Scripts (sometimes referred to as “programs” in the official Game Maker documentation) are supposed to begin and end with curly braces. The interpreter is forgiving if you don’t do this. But you should always do it.
  2. GML terminates statements with a semi-colon. The semi-colon is semi-optional, but I recommend using them wherever you intend a line of code to end, so that the interpreter doesn’t have to do extra work guessing that for you. Doing so will make it look like you know Pascal, C, or Java more than you know VB, which is enough reason to do it;) (Usually a statement is more or less the same thing as a line of code in your script file, but some structures may incorporate blocks of code, such as loops or branches.)
  3. Strings in GML can include line break characters (there’s no escaping with \n or using a character code like CRLF, like there is in most languages), so if you want to have a string with a line break in it, you simply hit the enter key and put a literal line break in your string. The interpreter handles this OK since the string is bounded by double or single quotes, but it’s still a weird thing about the language that they don’t really explain adequately in the documentation. If you’re not using semi-colons where you intend to end a line, it can get confusing to look at a multi-line string declaration that incorporates the line break as part of the string.
  4. It’s easy to not scope your variables appropriately without realizing it. GML allows global vars, and has two kinds of locality: locality with respect to an instance of an object, and locality with respect to a script. You shouldn’t use globals unless you really need to; according to the language doc they are slower to access than local variables. I haven’t noticed any difference that I can measure, but I’m sure that it is something that adds up, and at any rate proper scoping is a good habit to get into.When I refactored my project to pull drag-n-drop code out and replace it with scripts that were more re-usable, this caused some of my instance variables to turn into script variables. I had to go back and turn them into instance variables once I discovered that this had happened, and caused some issues with the way the code was executed by the interpreter.If you’re using variables in a script that were not declared in the object somewhere (normally the best place for this is in the object’s create() event), and you are declaring the variable in the script, but you want to make it an instance-local variable, you can do so. The code to do this is:

    obj_MyObject.MyLocalInstanceVar

    That dot between obj_MyObject and MyLocalInstanceVar is actually an operator. Knowing this is nifty, but I am not prepared to expound on why just yet.

Keep reading the language specification doc! Every time I go back to it and read, I pick up something I didn’t know, or refine something I thought I knew. One of the really nice things about GML is that it’s a small enough language that you can pick it up and do things with the basic features right away, without needing or being overwhelmed by all the other stuff that you’ll discover as you continue to progress as a developer. And there are enough built-in features in GML that you don’t have to spend a lot of time figuring out how to build sophisticated stuff out of primitives all by yourself.

The more I work in GML, the more respect and appreciation I gain for it. It is not the purest language, nor is it the most powerful, nor is the development environment as rich and sophisticated as a “serious” IDE like Visual Studio or Eclipse, but it is extremely accessible for a non-programmer to pick up and start working with and being effective quickly.

Refactor often

If you dread or fear refactoring, it’s because your code is a big ugly mess.

If your code is a big ugly mess, it’s because you didn’t refactor soon enough or often enough.

To make your code not be a big ugly mess, you need to refactor it.

So, refactor every time you see the opportunity. Keep the code lean and pretty. Then refactoring stays simple and easy and doesn’t take long or hurt.

I say do this even if you feel mounting deadline pressure. In the long run, the gains in maintainability will far outweigh perceived slowdown of productivity. Working on maintainable code is far more productive than working on unmaintainable code.

Don’t ship messy code. Messy code hides many flaws. Flaws that you’ll need to fix sooner or later anyway. When you fix a mess, it’s a lot more work than fixing something that’s clean.

Yummy GameMaker Goodness…

Good technical documentation is a beautiful, beautiful thing. Almost as good as bug-free code.

Ultimate DdD to GML Converter

It occurred to me recently that my Actions could be easier to understand and debug if I refactored to convert as much of it from Drag-and-Drop actions into GML scripts. It’s a LOT easier to understand a script call to “Player_Eats_Fish” than it is to read a dozen or more DnD actions that do “Player Eats Fish” and figure out what they’re doing. Plus, you can call the scripts elsewhere if you need to.

So I started doing this, and realized that there were certain things that I didn’t know how to do. Reading the language reference in the GameMaker Help helped, but at times I still got confused… Then I googled and found this. UDnD2GML does a nice job of converting Drag and Drop actions into valid and correct GML. It’s actually written in GameMaker, too; that’s right, it’s a GameMaker project that converts GameMaker drag-n-drop actions into GML. Very slick! When you need to work out a bit of tricky syntax, or are dealing with some function you don’t use a whole lot, it’s indespensible.

Drag and Drop Icons and Their GML Equivalents for Version 7.0

Basically the same thing, only a big hypertext reference document. I liked it so much, I printed it to PDF format and am keeping it with my other GameMaker docs. Although this is for GML7, there doesn’t seem to be a whole lot that has changed in GML8. Could be a bit clearer in places, and incomplete in some regards (I had to use UDND2GML to figure out how to apply instance_destroy() to other; this document was silent on that point.)

Official GameMaker 8 Help File as a PDF

Not hard to find by any means, but if you don’t have it, it’s the best place to start. A lot nicer than searching through the Help Menu index.

Boobie Teeth 0.17, CBNA SmartLight, and Google Translation

I’ve spent the last few days getting back into the Boobie Teeth project, trying to figure out how to do a couple things that have had me stuck for longer than I’d care to admit. I’m far from giving up, but I have come to the conclusion that hacking away at the problem isn’t going to be as fruitful as reading up on the problem.

At any rate, here’s a video of what I managed to pack in to today’s release, 0.17:

The major accomplishment for this release was the addition of a transparent gradient in the foreground, which enhances the background gradient that gives the illusion of diminishing sunlight at depth. For now this is just a cosmetic feature, although I already have ideas for tying it into the gameplay.

The other thing I worked with in this release, but dropped for now, is to implement some basic AI. I want the fish to get hungry and start chasing down prey. This is a lot harder than I thought it would be, owing to limitations of GameMaker’s instance handling. My usual approach of coding a little bit and seeing what I get, then coding a bit more once I’m sure what I already have done is working hasn’t gotten me very far. I’ll be researching and studying until I figure out an approach that works.

While researching, I came across a beautiful video of some lighting effects done in GameMaker which blow my simple foreground gradient away completely:

I noticed of course that the video appeared to come from French-speaking authors, but that didn’t dissuade me from tracking down the package that enables these effects and downloading it.

Let me just take a moment to say that I am absolutely amazed by the quality of google’s translation service from French to English. Seriously, click this link and you’d hardly even know that you’re on a French site. This impresses me even more than the lighting effects. If you happen to spot a mistake in translation you can hover over text and see the original, and provide feedback to google directly through the site that they’re translating for you. The translation is extremely fast, almost real-time, too.

It’s going to take me some time to work my way through the examples and tutorials and translating the documentation, but if I can get this SmartLight to work with my game project, it’s going to be well worth it, and make the game look way better than I had originally planned. I really want to get some AI going in my game though, so it’ll probably be a while before I get into re-doing the lighting effects.

Still more reflections from CodeRetreat

I’ve had some decent response to the posts about CodeRetreat. I had some more thoughts come to me in the days following, mostly on pair programming, but no time to write it until now. Here’s a brain dump:

Assert(Two_Heads>1);

This was my first opportunity to try it out, and I instantly liked pair programming. Much of my frustration in trying to learn how to program has come from trying to learn in isolation, getting stuck, and having no one to go to for help. Pairing means you have someone right there who you can talk to, and who can also catch mistakes that you might not have caught until much later.

Be Likeable

I think pairing is a great idea, but with the caveat that it requires you to like your partner. Establishing rapport is critical. I would say that if you are trying to program with someone and have yet to click on a personal level, it might be a good idea to stop trying to program and start trying to connect. Find some way to do this. Play a game that fosters working together with a common goal, like Jenga. If you can’t connect, then it might well be better to not try to pair up, or even not try to program together at all. Divorce is greater than the sum of its parting.

There’s only so much you can do about your partner, but you can do a lot to be likeable. Don’t focus immediately on the problem, but on the partner first. Smile. Be cheerful and enthusiastic. Remember names. Listen. Avoid negativity. Try out your partner’s ideas. When you think something, explain why you think it. When you want to try something, explain why you want to do it and what you hope to accomplish. Then don’t spend a lot more time talking about it — do it. What works becomes a lot more apparent at runtime than any other time, and the quicker you get there the better.

Assert(IsEqual(Life.Spice, Variety));

That’s not to say that you should never pair up with someone who has a very different style from yours. Very often that is precisely the type of person who you should be looking to pair with, because their differences will be the things that you learn from. Still, someone who you can communicate with easily is a must. You’re not going to enjoy learning from seeing a different style if the person demonstrating it is incapable of explaining it in a way that you can understand. Programming style is not the same thing as personality.

If you don’t get along well, or have very different styles, there’s a risk of ego clashes. Frankly, there’s always going to be a risk of ego clashes. Fortunately, this did not come up at CodeRetreat — the people who attended were there because they wanted to be there, which I think makes a huge difference. If your partner isn’t at least pair-curious, if not outright interested, they’re apt not to be a very good partner.

Swing your partner

Much like… uh… square dancing, pairing is even better if you can do it with more people. Try to find more people to pair with. Put on a snazzy shirt, go to bars, wear a lot of jewelry, buy someone a drink, and ask them if they code. See where that leads.

Good pairings are unequal

Intuition suggests the best pairing combinations are unequal. The value two great programmers contribute to a pair is not as great as the value created by pairing a great programmer with an average programmer. The great-great pair may be more productive, but the great-average pair will produce another great programmer in time.

Pairing isn’t just about mentoring and learning, it’s also about collaboration, error catching, and creativity. But mentoring and learning is a hugely valuable part of it — in most cases, probably more valuable than the resulting product.

Dance like no one’s watching, Code like your repository is /dev/null

Being willing to give things a try is a huge factor to pair success. I think what made CodeRetreat so easygoing was that it was deliberately structured to be coding without consequences. We threw out what we’d written at the end of each session, so there was no goal of getting it done, no stake in one person’s approach “winning”.

In the real world, there are consequences. But if you can, try to structure the project workflow in such a way that you can code as though there were no consequences. Be willing to take risks and experiment. Don’t just stick with what you know; grow.

If the established code base is getting in the way of this, don’t be afraid to cut it. From working on numerous development projects over the past five years, I’ve learned that the actual coding takes hardly any time at all, relative to the rest of the project. Requirements gathering, design, and testing all take considerably more time in my shop. Therefore, starting over rewriting code should not be intimidating.

In fact, going back over 20 years, to the bad old days when operating systems didn’t have protected memory and applications didn’t have automatic data recovery, in every instance I can think of when I was writing something and lost it, whether it was prose or code, the re-write was always faster and the results better. So don’t be afraid to take a clean-slate approach at times.

Start over

If CodeRetreat had taught me only one thing, it would have been that it’s not just OK to start over sometimes, sometimes it’s quite helpful. Too often, though, we attach value to code that has already been written and are reluctant to throw it out. Even if it was written as a short term stopgap, if it basically works no one wants to let you throw it out. “Reusable” code is something of a mantra in a lot of shops, as well it should — rewriting the same thing over and over again sucks. But if it wasn’t the right code to begin with, reusing code is about as appealing as reusing toilet paper. Before you try to re-use code, be sure that it is in fact re-usable.

Addendum to Things I learned at CodeRetreat

The most memorable moment for me was in the afternoon, when I spoiled a rant that Mark was about to launch into about a perceived problem he had with TDD “forcing” the programmer to code improperly by requiring them to expose inner members of classes that should properly be kept private.

Instantly, I blurted out, “So write the test method inside your class!” It was kindof a “from the mouth of babes” moment. I barely even thought before the words came out of my mouth. I wasn’t even sure if that was the right answer or not, but I guess it must have been, because the look on Mark’s face was priceless.

There were so many messages in that look: 1) that I was right with my guess; 2) that Mark had been stuck on this mental error for some time; 3) that it might’ve humbled him a little bit that he hadn’t seen it before.

I don’t mean to embarrass him by recounting this — if I thought this would, I’d never be using his real name. It wasn’t that I’d really shown him up — he’s got way more experience than I do, plenty of success under his belt, and he’s someone I like personally as well as look up to. But that was the first moment I felt like I was capable of making a tangible (if relatively tiny) contribution, that it wasn’t just a one-way knowledge transfer from the more experienced programmers at the event to me.

What made it a moment I’ll never forget was that I saw that I wasn’t the only person who gets mentally stuck in my own problems when it comes to programming. If a guy like Mark can still make a mistake like that once in a while, it makes me feel a lot better about doing it too, now and then.

But the bigger lesson is, it totally reinforced the value that we all get from being in the same room, talking to each other, making mistakes in front of each other, and learning from each other what lessons we might. With enough eyes, all bugs become shallow.

Things I learned at CodeRetreat

I went to a local CodeRetreat event hosted at Lean Dog, an agile development studio in downtown Cleveland. I had a great time, and learned a few things. There were a great many more things there to learn than I was able to learn, but that just means I’ll have an equally great time at the next one.

We broke the day up into 5 sessions, working in pairs on a test-driven development exercise. At the end of each exercise we deleted what we coded and paired with someone else and repeated the exercise. It was interesting to see both how different pairs would approach the problem, and how I approached the problem differently with each successive pairing. One of the interesting things I got out of this was how my own thought process and approach to solving the problem improved each time. Even though just 45 minutes had passed, each successive attempt was markedly better than the last. I’m intrigued by this and will be less reluctant to throw out code and start over in the future. I also gained greater appreciation for how important it is to pick good names for your variables, objects, properties, and methods. Good names really make it easier to think clearly about both the problem and your code.

I’ve heard of, and seen examples of, test driven development before, but never done it myself. I got an introduction to NUnit, which I’ll definitely be making greater use of in the future, courtesy of Mark W. Schumann of criticalresults.com. I got to pair with Jeff “cheezy” Morgan, who demonstrated some very keen insights on the essence of TDD, and gave me a little exposure to the Ruby language.

I’m really happy I went, and can’t wait to find more events like this to take part in. Lots of thanks to Corey Haines and everyone at Lean Dog for putting the event together.

Fixed that bug

Haha, I figured out what was causing that bug I ran into the other day.

While reading the GameMaker documentation, I read that the order in which objects are drawn is determined by their Depth, and inferred that it is not just the *drawing* order but the construction order when a new room is instantiated. (In GameMaker, Depth is a property that all Objects have, which helps determined which Object is drawn “on top” when their sprites overlap.)

I happened to have my Fish objects at a higher depth than the Player, because if the Player was overlapping the Fish, I wanted the Player to be “on top” so that the Player would always be visible. As an unintended consequence, the Fish objects got drawn and created first, and since Fish.size depends on the value of Player.size, and so were throwing exceptions when the Player did not yet exist.

For some reason this bug was always latent in my game project, but it only manifested after I added a Background to the Room, which is a bit odd. I am guessing that a Background has an extremely high depth so that it appears “behind” everything else in the room, but why it seems to trigger the creation order bug is mysterious to me.

What’s more, I am also confused as to why one of the things I tried to prevent the error did not work. In the Fish.Create() event, I added a check to make sure that the Player object exists before assigning a value to Fish.size that is based on Player.size. If !Player.exists, Fish.size is set to a safe value that is not based on Player.size. Yet, for some reason, the check was successfully able to determine that Player existed for purposes of Player.exists() check, but yet was not able to get the value of Player.size in order to calculate a randomized value for Fish.size. Apparently, the Player object exists at this point but its size attribute does not, or perhaps contains an undefined value.

Figuring this out was rather tricky and it never would have occurred to me had I not been thinking about this bug when I happened to be reading about how the Depth property determines drawing order. Hopefully this writeup will help some other GameMaker developer avoid making a similar mistake.

?! >:/

Something very weird going on in my GameMaker project…

I have two Object classes: Player and Enemy.

Player
{ Create() {size = 2}
}

Enemy
{ Create() {size = random_range(0.5, Player.size*3)}
}

This code was working fine up until today. Technically, I should be checking to see if Player exists before I try to use Player.size, but I set things up so that Player always exists and was created before any Enemy instances, so it never threw an exception. I understood the risks and tested it and it never had a problem.

Today I created a background image for the room where the game takes place, and added it to the room. All of a sudden now, the Enemy.Create() event is throwing an exception at Player.size because it is an unknown variable.

I scratch my head a bit and try to figure out why…

Was there actually a fortuitous timing condition prior to adding the background that caused the Player instance to be created before the Enemy instances when the room was being constructed? I try removing the background, but the game still throws the exception. I try creating a new room, adding the background to it, then the player, then the enemies, in that order, and it still throws the exception.

OK, let’s be more careful, and check to see if Player exists when in Enemy.Create(), then.

Enemy.Create()
{
If instance_count(Player)>0
{
size = random_range(0.5,(Player.size*3))
}
}

Game STILL throws an exception trying to access a non existent Player.size variable.

OK, did my game project somehow get corrupted? I roll back and test, things are working, then re-implement the background, and the bug is back again.

Does a room with a background image somehow conflict with Player.size? I run the built in variable name checker, no conflicts.

Hmm, what if there’s a name conflict anyway? I try renaming the size variable to foo. Still throws the exception. I try deleting the variable and adding a new variable that performs the same function. No better.

I run the game, Ignore all the thrown exceptions, and then once all the Enemy instances are created the game works. Clear level, next level is fine, no errors.

Alright, last thing: I pull size out of the Player object and make it global. Surely if Enemy isn’t capable of peering in to Player’s public variables (GameMaker doesn’t seem to have a distinction between public and private object properties, at any rate — everything is public for sake of simplicity for newbie programmers) but what the hey, let’s try it. Add the global.Player_size variable, attempt to access it in Enemy.Create(), still throws the exception.

To quote Moss from The IT Crowd, “What the ever-flipping flip?”

Player.size most certainly exists, why can Enemy no longer access it?

Update: To troubleshoot further, I created a new gamemaker project and made it as simple as possible. I created two sprites, two objects, a room, and a background. With the objects, I assigned a variable in the create() event, and tied the value of object0’s variable to the value of object1. Then added both to a room, then ran the game. No error. Then added the background to the room. Again, no error. So, it doesn’t look like this is a bug in GameMaker, then. Something must be messed up with my game project, and it’s up to me to sort through and figure out what it is, then.