Demo recording/playing
========================================================================
Just4Fun Productions presents:
Part 5 of: the Techtutor
(Topics never covered)
Demo recording/playing
========================================================================
Introduction
I finally got a nice new idea for a tech-topic, and while I was doing this I also released all tech-tutors to the net so that they can now be viewed offline...
This topic is Demos, and I don't mean playable-demos, I mean a demo that will be viewed when the player isn't touching the keyboard for a while (like in Doom, JazzJackrabbit, etc...) or like the demos showed when you start Quake.
----------------------------------------------------------------
For contacting Just4Fun or me (PeeBee) use the following ways:
Internet : http://people.zeelandnet.nl/rpb
Email : just4fun@zeelandnet.nl
SnailMail: P.Bestebroer
Anthony Edenlaan 28
4463 JC Goes (Zld.)
Holland
ICQ : 2309110 (probably fastest way to contact me)
----------------------------------------------------------------
Difficult AI?
Ok most people don't have a clou as to how to program a rolling-demo, some people might think that the programmer has a programmed a very difficult AI routine for the computer to play like a human...guess again, it much simpler then that, because you can just find some human beta-tester (or do it yourself) and let it play the computer while you "record"!
If you ever played with Doom, you might have created your own demo showing how cool you are, and how good you where...well we'll do it that way as well!
Is this camera on?
What we are going to do is record the actions of our beta tester... Now we can do this in a smart way, or a very stupid way..I'll choose the smart way for this tutor (it makes me look smart as well ;) To show the difference, here's how the stupid way would be:
Everytime the players keys are read for taking actions, we also write those values to the demo file...which means that every frame the keys are saved. Now most games have 4 directions keys, 2 or more fire keys, and since doom most games also have 0-9 for selecting weapons...
You want to save that all to a file? each frame? what if the player plays for 30minutes in one level? you better make sure your harddisk is large enough for those demo files!
I did it the smart way
First we will examine what went wrong in the "stupid" way.
The reading of the keys is ok. No problem there, but saving them at that point?
I don't think so, what if the player is not pressing any keys? then we are saving things that need no saving! On the other hand, what if the player is still pressing that one direction key he was pressing for the last 2 minutes? we would save a lot of extra values to the file which we don't need... What we do is the following: We keep a small counter (preferably a lonint) that is set to 0 at the beginning of the game/level/demo.
Now as long as the player doesn't release/presses a key, we simply increase this counter (per frame) once the player DOES make a change, we need to write those changes to the demo file!
So first we write the counter value, because this can be used when reading the demo, to specify the time of the previous keys pressed values and the amount of frames that they are being pressed/released...
After the counter we write the new values for all keys involved, and reset the counter to 0...so we can start again with counting the frames until somethings happens again.
Timeout, TIMEOUT!
One thing we really need to take care of is a small thing: the maximum value of a longint! Because if the player doesn't touch anything for to long, and the game is not paused, our counter is being increased, but once it reaches its maximum value (which is possible!) it will restart at 0, and mix up our timing of the complete demo...
So when increasing the counter, check on the maximum value, and if matched just treat it like a new key-change was reported and save the counter+key values...
Lights, camera, action
Now we want to play the demo!
Simple, just reset the demo (you might like to check if its a valid demo, in which case you would have to save and ID of your demo file at the beginning of the demo-recording procedure).
Reset the demo_frame-counter, and make a few changes to your player procedure. The few changes are simple, you just have to keep a boolean that specifies if there's a demo being played, and if so read the keys from the file instead of from the keyboard!
Now the procedure that reads the keys from the demo file must only read when the counter hits 0, because if counter>0 then dec(counter).
After the counter hits 0, you simply read in the new demo-key values (store them in a separate set of booleans then your normal key booleans because the viewer might press some keys, and screw up the demo-playing).
Then the player procedure simply uses the demo-key values and walk thru the normal player procedures.
Important things
Some important things are:
- Don't use random enemies!
- Make sure you initialise the player BEFORE playing the demo!
- You might like to save the map-file information into the beginning of the demo.
- Take a look at the example procedures supplied with this text
Final word
This tutor was a bit difficult to write, but I hope its less painfull to read. The best way to read this is to also look at the example procedures which show how you could do it...again the examples are taken from the SuperFX engine.
I don't yet know what my next topic will be, but all ideas are welcome!
PeeBee - September 10th '97
Tech05.pas
{
TechTutor5
Demo Recording/playing (ripped from SuperFX Engine)
Coding by P.Bestebroer
FreeWare
Source does NOT work on its own, it was merely used for showing
howto, instead of doing it foryou.
The code DOES work when put inside the SuperFX engine (wich is
allready done).
Contacting:
HTTP://people.zeelandnet.nl/rpb/
EMAIL:just4fun@zeelandnet.nl
}
PROGRAM Tech05;
USES CRT;
{-----------------------------------------------------------------------------}
{
First create a few variables
}
VAR DEMrecord : boolean; { recording demo? }
DEMplay : boolean; { playing a demo? }
DEMfile : file; { filename of the .DEM file }
CONST DEMID : string = 'SFX_DEMOv1'; { ID of the SuperFX Demo files }
VAR DEMleft : boolean; { left key is pressed? }
DEMright : boolean; { right key is pressed? }
DEMup : boolean; { up key is pressed? }
DEMdown : boolean; { down key is pressed? }
DEMfire : boolean; { fire key is pressed? }
DEMfire2 : boolean; { support for 4 joystick buttons }
DEMfire3 : boolean;
DEMfire4 : boolean;
DEMpress : longint; { time the keys are pressed }
{-----------------------------------------------------------------------------}
{
This procedure will create a new DEMO file
Expects: Filename of the Demo
Returns: Nothing
}
FUNCTION _NewDemo(filename:string):boolean;
BEGIN
Halt_Proc:='_NewDemo';
assign(DEMfile,filename);
{$I-}
rewrite(DEMfile,1);
{$I+}
if IoResult<>0 then begin
_NewDemo:=false;
halt_Error:='Unable to assign demoname ('+filename+')';
exit;
end;
blockwrite(DEMfile,DEMID[1],sizeof(DEMID)); { write DEMO ID }
blockwrite(DEMfile,MapFile[1],32); { write map-filename }
blockwrite(DEMfile,MapLib,1); { write map-using lib? }
DEMleft:=false; { not pressed,}
DEMright:=false; { not pressed neither, }
DEMup:=false; { definetly not pressed, }
DEMdown:=false; { maybe...NAH not pressed! }
DEMfire:=false; { ...don't shoot!, don't shoot! }
DEMfire2:=false; { support for 4 joystick buttons }
DEMFire3:=false;
DEMfire4:=false;
DEMpress:=0; { the pressing-time of the keys }
DEMrecord:=true; { silence please, we're recording! }
{$IFDEF PLUGIN_SHOWEVENTS}
_Ucase(Filename);
_AddEvent('RECORDING DEMO ('+filename+')');
{$ENDIF}
_NewDemo:=true;
END;
{-----------------------------------------------------------------------------}
{
This procedure will OPEN a DEMO for playing
Expects: Filename of the Demo
Returns: Nothing
}
FUNCTION _OpenDemo(filename:string):Boolean;
VAR testID : string[24];
tmpFile : string;
BEGIN
Halt_Proc:='_OpenDemo';
tmpFile:=filename;
assign(DEMfile,filename);
{$I-}
reset(DEMfile,1);
{$I+}
if IoResult<>0 then begin
_OpenDemo:=false;
Halt_error:='Couldn''t spawn demo ('+tmpFile+')';
exit;
end;
blockread(DEMfile,testID[1],sizeof(DEMID));
testID[0]:=DEMid[0];
if testID<>DEMid then begin
halt_Error:='Not a valid DEMO file ('+filename+')';
halt_proc:='_PlayDemo';
halt;
end;
blockread(DEMfile,MapFile[1],32); { read map-filename }
blockread(DEMfile,MapLib,1); { read map-using lib? }
LoadMap(mapLib,MapFile);
DEMpress:=0;
DEMplay:=true;
{$IFDEF PLUGIN_SHOWEVENTS}
_Ucase(tmpFile);
_AddEvent('PLAYING DEMO ('+tmpFile+')');
{$ENDIF}
_OpenDemo:=true;
END;
{-----------------------------------------------------------------------------}
{
This will END the DEMO file, and close it...
Expects: Nothing
Returns: Nothing
}
PROCEDURE _EndDemo;
BEGIN
{$I-}
Close(DEMfile);
{$I+}
END;
{-----------------------------------------------------------------------------}
{
This will handle the recording of key-presses, and save
it to the DEMO file.
Expects: all directions, and the fire presses
Returns: Nothing
}
PROCEDURE _RecordDemo;
PROCEDURE _ADDtoDEMObyte(code:byte);
BEGIN
blockwrite(DEMfile,code,1);
END;
PROCEDURE _ADDtoDEMO(code:longint);
BEGIN
blockwrite(DEMfile,code,4);
END;
BEGIN
if (joystickleft<>demleft) or (joystickright<>demright) or
(joystickup<>demup) or (joystickdown<>demdown) or (joystickA<>demfire) or
(joystickB<>demfire2) or (joystickC<>demfire3) or (joystickD<>demfire4) then begin
if demleft then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demright then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demup then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demdown then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demfire then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demfire2 then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demfire3 then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
if demfire4 then _ADDtoDEMObyte(1) else _ADDtoDEMObyte(0);
_ADDtoDEMO(dempress);
DEMleft:=joystickleft;
DEMright:=joystickright;
DEMup:=joystickup;
DEMdown:=joystickdown;
DEMfire:=joystickA;
DEMfire2:=joystickB;
DEMfire3:=joystickC;
DEMfire4:=joystickD;
DEMpress:=0;
end else inc(DemPress);
END;
{-----------------------------------------------------------------------------}
{
This will handle the PLAYING the demo
Expects: all directions + fire keys (VARS to be returned)
Returns: the boolean filled with TRUE or FALSE
}
PROCEDURE _PlayDemo;
VAR i : byte;
PROCEDURE _GETfromDEMObyte(var code:byte);
BEGIN
blockread(DEMfile,code,1);
END;
PROCEDURE _GETfromDEMO(var code:longint);
BEGIN
blockread(DEMfile,code,4);
END;
BEGIN
Joystickleft:=DEMleft;
JoystickRight:=DEMright;
JoystickUp:=DEMup;
JoystickDown:=DEMdown;
JoystickA:=DEMfire;
JoystickB:=DEMfire2;
JoystickC:=DEMfire3;
JoystickD:=DEMfire4;
if DemPress>0 then dec(DemPress) else begin
if eof(DEMfile) then DEMplay:=false else begin
_GetFromDemoByte(i); if i=1 then DemLeft:=true else DemLeft:=false;
_GetFromDemoByte(i); if i=1 then DemRight:=true else DemRight:=false;
_GetFromDemoByte(i); if i=1 then DemUp:=true else DemUp:=false;
_GetFromDemoByte(i); if i=1 then DemDown:=true else DemDown:=false;
_GetFromDemoByte(i); if i=1 then DemFire:=true else DemFire:=false;
_GetFromDemoByte(i); if i=1 then DemFire2:=true else DemFire2:=false;
_GetFromDemoByte(i); if i=1 then DemFire3:=true else DemFire3:=false;
_GetFromDemoByte(i); if i=1 then DemFire4:=true else DemFire4:=false;
_GetFromDemo(DEMpress);
end;
end;
END;
{$ENDIF}
{-----------------------------------------------------------------------------}
BEGIN
while port[$60]<>156 do ;
textcolor(7); textbackground(0); clrscr;
writeln('TechTutor #5');
writeln('written by P.Bestebroer, Just4Fun Productions');
writeln('');
writeln('A great source code showing how you COULD implement demo-playing');
writeln('and demo recording. The source does not work stand-alone, but it');
writeln('can be used when some tweaking is done, and you have a game to put');
writeln('it in.');
writeln('The source code was taken from the SuperFX engine.');
writeln;
writeln('Watch out for the other techtutors...');
writeln;
writeln('Press a key');
writeln;
writeln;
writeln;
writeln('----------------------------------');
writeln('Contacting: just4fun@zeelandnet.nl');
writeln('http://people.zeelandnet.nl/rpb ');
writeln('----------------------------------');
repeat until port[$60]<>156;
END.