User Interface

Published July 01, 2008
Advertisement
Welcome back to my semi-annual series on Milkshake development. Last night I was reading the hackenslash diary ... that whole game was made in a week! I think I can safely say I haven't achieved anything close to that in the last year ... really makes you wonder if you're going about this the right way.

Anyway, I have at least made a little progress toward my goal of a first playable this year - but if I was half as honest with myself as the guy was in hackenslash, I'd probably admit that I was a tiny bit behind schedule. I'm currently trying a new strategy for making it playable, where I sit down with my project and load it up - and the first thing that doesn't work like an actual game goes to the top of my todo list (note I very deliberately didn't say "look like an actual game" because I'm trying to avoid polishing thing ... well that and I'm a bit of a crap artist).

Suffice it to say I didn't get very far without finding something ... so this entry will look at the startup menu =) Nothing fancy mind you - just a basic front end to the game - I'm even going to skip the game options for now. We'll start by looking at the result, and then I'll show you all the grief underneath it:

Disabled

So how does this all work? Well, I had some very crude UI elements (menus, buttons, text labels, etc) kicking around from the game's editing tool (covered in a previous entry), but they not only looked pretty utilitarian, but they worked a lot like the MFC UI elements - with a bunch of callbacks into the code with user-defined event Ids. Essentially, it meant every time you wanted new UI, you had to write a bunch of C++ to do whatever it was that menu was supposed to do.

In typically bad style, I tackled the ugliness problem first by extracting the UI "style" out into a separate object that "skins" a set of generic UI definition objects. This means a Column menu can look one way in the Editor, and a totally different way in the game. It also means when I get around to writing my World War II POW game, I just have to write a skin and away I go.

Next I looked at the harder problem of how I could build a menu without writing a whole bunch of C++ code every time I did it. But before I get too far into the solution, I should perhaps explain something else about my engine - you can do everything from objects and/or script. Well, not everything, but when I find something I need that I can't do, I add it. This includes creating instances of game characters, loading/saving maps, setting the screen resolution, etc, etc. So the plan for the UI was to turn the UI widgets themselves into part of the engine's "Object" system. This not only allows them to be serialised (so you can build some UI, save it to disk, and load it back in) - but it means they can use (and can be used) by anything else in the game. So you can have UI widgets that open doors in the game, or a door that pops up a menu.

Here's are the Objects behind the main menu, including what to do when the item is selected:

Disabled

[Note that the editor boxes don't show up too well on that black background ... whoops]

With a shiny new startup menu allowing me to start a "New Game", I quickly hit the next thing that was pretty un-game like: the game just drops you straight into the world with no idea what you should be doing or why you're there. Now I wanted to go for a bit of a comic/graphic novel style to my game, so I figured the next thing to add would be narration dialog. Just like the menu UI, there's a generic "Dialog" node, and then the "skin" renders it into the game. Here's a shot showing a narration box with an in game menu just because I can:

Disabled

The one thing that occurred to me while I was putting these test scenes together was that I'm very quickly going to need programs with loops in them. And in the current code, the connection "loop" would create a memory leak (because upstream connections hold references to the downstream targets - and a full loop means they all keep each other reference counted even if everything else in the game has forgotten about them). After a few minutes of panic thinking I'd need to embark on some performance glitching garbage collection scheme - I think I've come up with a way to detect loops when the connections are made and then just store a description of the loop so that I can clean it up when the next map gets loaded in. Quite a relief that.

I now need to work out where to next. I'm a little overwhelmed with the whole damage/death/load/save bit ... so maybe a bit of plot/story work first?

Cheers!


0 likes 7 comments

Comments

Jotaf
Wow it looks so polished with the dialog box! I just looked at the pictures and suddenly was fooled into thinking that the game was complete. :P

Don't be so quick to dismiss garbage collection and come up with a home-made algorithm. I was interested in the whole "dangling reference" topic once before and I thought I could solve everything; after some thought, I had to throw all my ideas away, because all of them had some fundamental flaw. Then I learned to recognize that when something is considered by the research community as a "hard problem", it means someone else put a lot of thought into it before I ever did, and some puny algorithms usually don't stand a chance! ;)

OTOH, I really don't mean to discourage you on this topic, and if you'd like to describe a bit what's on your mind I'd be interested.

On a lighter note, I think you just need to figure out who should be the cows' mortal enemies, and go from there! x)
July 02, 2008 04:54 PM
Milkshake
Heya Jotaf!

I did think about having "proper" garbage collection - but after years of writing high performance apps in Java, I was really keen to avoid it if possible. For 99% of the reference counting the game, the classes and the reference counting has been written to make sure circular references can never happen (e.g., in the scene graph, a parent node will hold a reference to all it's children, and an object will hold references to all its children). So adding overhead into every object (or worse, every reference) is pure overhead in these cases.

The only case where circular references are useful are in "program" objects (kismet style building blocks), where some AI or menu might finish one branch/task and return to a higher bit of the program. And given these are all based around a Connection object (not just a reference - as the connection handles data propagation between objects), I have a head start on both finding and monitoring circular references which makes it possible to design a solution that has a much more deterministic behaviour.

The only case it won't handle is where part of a loop happens in "program", and part are "normal" references. For example, if a child node's program code ends up connecting back to its parent somehow. This would need a more general solution.

Either way, for right now, nothing I've done hits this issue, so I've just scribbled down my notes on it and tucked them away for the day I need to tackle it. You don't have any good references on garbage collection schemes do you?

And your suggestion on a "mortal enemy" is spot on! I've been wracking my brain trying to work out the moral/political backdrop I want to use ... and so far I've really struggled to come up with an enemy and a scenario which I like that isn't totally cliched. Right now, my inclination is to have an evil goat style enemy. But I seem to change my mind on this every week.
July 02, 2008 05:50 PM
Aardvajk
Looks the ticket with that dialog. Glad to see this project is still alive and kicking.

Do you not think a mortal enemy should be some kind of vet with a long rubber glove?
July 03, 2008 03:30 PM
Jotaf
All right then :) I see you're set without garbage collection, my engineering senses are giving me the alarm that adding it just to handle recurring programs is total overkill.

Ahh so since you have two-ways pointers already, instructions can return to the previous level easily? Hmm... This gives me an idea for the perfect solution! C++ gets by without GoTo's 99.999% of the time; for that we have breaks and returns. How about this, you have a Block instruction, and then a Break/Return instruction will simply follow back the two-ways pointers until it hits the next Block. Execution resumes there. How about it? :) I can even think of better primitives: Restart (as "continue" when used in fors/whiles in C++) and Exit (as "break").

The Vet could be the evil mastermind plotting to conquer the Cow world and enslave them for evil experiments (like extracting milk). Or something. Could be Cows vs. Humans. Or Cows vs. Reptiles, since, well, they aren't mammals and stuff. Weird folks don't drink milk and are born out of eggs.
July 03, 2008 06:39 PM
Milkshake
Quote:Original post by EasilyConfused
Looks the ticket with that dialog. Glad to see this project is still alive and kicking.

Do you not think a mortal enemy should be some kind of vet with a long rubber glove?


Heya EC,

Rest assured - no matter how long I go quiet for, Milkshake will always be ticking along! With 2 kids and another on the way plus a fulltime job, I don't get as much time as I'd like unfortunately.

And that long rubber glove just doesn't bear thinking about! I wasn't thinking of having actual people in the game at all ... but if I end up giving him a little spaceship (which I might), I thought it might be cool to tie him back into the UFO cow abductions somehow =)
July 03, 2008 06:56 PM
Milkshake
Quote:Original post by Jotaf
All right then :) I see you're set without garbage collection, my engineering senses are giving me the alarm that adding it just to handle recurring programs is total overkill.

Ahh so since you have two-ways pointers already, instructions can return to the previous level easily? Hmm... This gives me an idea for the perfect solution! C++ gets by without GoTo's 99.999% of the time; for that we have breaks and returns. How about this, you have a Block instruction, and then a Break/Return instruction will simply follow back the two-ways pointers until it hits the next Block. Execution resumes there. How about it? :) I can even think of better primitives: Restart (as "continue" when used in fors/whiles in C++) and Exit (as "break").


You really got me thinking with your comments on garbage collection. The more I played with examples, the more worried I became. Any time a program refers back to any object that can in some way hold a reference to the start of the program, it will leak. So, for example, if an object's program tries to set a value on the object (e.g. make it invisible), then the reference counting from the object to the program will complete a loop and leak all the objects.

So I've come up with a hybrid mark and sweep scheme, using the assumption that you will need at least one connection to complete a loop (even if other non-connections are part of it). I'll keep a table of all the connections in the system (which should be far fewer than the number of objects), and then whenever I load a new scene/map in, I'll check any undeleted connections to see if they're part of a self-referential loop, and break them if they are (which will free all the objects). Because it's a game, I don't want to be allocating/deallocating many (if any) objects during game play anyway - and doing it at file load time mean the old scene will have been freed up - so there won't be many connections left to check at all. Should give me good performance, and the freedom to go crazy with my references.

But just to be safe, I think I'll #ifdef this scheme in, just in case I want to add a fullblown object-level mark and sweep as alternative down the road.

The block is a totally different thing ... and a very cool one. I'd been calling them functions or macros - but it would allow you to drag a box around a chunk of a program and then "call it" from multiple places. And then you could have returns inside the function. It's cool both for the program flow possibilities, and the scalability/modularity it allows ... unfortunately, it also brings along a lot of state management problems. It's somewhere down on my todo list ... but there's a lot of game play stuff above it.


Quote:Original post by Jotaf
The Vet could be the evil mastermind plotting to conquer the Cow world and enslave them for evil experiments (like extracting milk). Or something. Could be Cows vs. Humans. Or Cows vs. Reptiles, since, well, they aren't mammals and stuff. Weird folks don't drink milk and are born out of eggs.


Another vote for the vet eh?

I had been thinking of a reptile - a nice D&D like crocadilian style thing ... but my 2 year old was so scared of the concept sketch I did, I shelved it =(

July 03, 2008 07:17 PM
Jotaf
Ah alright then, I didn't know you were already knee-deep in this sort of structural complexity. Mark and sweep / loop detection seems fine. In my game there's always an owner for each object, usually it's just a list<> or a owner object, so all other references can be considered weak pointers. Usually they're double references like the connections you mentioned, so when an object gets deleted it either takes down a bunch of other objects with it or sets their pointers to NULL. It sucks to write special code for every single case, but it gives me more power over the decision (delete referenced object or just NULL the pointer). Still, it's in a simple enough state that it's not much trouble (until now).

About the vet, I dunno, it would be weird if most of the enemies were vets actually :) Maybe for a boss figure or something. Reptiles are cool, can't you make them cute so your 2yr old isn't so scared anymore? x)
July 06, 2008 08:20 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement

Latest Entries

Moose Sighting

1358 views

Constraints

1529 views

Moose

1263 views

Melon Golf

1810 views

Toon

1325 views

Spaceships

1078 views

Rendering Pt2

1170 views

Hardware Shaders

1205 views
Advertisement