Main Page » My Games » Villainmad »

2011/01/02 (Villainmad): Thinking Out Loud, Sequences, and Closed File Formats

Created:

Always use secure-HTTP / Secure HTTP / Permanent Link

So, with the new year,1 I'm going to be semi-periodically blogging about this kind of thing, both to let people know I'm not dead and as a "thinking out loud" exercise that helps me focus my thoughts by writing them down and exposing them to people in general.

Since I haven't actually done anything yet from an astronomical standpoint (where 1 A.U. = "completed game") except "getting a feel for C#," I feel like I'm still in a position where I can safely take a step back, look at what I've been planning, and ask myself questions like "Why am I going to do X?" and "How easy will it be to do X?" and "Would it be easier to do Y or Z instead?" and so forth.

I've got a case in point. Two of them, actually.

Sequences

So, I have "Sequences" and I have "Functions," and the two are separate entities. The difference is that Functions can have return values, and Sequences can have "wait" statements (i.e. here's an example of an entire Sequence: "Spawn an enemy just off the left-hand side of the screen moving right at such and such a speed, wait two seconds,2 have the enemy stop and fire a ring of eight bullets, wait one second, have the enemy fly off to the right at double the previous speed while the bullets fly towards the player and remain in a circular formation, wait four seconds to give the enemy time to go off the right-hand side of the screen, delete enemy."). This led to the following series of questions:

1. How easy will it be to implement mid-Sequence "wait" statements?

I was worried that the answer would be "fairly difficult." See, you can't just "make it stop what it's doing" beacause you need to make sure that 1. it's actually stopping what it's doing (there's a number of ways to get this wrong), and 2. you want to make sure it doesn't stop anything else (the system function "sleep" would pause the entire program). I started to consider other questions:

2. Would it be easier to do away with the concept of Sequences, and be able to have everything as Functions, but with a delay-before-starting for Functions that don't have return values?

Even though it's more complicated to describe, it may or may not be easier to program for "Function A: spawn this enemy moving in direction X at speed Y, set function B to trigger two seconds later. Function B: if the enemy isn't dead, stop its movement, spawn a bunch of bullets, and then set function C to fire one second later. Function C: have bullets fly towards the player in ring formation, and if the enemy isn't dead, have it start flying off at double previous speed, and sets Function D to run four seconds later. Function D: delete enemy." than as one whole Sequence above. But whether it's easier than the other way depends on what method is used for the other way.

3. What's the practical difference between a Sequence with no "wait" statements and a Function with no return value?

First, a bit of background:

I did not invent the term "function"; it is one of the many (often programming-language-specific) names for "subroutines." A subroutine is a smaller portion of a computer program which is capable of operating solo, and which (with a few ifs and buts) can be called forth from the aether ("calling" is the actual term for this) whenever the program wants from any other part of the program, including itself. They generally take the form of NameOfSubroutine(InputParameters, SeparatedByCommas); a given subroutine must usually have a specific set of input parameters (say, three numbers).

Subroutines fall into two different categories: subroutines which do stuff, and subroutines which tell you something. (The technical term for the "something" is the return value.) The two are not mutually exclusive; many subroutines, after doing stuff, then return the results of that stuff.

Example: let's say I wanted to have a given type of bullet which I want every single enemy in the stage to be able to fire — say, a purple circle 10 pixels wide with a hitbox 4 pixels wide which does 5 damage when it hits the player, etc. etc. etc. So, I create a function which takes the location and starting speed etc. as input parameters, fills in all the blanks, and returns the unique identifier of the bullet in question, so I can get at it later.

Q. Why can't you have a function with both "wait" statements/a delay and a return value? A. Two reasons: First of all, when you want a function's return value, you want it right now. Let's say you have a Function called, I dunno, NumberOfBees(), and you wanted to check if it was greater than 6 at a given point in a Sequence. If it takes five seconds to give you the answer ... well, what's the Sequence supposed to do? Pause while it waits for the answer? Second of all, a subroutine can't do anything after you've gotten its return value. It's called "return" because it returns to the bit where the subroutine got called, see?

But, uh, anyway! Ultimately, no, since Sequences and Functions are both essentially subroutines, there is no practical difference, which is yet another reason to scrap the distinction between the two; just create a rule that goes "you can have return values or wait-statements/delays, but not both," the same way you have "you can't have anything after a 'return' statement."

Moving on:

4. Is there any compelling reason to "wait" statements, rather than delays?

I quickly realized that the answer is yes: variable scope.

Variable scope can be summed up with respect to answering the question, "Does this variable exist outside this particular part of the program?" My setup will basically use three levels of variable-scope: Sequence-level (or Function-level; variables which only exist within a particular Sequence, i.e. "the number of bullets we're going to make within this pattern"), stage-level (variables which only exist within a particular stage, i.e. the number of seconds since the stage began), and global (variables which persist between stages, such as the score and number of lives and the difficulty-level).

In the example above, there's nine things I'm keeping track of: an enemy, and eight bullets. If this were a single Sequence with "wait"s, it'd be simple enough to open it by declaring a variable for the enemy and an array (a variable which is actually a list of other variables of the same type, so that the precise number of bullets can vary by difficulty or whatever) for the bullets.

But if I was using four different functions, I'd need to find some way of getting the information between them. You can't do it directly with Sequence/Function-level variables, it's a bad idea to use stage-level or global variables because there'd be conflict if you wanted to have more than one enemy do that simultaneously, and using them as input-parameters for the next one just feels so ... messy.

It would be easy to code for "different functions for delays." But after I'd done that, it'd be tough to code with it.

5. So ... how would one implement wait-statements?

Okay, so if I'm doing my own file-format, I'm going to put each action in a Sequence into a queue, which is a collection of things in which it is relatively easy to add things to the beginning of the list, and also relatively easy to see and/or remove whatever thing is at the end of the list. It uses the "First In, First Out" system of Doing Stuff With Collections Of Things.

So then what I'd do is, I'd set it up so that every Sequence would try to do everything in its queue until it ran into a "wait" statement, which sets a timer, and on the frame after timer ran out (so that "wait(0)" would actually just advance it to the next frame), it would resume the queue where it left out. (Each Sequence would have a specific timer-variable, and every frame in which a Sequence exists, it would do "If the timer is 1 or greater, decrement it by one; if not, run through the queue until you hit the next 'wait' statement." The benefit of doing it that way is that you could still set a delay before it begins, if it doesn't have a return-value.)3

So, the answer to question 1 is, "if I make the game-format from scratch, not very difficult at all."

Closed Source

But one of the reasons the previous issue came up was because I was considering not making the game-format from scratch.

This train of thought came when I was playing Aquaria, and discovered that all the maps, scripts, and constants (recipes, etc) were in plaintext files, just sitting there in the Windows directories. Well ... the map-format was XML and the scripts were Lua. But the point is that the entire game was effectively human-readable.

I brought this up with Chaoseed (who, uh, is going in the comments here by the name John Evans), who said to me: "One theory of game design is...if people want to hack the game, they'll probably find a way, so why not make it easy."

So, I figure ... why not make it easy? Why not get rid of XNA's content pipeline, and load images and sounds and whatnot from ordinary .png files, then load Lua scripts (or whatever) for the levels? For one thing, it'll save having to recompile everything every five minutes, and makes the game easier to mod for those who want to get creative. Hmm ... official support for mods, there's an idea ...

Uh ... well, if I used Lua in particular I'd have to come up with a new method of doing the "wait" function, thus throwing out the entire previous issue.

Psh, complaining about that leads to the sunk cost fallacy, especially since I haven't actually written a single line of code yet. I've put more effort into writing this article, and that was mostly due to writing up all the stuff for non-programmers.

Opened

So, yeah, I think that I'm going to be doing things differently than what I originally planned: Villainmad will be made of things which are human-readable, and can even be modified if you wanna. Lord knows I'll find it easier to work with. After all, this is more of an experiment than anything else; I wasn't going to sell it.

1Insofar I started writing this on Wednesday.

2Technically, it will be measuring it in frames (most games have 60 frames per second as the baseline, as will Villainmad), but let's keep this simple.

3Incidentally, fractional timers will be allowed. Why the hell would anyone want to do that? Well, let's say you have a particular bullet-pattern which fires a miniature spiral of bullets over the course of one second, and you want it to launch between 30 and 120 bullets, depending on the difficulty. In the former case, there will be two frames between each bullet, so it will go "Okay, launch the first bullet right now, launch the second one with a delay of 2, the third one with a delay of 4, the fourth with a delay of 6 ..." and so forth. But with 120 bullets per second, it will go "Launch the first bullet right away, the second one with a delay of 0.5, the third with a delay of 1 ..." and it would effectively launch two bullets per frame.

46 Comments (auto-closed) (rss feed)

Page 1 2 / Previous / Next / All

Wyrm

My general point was that, as far as this project is concerned, I really didn't see the immediate point of implementing it.

That's encouraging. You probably don't need the full enchillada now, but it's useful to think about what you might need right now and programming towards that end.

Maybe splines are a little opaque right now, but Bezier curves of multiple orders are easy to grasp. Take a gander. Basically, if you can do a linear interpolation, you can use multiple linear interpolations to get a Bezier curve.

Wymar

I cannot resist reading "FSM" as "Flying Spaghetti Monster."

Qft.

Take a gander.

Great. Now I'm sure that tomorrow... no wait, today one of my math teachers will scold me for doing parameters instead of calculus >_>

Anyway, how does a game handle several bullets at the same time? I did some messing around with danmakufu, so I know that in that particular program every type of bullet gets identified with an ID, but what about the individual bullets? It can't just be something like "accelerate the bullet with this ID towards there with this intensity etc.", because there are several bullets with that particular ID and despite having the same parameters they're not identical. And I doubt that the program labels every bullet individually considering how many there potentially are on the screen (or does it?).

TechSY730

@Wymar

I'm pretty sure that bullet type and individual bullets are separate "classes". When a bullet is created, it is assigned a certain bullet type which determines its behavior. This way bullets can be created and destroyed freely and can act independently, but can still reuse behavior.
And for tracking each bullet individually, yes that is completely practice. I've seen programs that handle thousands of objects, each with more complicated behavior than a bullet, each with real-time behavior, and still have little to no lag. Computers may be unbelievably stupid, but they are GREAT at keeping track of large numbers of objects.

TechSY730

Doh, typo. Where it goes "...individually, yes that is completely practice.", it should be "...individually, yes that is completely practical."

Dizzy H. Muffin

Yeah, the "different classes and different individual bullets" bit was how I'm going to do it, though I don't have the "different types of bullets" part coded yet. My bullet-tracker is based on the one shown in this thread, but I'm thinking of using a slightly different way of keeping track of them (since newly-created bullets inherit positions in the array from previously-deleted bullets): the bullet/object/whatever bullets will an object-ID struct containing the index in the array and the frame in which it was created, which the bullet will also store in itself. It is this object-ID which will be stored in "bullet variables." So if you want to do something with a bullet, it checks the bullet at the given array-index, and if the creation-frame is different from the one in the object-ID, it gives you "Nope, sorry, it ain't there!"

Also, the "delete bullet" function will set the bullet's creation-time in the array to -1, since you can't really "delete element in array" or anything like that; to do this, I'll have to change the type of the framecounter to a signed integer, which will make it so the maximum time a stage can go on is 1.14 years instead of 2.27 years, OH WELL.

TechSY730

@Muffin

What, no epic 2 year boss fight? Aww, guess we'll have to settle with the mediocre 1 year boss fight. :P

Anyways, are you just making an engine, or are you also making a demonstration game or even a full game to go with it?

Dizzy H. Muffin

Full game. Well ... full-ish, I guess.

TechSY730

In that case, I have an idea.

You know how Yukari in PCB had like a jillion spellcards you had to plow through to beat her? Well, even though she was a long fight, many of those patterns are considered tame compared to modern extra boss' spell cards. Maybe you can make a similar fight for a phantasm level, but with spellcards that are threatening by modern standards, and maybe have even more spellcards to fight through than in PCB. This would be a true showing of Yukari's power, and show why you should fear her! (Unlike some gamers, I think marathon-bosses are fun if variety and challenge can be maintained) If you don't feel skilled enough to pull that off yet, or lack the experience to make bullet patterns threatening to experts, maybe at some future time?

MWchase

I was under the impression that this would be its own story with original characters and such. Plus, no offense, this is just memory and conjecture, but I'm not sure if Muffin has experience with expert-level danmaku, since he plays on a different, less insane level. I mean, assuming I'm getting all the nuances behind those statements right, which I'm probably not.

If my attempt gets off the ground (current priority: see how many bajillion bullets I can put on screen before my laptop starts choking. My guess, based on my luck with efficient coding... five. Five bullets.) I've given myself license to make it as hard as the engine and my sanity allow, so if it gets to that point, I'll... probably post progress reports elsewhere, and also accept suggestions. But that's not something I should be thinking about now.

Dizzy H. Muffin

Yeah, it's going to be an entirely original thing.

Nor do I have that much experience with expert-stuff, indeed. I mean yeah, I'll do my best to make it hard for the people who like hard, but it's not going to be PCB Orchestra, and I'll also try to make it more accessible for people who don't like "hard" quite so much.

sRc

hahaha, MWchase now, eh? with everyones sudden interest in making danmaku games now, maybe I shouldn't start making one after I finish KM's new Twitter client... oh wait, the fact that I have a like 300ms reaction time and can barely play above Easy was already a good enough reason not to do that...

which reminds me, KM, how is that method from that thread working for you for bullet counts, anyway? you able to get results closer to their original counts than the 100 or so before slowdown that I was getting trying it out?

Dizzy H. Muffin

I dunno, I haven't really been doing Huge Amount Of Bullet tests, on either of my computers.

TechSY730

Sorry about that. I missed your goals for this project. Do you know of any fan game that has epicness of that scale?

sRc

@TechSY730

Concealed The Conclusion?

MWchase

My feeling is, danmaku is about bullets. Many bullets, each following their own trajectory. In terms of optimization, bullet-drawing is sure to be the inner loop, unless I've done (or will do) something very wrong. So, now that I've got some cubic spline code bashed together (it ought to run fast, now that I've realized that the six pows I was using could be replaced with three additions, which pretty much has to be a speedup), I want to get a feel for what I can do, so I know what to shoot for with stuff like circles. (I've actually already worked out some pretty good optimizations. Just need two trig calls to set up the path, and everything after is matrices. Should be slower than splines, but if it's fast enough, uniform curvature should be worth it. Plus, with some tinkering, I should get all manner of similar paths for free, if that works.)

Hey, look, I'm applying my college and/or high school education! :p Next, I should include Fluid Mechanics stuff, because according to my grade, I totally understand that boop. I do not understand that boop, at all. I think I'm weird because, when I whine about undeserved grades, it's typically because I think the professor was being lenient.

MWchase

Also, there is no danger of me rendering anyone's efforts redundant, because I seem to have a talent for coaxing inexplicable errors out of code. I think the best course of action is simply rewriting the offending class here, because holy shit, what is it doing? :/

Also, it is 4:30, local time. I hate myself right now.

MWchase

Lesson: sometimes, your tests don't work because the test code is buggy. AAAUURGH.

Also, for all that I was all "optimization! yay!" my code currently can't handle much more than 600 bullets at once. (Since there's no code taking advantage of garbage collection, the question of "onscreen" is currently irrelevant.) (Wait... I just realized there should be a quick-fix that'll boost that number by a factor of three. Let's see...)

MWchase

HAHA my intuition is a dirty liar.

That is all. Time for some nice, 4-in-the-afternoon breakfast.

Wyrm

@MWchase:
Profile your code. See where the processor/bus capacity is being sucked up. Otherwise, you'll be be spending your time optimizing code that never runs very much in comparison to the real cycle-hogs.

Also, even without garbage collection it may be offscreen objects that eat your speed if your OS's graphics code is stupid. Arrange for a shitton of bullets moving about offscreen while your actual visible field is empty and see how many you can place before it drags. If you're doing it right, you should be able to place many more bullets offscreen than on without lagging. (Make the motion part simple, maybe flopping between two positions, which should be cheap as hell, instead of a complex curve.)

MWchase

... Would you believe that I get visible speedup from not calculating the FPS? You're right, this needs a profiler something fierce, as soon as I finish this organizational rewrite for the bullets. ... Or maybe before, since I keep on changing my mind about how to do it.

("I'll use a tree structure! Except that that explodes for no good reason whatsoever.")

(Judging by what happens when I comment out one of the two relevant lines in the main loop, I can have 60fps, bullets that are visible, or bullets that move. Pick two.)

MWchase

Pre-pass-out update: I got the new organizational scheme for the bullets worked out. It's no more efficient (I estimate drawing and moving to average around 11 microseconds per bullet, under pretty much every working scheme I've used), but it'll be great for organization later (which may get me to do some random tweaks to it, but it doesn't need to be touched again until I implement collision detection). Next up is probably FSM hacking, or trying to work out how to either get out-of-the-box profilers to work with Löve, or break down and actually learn C so I can roll my own using the standard library.

On the other hand, maybe I should just accept (post-some-tweaking) 750-ish as a limit for how many things I can have zooming around the screen at once. I don't think I was actually planning to use that many bullets and enemies.

Page 1 2 / Previous / Next / All