When Chronotron came out, the idea of playing with an early 'you' was not new, but was technically complex for most of programmers to 'copy' over. I must say when I first saw it, I thought about a million ways to achieve the effect, but couldn't think on any GOOD way to do it.
I had a simple concept to keep in mind: There would be a Recorder. This recorder would save the state of whatever I wanted to replay into a 'Replay' array, and then there would be a Runner. The runner would interprete the Recorder saved states and would playback it, much like a tape recorder. But I was yet to find a good implementation of the 'recorder' and 'runner'. That sent me in my journey of tries for a perfect Chronotron-like mechanics.
My first try was with a simple script (less than 20 lines, not counting the event handlers) that would store the player's position and animations at every frame. Any programmer with some experience in game coding (and general programs, whatnot) will figure the memory and performance issue we would run into after running that thing for a while. The idea of an array filled with hundred of indices scares me! That said, code scratched out and back to zero.
Second try, this time, I'd only store the player position when it changed. That is, in a rest state, no data would be saved, as it'd be silly to have an array that gone like ...[200, 150], [200, 150], [200, 150]... repetedly. But still, I had the issue of not knowing when it should change state, more precisely, when the 'runner' would skip to the next Replay indice. Oh, headaches, and back to complete nothing.
Third try, this time, I was convinced to complete my quest sucefully! And what a surprise when I tought of a good, simple and elegant way of doing it during a brainstorm session with my MP3 player running Metallica out loud! This time, the idea would be more complex than simply storing the player replay data as a set of position, or states. The recorder would store the Keyboard input into the Replay array, and then, the Runner would feed from it and spit the same movements the player did! That gave me this:
So, let's cut the crap and see how I did it:
The Recorder:
It must listen and process all of the keyboard input. It chews raw input data in the format of KeyboardKey:Array, where each indice is actually the number of frames the key was kept held down (being 0 = released, 1 = key was kept 1 frame down, 2 = key was kept 2 frames down, etc)
It then listens for when the key is released, and stores the key inside the Replay array 'KeyPress', in this order:
[key:uint, startInterval:uint, duration:uint];
This data is ordered using the startInterval value (for the sake of the Runner), being keys with lower startIntervals stored first. This sorting is done as the keyboard key is released, in a loop that runs backwardly, like this:
// If not, run a backward loop, assuming the nearest position is at the end
// of the array:
for(var i:int = Run.length - 1; i >= 0; i--){
// Checks to see the event order:
if(i == 0 || (Run[i][1] < start)){
// If this event happened before the event on [i], insert this event
// after it:
Run.splice(i+1, 0, [key, start, interval]);
break; // Break as soon as we hit the desired insertion point
}
}
Got it? Next, to the Runner!:
The Runner:
The Runner hides inside the Mc_NPC (the past You instance) code. It simply takes this Replay data and interpretes it by creating a virtual keyboard that is used by the Mc_NPC clip. Since the Mc_NPC and Mc_Player (the actual you) uses functions of same name to check whether a key is being held down (keyDown function), you can simply paste the code inside the main loop of the player character directly into the NPC past you, and as long as they share the same methods, it'll work like a charm!
But back to the runner, shall we? The Runner keeps track of time, just like the Recorder. It does so to better run the Replay with no issues. It does so in Frames (not millisecs! That'd bring the whole thing to chaos if it lags for even a fraction of a second). Since our Recorder sorts the Replay array in order of events (being the older event at the start of the array), we can simply keep track of the current event using a posNow value. This value tells us where we are in the Replay array. Then, we iterate through the Replay array catching any event that overlaps the posNow value. Let's see how it does so?:
// Iterate at Run[posNow(n)] until there are no events left:
while(Run[posNow] != null && (Run[posNow].length == 3 && Run[posNow][1] <= Runtime)){ // Register key:
// Store the key to the KeyHeld object:
KeyHeld["k" + Run[posNow][0]] = Runtime + Run[posNow][2];
KeyEmu["k" + Run[posNow][0]] = true;
posNow ++;
}
And that does it. In the same method, just above this part, there's another small loop that checks if a key expired (that is, it was released):
// Check for keys that already 'expired':
for(var s:String in KeyHeld){
if(KeyHeld[s] <= Runtime){ KeyHeld[s] = false;
KeyEmu[s] = false;
delete KeyHeld[s];
delete KeyEmu[s];
}
}
The KeyHeld and KeyEmu are two objects used to make up the simulated keyboard. KeyHeld states when the key was pressed (in ints) and KeyEmu stores boolean values (whether or not the key is being held down). The 's' variable used to iterate is equals to:
s = "k" + Run[posNow][0]; // This is the keyCode in int
I know, it's not ideal, but it was made so we could use delete on the no longer needed keys. I think it was a stupidity of mine back at the time I wrote the code.
I know there may be many more suitable implementations to this method, but I found a keyboard emulator would be more flexible than storing positions. There is no Mouse support, also, but I figured since you'd have to store the mouse positions on every frame, it'd defeat the purpose of being a clean and low-memory consuming implementation. It could be done very easily, tough!
And of course, here's the source code:
<<< (Flash CS3) >>
(sorry about the code ident, but BlogSpot broke everything!)
So, this is about everything I have to say about my experience. I hope you find some non-apocalyptic practical use for the code!
See you in a later,
-Luiz
11/24/2009
Subscribe to:
Post Comments (Atom)
ViciousPud.WF.jpg)
1 comment:
thanks for the post, Luiz!
Post a Comment