Input Replays in Game Maker

A place for people with an interest in developing new shmups.
Post Reply
User avatar
BPzeBanshee
Posts: 4859
Joined: Sun Feb 08, 2009 3:59 am

Input Replays in Game Maker

Post by BPzeBanshee »

As folks might've seen previously in the GMOSSE thread, and again now in the Trigonometry Wars thread, we've been having a bit of trouble getting input replays to work in GM.

I said my system worked for all cases except Swordfish; I tried to record a replay with Xonochrome tonight and it was horribly broken. Many headaches ensued.

Continuing on from the discussion though, I've discovered a few things about Game Maker's order of events that the official documentation doesn't include. A little bit of research found me this: http://www.gmlscripts.com/forums/viewtopic.php?id=138

Now in theory this means that any kind of logging of keyboard events to file should happen after the keyboards have been polled, ie. Step, or End Step.

In practice I managed to come up with this handy example project here: http://www.mediafire.com/?cc5d5naef6heuia

The INI recording is like GMOSSE: 0 does nothing, 1 records inputs and saves to file upon exit, and 2 opens up that file and plays again. It also displays a bunch of variables and *takes a screenshot of each frame for comparison purposes*. There's two rooms, one running at 60 FPS and one at 1 FPS, you'll want to use GM Pro to open up and change the room order otherwise you may be swamped with screenshots. :P

Sounds great doesn't it? Well, not exactly. I still couldn't get it to work properly in GMOSSE for some reason although in the testing that I did do the behaviour was better than before. Certainly in the 1FPS room the recordings screenshots matched the playback screenshots exactly...once I added an offset in the lines.

I did however learn two new things: GM appears to have two frames of lag for inputs, and the order of each specific key is actually the order they appear in the IDE when you use the UI's keyboard events.

Hopefully a bit of collaboration in this thread and some thorough testing from someone other than just me may result in us getting a definitive system worked out.
User avatar
Keade
Posts: 385
Joined: Mon Jul 16, 2007 8:44 pm

Re: Input Replays in Game Maker

Post by Keade »

I can't really help you with Game maker specifically.
However (depending on why you need this replay capability) I think you might be interested in knowing that if the "simulation" part of your game is using floating point calculations in some places, there are high chances that replaying the same source inputs will not be consistent from one machine to another : same input may produce slightly different result - it's what I have learned from a few papers (see this article).
This should probably not be a problem when replaying on the same machine though.
Meseki
Posts: 129
Joined: Fri Mar 30, 2012 11:36 pm

Re: Input Replays in Game Maker

Post by Meseki »

According to the link you posted, most of the (relevant) events are done in Object ID order, not Instance ID. If that's correct, fixing the issue could be a serious pain, as objects that were designed first would have their events first, regardless of the order they were created in the room.
That would be a major issue if you created the ship object before you created the object that records/plays back input, as that could possibly make everything a frame late. If you were to record and playback input through the ship object, that could possibly fix an issue like that.
User avatar
Giest118
Posts: 1042
Joined: Wed May 02, 2012 1:50 am

Re: Input Replays in Game Maker

Post by Giest118 »

Meseki wrote:According to the link you posted, most of the (relevant) events are done in Object ID order, not Instance ID. If that's correct, fixing the issue could be a serious pain, as objects that were designed first would have their events first, regardless of the order they were created in the room.
Sweet jesus, I never even considered that. That would probably explain what was going wrong.
User avatar
BPzeBanshee
Posts: 4859
Joined: Sun Feb 08, 2009 3:59 am

Re: Input Replays in Game Maker

Post by BPzeBanshee »

Giest118 wrote:
Meseki wrote:According to the link you posted, most of the (relevant) events are done in Object ID order, not Instance ID. If that's correct, fixing the issue could be a serious pain, as objects that were designed first would have their events first, regardless of the order they were created in the room.
Sweet jesus, I never even considered that. That would probably explain what was going wrong.
This also explains why GMOSSE wasn't working and this example did: the same object recording is replaying it back, the one thing I didn't replicate.

And with this considered I just redid the player system so that the player object itself does the actual recording (obj_replaycontrol is still there but only for saving/loading replays/interface etc), doesn't actually get destroyed on impact, and I accounted for the Ship Select menu by having the obj_input object do the recording first and then when the player spawned it took over.

I got replays to behave better as a result but I decreased overall filesize by a short amount as well (it doesn't record inputs at the stage clear event because the player gets destroyed there, with no negative result on the replay). I've done some runs with Xonochrome with success, and even one run with Swordfish that behaved perfectly.

I did another with Swordfish though and on one section of Stage 2 where it should've avoided a popcorn ship and broken its chain it actually hit the bastard and kept the chain going, but yet it died and Game Over'd at the exact same place, so I'm kinda back at Square 1 again. At least the rest of it seems to play okay now though compared to how broken it was behaving after my first pass of post-MK-VI tinkering.
User avatar
eebrozgi
Posts: 178
Joined: Fri Nov 27, 2009 11:17 pm
Location: Finland
Contact:

Re: Input Replays in Game Maker

Post by eebrozgi »

BPzeBanshee wrote:Continuing on from the discussion though, I've discovered a few things about Game Maker's order of events that the official documentation doesn't include. A little bit of research found me this: http://www.gmlscripts.com/forums/viewtopic.php?id=138
I don't have anything helpful to say on the topic, but I have to thank you for this golden bit of information.
If watching the trailer of the game
makes you feel a certain way
I would be very happy if
you would give the game a try

~Daisuke Amaya, 2015

ZeroRanger - RELEASED!
User avatar
Giest118
Posts: 1042
Joined: Wed May 02, 2012 1:50 am

Re: Input Replays in Game Maker

Post by Giest118 »

I'm gonna look into this shit again because I intend to try Input Replays again with my next game. I intend to log my efforts here for others to potentially learn from.

I'm basically just in the pre-planning phase at the moment, and one thing I want to take care of before anything else is making sure that replay files are as concise as possible. When I attempted them in TW3R, they were rather large. The format I came up with for them was basically this line for every frame of recorded gameplay:

Code: Select all

MoveUp MoveRight MoveDown MoveLeft Firing Lasering Bombing
Where each thing above was represented by a 1 or a 0, indicating whether that was one of the buttons the player was pressing at that frame. This was a very inefficient way of going about it... to say the least.

Of course, I did that before I knew about a wonderful little operation called the "bitwise AND".

One of the things I intend to do is define a series of global variables specifically useful for input replays. These variables will be non-zero integer powers of two, and they will look something like this:

Code: Select all

global.inp_up = 1;
global.inp_right = 2;
global.inp_down = 4;
global.inp_left = 8;
global.inp_fire = 16;
global.inp_focus = 32;
global.inp_bomb = 64;
Here's what these will be useful for. There will be some global input variable, call it global.input. Every frame, this variable will go through the following operations. This is pseudo-code, I haven't actually done this yet.

Code: Select all

Set global.input to 0
If player is pressing Up, increment global.input by global.inp_up
If player is pressing Right, increment global.input by global.inp_right
...
If player is pressing Bomb, increment global.input by global.inp_bomb
Write the value of global.input to the replay file
Repeat these operations next frame
This is enough to record a basic input replay file. Every frame is expressed with a single, max-of-three-digits number, as opposed to the space-delimited 7 digits I had up above, so this approach saves about 75% filesize.

So how will the game interpret the replay file once it's made? To understand that, you need to understand what a bitwise AND does. If you were thinking about the above process, you would know that if, say, the player was moving Right, was Focused, and was Bombing, the result of the above process is that the number 98 would get added to the input file. The binary representation of this number is 1100010. Note that there are 3 1's there, and a bunch of 0's. The 0's indicate an action the player is not taking, and the 1's represent the actions a player is taking. Now, global.inp_bomb is equal to 64. The binary representation of this is 1000000. A bit-wise AND basically compares two numbers (two sets of 1's and 0's) and sees where there are 1's in both numbers. For example, if you perform a bit-wise AND on 1100010 and 1000000, the result is 1000000.

If you perform a bitwise AND on 98 and global.inp_bomb, the result is 64. So, while reading the replay file, you can find out if the player was bombing in a given frame by doing something like this:

Code: Select all

if((global.replay_inp & global.inp_bomb) == (global.inp_bomb))
{
//Put player bomb code here
}
I haven't tested this yet (still just planning here) but I'm pretty sure this will work as far as replicating player movement.

Next item on the agenda: Dealing with randomness. I have a plan already, but if anyone else would like to share their ideas, I would enjoy hearing them.
Last edited by Giest118 on Wed Apr 10, 2013 9:01 pm, edited 1 time in total.
User avatar
trap15
Posts: 7835
Joined: Mon Aug 31, 2009 4:13 am
Location: 東京都杉並区
Contact:

Re: Input Replays in Game Maker

Post by trap15 »

For randomness, your random number generator should be deterministic. Then all you have to do is either: 1) make the replay contain the seed, or 2) set the same seed every time the game starts.
@trap0xf | daifukkat.su/blog | scores | FIRE LANCER
<S.Yagawa> I like the challenge of "doing the impossible" with older hardware, and pushing it as far as it can go.
User avatar
nasty_wolverine
Posts: 1371
Joined: Sun Oct 09, 2011 11:44 pm

Re: Input Replays in Game Maker

Post by nasty_wolverine »

this is just my thought, i dont how this can be implemented in GM, but i can pretty much do it in c++

the game cycle follows as such:

Code: Select all

while(game not quit)
{
update input //this updates a struct, contains key state on/off, frames key held on/off
update world
render
}

input struct:

struct key
{
symbol //can be a list of symbols for multimap
state //bool for on/off
framesheld //how long has it been held
}
input key[8];
now to record input, you capture the state of the struct per frame and save it to a file... (or only changes to the struct to be economic)
also can save random number seeds in the file, like trap15 said
so to play back, all you have to do is update the struct from the file instead of the real keyboard.
if your update loop is not fixed step, its harder to implement replay, because its harder to keep everything synced, because its not necessary for updates to be the same length of time in different sessions (like OS might throw a fit)

hope this helps...
Elysian Door - Naraka (my WIP PC STG) in development hell for the moment
User avatar
BPzeBanshee
Posts: 4859
Joined: Sun Feb 08, 2009 3:59 am

Re: Input Replays in Game Maker

Post by BPzeBanshee »

Defining seeds in GM shouldn't be a problem. I'm not 100% confident that it'll make all random functions afterwards completely consistent, especially if multiple random-related functions are used in a frame, but the theory and method of defining seeds is sound. IMO it's more safer to just not have randomness in anything important to gameplay, or better yet get the existing system working first in some kind of no-random-variables-used environment, and then figure it out afterwards providing nothing significant needs a rewrite.

Regarding filesize: even in my not-really-working method (inefficient as it is as Giest has pointed out) the files were easily compressed by standard utilities to make something otherwise several tens of MB in size down to about 3kb. The general load/write times were pretty much nonexistent too. Considering what Giest has come up with I think the original example I have in the first post can be modified to use bitwise ANDs instead though, and the compressionability of the files should be more or less the same.
User avatar
Giest118
Posts: 1042
Joined: Wed May 02, 2012 1:50 am

Re: Input Replays in Game Maker

Post by Giest118 »

In my testing I've found that if you do define the same seed twice, the RNG does produce the same set of random numbers. My testing has just been by generating arrays though, so I'm not sure if it will hold up in a 25-minute game across several rooms. I don't know enough about how Game Maker seeds its randomness. It might re-seed it with every room transition for all I know.

But I do know you can seed the RNG pretty easily, so that's a place to start.
User avatar
trap15
Posts: 7835
Joined: Mon Aug 31, 2009 4:13 am
Location: 東京都杉並区
Contact:

Re: Input Replays in Game Maker

Post by trap15 »

BPzeBanshee wrote:not have randomness in anything important to gameplay
Don't do this :mrgreen: Some of the best STG are quite RNG-heavy.
@trap0xf | daifukkat.su/blog | scores | FIRE LANCER
<S.Yagawa> I like the challenge of "doing the impossible" with older hardware, and pushing it as far as it can go.
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: Input Replays in Game Maker

Post by mystran »

nasty_wolverine wrote: if your update loop is not fixed step, its harder to implement replay, because its harder to keep everything synced, because its not necessary for updates to be the same length of time in different sessions (like OS might throw a fit)
Trying to playback a replay with different timesteps will certainly lead to random (not even pseudo-random!) results, since any differences in rounding will accumulate over time: you need the exact same logic on playback so your results are (observably) identical all the way to the least significant bits.

You can still support variable time steps though, by storing the "input" per-tick with explicit step-lengths (instead of the constant "1 time unit" that is implicit in a fixed-step); if you have a tick(input, steptime) function and wrap the replay logic around that, then you can provide it with the same (input,steptime) pairs during playback. Essentially this simply moves the timing to be part of the (recorded) input. The main problem then is getting smooth playback: you can accumulate time, and process those (variable) ticks that are short enough to fit into the "not yet processed" region, but if you fall behind (playback takes longer somewhere) you need some "catch up" or slow-down strategy, ideally without too many obvious hick-ups.

The main downside that I can see is that this takes more space to store and makes compressing the replay a lot harder: for constant step replay, if you have maximum of 8 recorded buttons, you can store a byte per frame. In fact, you can even fit run-length encoding in while still keeping the 8 bits/frame worst-case, for example by encoding up/down (or left/right, or any other "either-or" pair) into the first two bits and observing that 11 (up + down) is the same as 00 (neither), so one can normalize 11 to 00, and use 11 to instead mean "repeat the previous input" where the repeat count is stored in the other 6 bits (giving maximum 64 repeats, so for unchanging input you need 1 byte/second worst-case just by chaining such repeats). Such a compression is trivial to encode on the fly as well, while still pretty effective.. where as with varying timesteps you need more data (the step length) and this data is likely to vary enough that simplistic RLE is pretty much useless (so you either need larger replays, or more sophisticated compressor).

Anyway, one gotcha for replays: be careful with "implicit" randomness [edit: sorry, noticed this was already discussed above.. well.. I'll leave this here anyway]. For example, if you have a collection of "things" and then iterate over that collection, and then find out that a player hit a given bullet.. this is predictable if (1) you always collect the results first, then apply them or (2) you make sure the iteration is "in order" for some predictable order (say spawn order). But if you decided to jump out of such a loop on "first collision" and the entities were in some "pool order" and the pool (maybe it's the system heap?) wasn't reset at the beginning of replay.. you could get REALLY subtle differences.. which depending on the exact game logic details could later accumulate into score differences or even total "out of sync." Things like "did my bullet hit the enemy first and cancel all bullets, or did I hit the bullet first and die?" which could happen if everything is allocated from a single, unpredictable pool... or something.

There's probably other hazards.. but those are the ones that I can think of immediately.
Last edited by mystran on Thu Apr 11, 2013 7:11 am, edited 1 time in total.
mystran
Posts: 77
Joined: Tue Mar 12, 2013 11:59 pm
Location: Helsinki, FI

Re: Input Replays in Game Maker

Post by mystran »

trap15 wrote:
BPzeBanshee wrote:not have randomness in anything important to gameplay
Don't do this :mrgreen: Some of the best STG are quite RNG-heavy.
Well, you can do pretty "unpredictable" gameplay even without a traditional PRNG, if you let slight variations in player actions have chaotic long term effects. For example, if you have a scoring system that makes it "almost impossible" to get the exact same score (down to the last digit) every time, you could then use the last digit of the score at a particular point to control some variable (which is then likely to lead into further score variation, and so on). This is "deterministic" in the strict sense (and doesn't require a seed value) but might still be "as good as random" in practice. You could use any number of such "unpredictable" variables like the (exact) player position as a seed for a "chaotic" function like giest suggested in the boss-patterns thread.
User avatar
Giest118
Posts: 1042
Joined: Wed May 02, 2012 1:50 am

Re: Input Replays in Game Maker

Post by Giest118 »

I came up with a system to get around the "unknowns" of randomness in Game Maker. This system might be totally unnecessary, but damn it, I know it works.

I knew from my initial test that, given a random seed, you will generate the same random array of numbers every time you set that seed immediately beforehand. So, in lieu of actually using Game Maker's random() function during gameplay, I wrote a few helper functions. One of these functions was InitRandom, which should be used at the start of gameplay:

Code: Select all

randomize();
global.randseed=random_get_seed();
i=0;
while(i < 5000)
{
    global.randarray[i]=random(1000);
    i+=1;
}
global.randplace=0;
This function calls randomize, which chooses a random seed completely at random. It then records what the random seed is that was chosen for the purpose of recording in the replay file. The loop generates a large array of random numbers based on the seed. global.randplace will be explained shortly.

Whenever a random number needs to be used in gameplay, this function, GetRandom, can be called:

Code: Select all

Max=argument0;
Result=global.randarray[global.randplace]*Max/1000;

global.randplace+=1;
if(global.randplace >=5000)
{
    global.randplace=0;
}

return Result;
Basically, GetRandom pulls the next number out of the random array, and converts it to the scale given by the argument. If the end of the random array is reached, return to the beginning of it. Players won't notice or give a shit that this is happening.

When loading up a replay file, read in the random number seed from it and then call this function, SetRandom:

Code: Select all

random_set_seed(argument0);

i=0;
while(i < 5000)
{
    global.randarray[i]=random(1000);
    i+=1;
}
global.randplace=0;
Which basically is the same as InitRandom, but it sets the seed instead of retrieving it.

I have tested this system in an actual input replay, in conjunction with the bitwise AND stuff I went over before, and I seem to have hammered together a system that works. It remains to be seen whether this system will crash and die horribly when given a 25-minute game to run in, but I'll deal with that when it comes.

My test was on a series of three rooms that contained only objects that spew a random series of bullets. The recording/playback survived the room transitions, and all of my close scrapes with bullets were intact without actually resulting in collisions. The rooms each lasted for 30 seconds before transitioning to the next, or returning to the title screen.

Regarding filesize: That 90 seconds of gameplay resulted in a 26kb replay file. By my calculations, a full 25-minute game would yield roughly a 440kb replay file. This comes in under the likes of Blue Wish Resurrection and Eden's Aegis (600kb apiece), so I call that a win.
User avatar
BPzeBanshee
Posts: 4859
Joined: Sun Feb 08, 2009 3:59 am

Re: Input Replays in Game Maker

Post by BPzeBanshee »

Nicely done! A pseudo-random generator, and it works between room transitions? Could you upload whatever working test build you got?
User avatar
Temia Eszteri
Posts: 43
Joined: Mon Dec 24, 2012 10:45 pm
Location: Rainy Seattle
Contact:

Re: Input Replays in Game Maker

Post by Temia Eszteri »

Giest118 wrote:Regarding filesize: That 90 seconds of gameplay resulted in a 26kb replay file. By my calculations, a full 25-minute game would yield roughly a 440kb replay file. This comes in under the likes of Blue Wish Resurrection and Eden's Aegis (600kb apiece), so I call that a win.
Have you looked into gzip or zlib compression on the complete replay data post-completion? It should get you some pretty good compression, especially if you can pipe read/write operations through a gzipped file wrapper in realtime.

Then again, I'm thinking in the scope of general programming - I have no idea if Game Maker's capable of that in the least.
User avatar
BPzeBanshee
Posts: 4859
Joined: Sun Feb 08, 2009 3:59 am

Re: Input Replays in Game Maker

Post by BPzeBanshee »

I don't think GM by default has compression handling routines, but is expandable (to paraphrase iPhones, "there's a DLL for that"), or there's 7-Zip's standalone archiver to pass arguments through.
Post Reply