Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / Time Machine any ideas

Author
Message
Neotron
16
Years of Service
User Offline
Joined: 21st Jul 2010
Location: I am in an underworld , making a E.Army
Posted: 24th Jul 2010 14:45
Hi guys.

OK i have played prince of persia two thrones and was impressed how did it rewind time . Getting inspired with this effect i googled it but found nothing ,any ideas how to do it.

I think to slow it i can set the sync rate to 10 or less.

But i have no idea how to do rewind stuff.

Thanks.

ALLAH IS THE GREATEST
May he forgive me
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 24th Jul 2010 15:26
You would have to record the position, angle, and animation frame, plus any other factors that you might need. This has to be done for everything in your game, so it's probably best to use the same array for any object in your game, except static objects like buildings.

Then for everything, you 'stamp' their state into an array each loop, then if you need to rewind, you step back through each object, setting their state to the stored state. Other games might use the hard disk, but in DBPro, you might have to investigate a plugin, perhaps IanM's matrix utilities would have something to read and write to a file on the HD quickly enough. I would try and do it in physical memory first. Each loop you would increment an integer to store the game loop 'time', this would have to be written to the array. It's probably not a good idea to use a dynamic array, you have to work out the frame rate of your game, then use that to gauge the size that your array needs to be, including every object that moves in the game.

I'm intruiged though, I've only done this once and it was a pretty straightforward memory footprint, really just 1 character in a snowboarding game, so easy to track and replay that. It's something that is rarely seen in indi/hobbyist games. I might see if I can make a little demo, maybe just a mad avoid the falling cubes game, but with a rewind. Will see what I can do, I need a break from my main project anyway.


Health, Ammo, and bacon and eggs!
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 24th Jul 2010 16:46
Here you go, give this a try.

Just try and avoid the cubes, if you get hit, press the mouse button to rewind!




Health, Ammo, and bacon and eggs!
Neotron
16
Years of Service
User Offline
Joined: 21st Jul 2010
Location: I am in an underworld , making a E.Army
Posted: 24th Jul 2010 17:19
Hi van b

Thank you so much for this help.

What is its max rewind time.

Could you make a step by step tutorial for this again thanks.

ALLAH IS THE GREATEST
May he forgive me
IanM
Retired Moderator
23
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 24th Jul 2010 20:58
Quote: "What is its max rewind time"

From what I read in the code, time isn't the issue - timelines=100000, so that means that 100,000 separate values can be snapshotted, which if you have 1,000 objects at 60 fps, you'll have 1.66 seconds approx.

If you have less objects, you'll have more rewind time. If you have more objects, you'll have less.

Of course, you can always make the array larger, and you don't have to snapshot objects that aren't moving either. Or you could snapshot different things instead - rather than positions, and motion, you could avoid snapshotting positions and work back from the current positions from the recorded motions.

Lots of possibilities...

Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 24th Jul 2010 23:30
Yeah, it's probably about 16/17 seconds - it depends on how many moving objects are in your game. If you had a racing game, then 100,000 samples would give you about 3.5 minutes with 8 cars.


Health, Ammo, and bacon and eggs!
Math89
22
Years of Service
User Offline
Joined: 23rd Jan 2004
Location: UK
Posted: 25th Jul 2010 15:53
I don't think it's the correct way of doing it. You should not record every state for every frame, but only the important ones: player jumps, player attacks, enemy dies, etc. And then, you need to interpolate the current state of the game by using these keyframes.
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 25th Jul 2010 16:18
Yes, but that's why we never see it in complex games, at least this method would work for pretty much any type of game. Take physics for instance, there's no way to record physics objects accurately.


Health, Ammo, and bacon and eggs!
Neuro Fuzzy
19
Years of Service
User Offline
Joined: 11th Jun 2007
Location:
Posted: 26th Jul 2010 00:08
Quote: "Take physics for instance, there's no way to record physics objects accurately."

I don't quite get what you're saying... why not use the same method as above? I think keyframes and interpolation is the best way to go... though implementation would be pretty game specific.


Is't life, I ask, is't even prudence, to bore thyself and bore thy students?
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 26th Jul 2010 01:48
The example I came up with would provide a more acceptable result with physics than keyframes. Recording each objects state and forcing it when rewinding would at least keep the physics objects on a leash - with keyframe style recording that would be very difficult to guarantee. I mean, interpolating keyframes for different physics objects would change their outcome, but by recording the state of all objects at least the replayed outcome would be consistant.


Health, Ammo, and bacon and eggs!
Math89
22
Years of Service
User Offline
Joined: 23rd Jan 2004
Location: UK
Posted: 26th Jul 2010 16:45
Well, the whole thing is actually pretty similar to what should be used for a multiplayer game: instead of spamming the network with some useless data, only the relevant states of the entities can be sent. For some simple physics like jumping, there is absolutely no need to send all the positions: all you need is the position, velocity and acceleration of the player before he jumps.
If the physics is complex, then you still don't need to record every single frame. By recording and interpolating the positions, rotations, velocities and accelerations, you should be able to get a consistent behaviour at only 15 Hz.
Visigoth
21
Years of Service
User Offline
Joined: 8th Jan 2005
Location: Bakersfield, California
Posted: 27th Jul 2010 04:20 Edited at: 27th Jul 2010 04:20
I think you would need a DeLorean, a flux capacitor, and some plutonium.

just a joke.

Actually, isn't this better described as a VCR?
I'm watching this thread as I have an interest in this for something I've been working on.
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 27th Jul 2010 15:47 Edited at: 27th Jul 2010 16:02
Yeah, it does remind me a bit of a tape recorder, rewinding - it could be changed to pause, then allow rewind and forward - re-record etc.

I think it Would be useful for recording machinima, intro scenes etc. The problem with FRAPS is that it affects performance too much to record real results, at real speeds and graphics resolutions.

If you could record your gameplay at a constant 60fps, then it could be replayed as needed. For example one pass might record a wave file of the video while showing no video at all - then another pass could render at high res, AA, post process etc frame by frame. Then once that's done, combine the two in a video editor, and it would be better than what FRAPS can do, by far. There is a plugin that allows recording of AVI files, frame by frame, would be ideal I think.

Math89
Quote: "If the physics is complex, then you still don't need to record every single frame. By recording and interpolating the positions, rotations, velocities and accelerations, you should be able to get a consistent behaviour at only 15 Hz."

Do you really think that the physics wouldn't be affected by keyframing?. I think that the keyframing method would be so much more complex and thats without even considering physics. Feel free to write an example and proove me wrong!. By recording everything every frame, it's possible to recreate a scene as it's calculated - you might not get exactly the same result with physics, but it would be pretty close. For one thing, how would you even handle the timing! - no offense but I don't think you've thought this through at all, your just telling us how the pro's do it, not how you would actually do it.

Health, Ammo, and bacon and eggs!
Neotron
16
Years of Service
User Offline
Joined: 21st Jul 2010
Location: I am in an underworld , making a E.Army
Posted: 27th Jul 2010 18:24
guys no offence but i want a step by step tutorial .it would be very very nice
thanks

ALLAH IS THE GREATEST
May he forgive me
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 27th Jul 2010 18:51
I will see if I can pad out the comments etc, sorry, no time to write a full tutorial on it.

Health, Ammo, and bacon and eggs!
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 28th Jul 2010 17:06
Ok, will try and explain how this works... (check's coffee supply)

So, what this example does, is keeps track of several objects and records their state for each cycle of the main loop. The system relies on an integer 'game time' variable to hold the actual time index, and this is used to rewind and record.

Ok, so lets step through the code...

sync on
sync rate 60

type entitytype
x as float y as float z as float
xs as float ys as float zs as float
xa as float ya as float za as float
xas as float yas as float zas as float
mode as integer
stamp as integer
id as integer
endtype


So, that creates a type called entitytype, which is your standard entity properties, position, velocity, angle, angular velocity, mode - the only thing that these entities do right now is move in a set direction until they hit the wall, then they bounce back. There's no gravity or anything, but adding gravity would be easy, just decrement the YS variable, that's the vertical speed. This type is used to record the data, and control it, so it is used in the array Entity() to store those cubes, and also Timeline() which records it. The Stamp variable holds the integer game time, which simply increments each loop while recording, or decrements if rewinding.

In this example, 100,000 indices have been set for recording, at 60fps with 100 objects, that gives about 100000/(60*100) seconds, or 167 seconds, with 1000 objects you get 16.7 seconds. However, you only have to record active objects, any object that is not static should be recorded, so you might even just be recording the player position each loop, in which case it'll record for almost half an hour.

Anyhoo, timelines global holds the number of entries, and timelinecur holds the current timeline number. So when recording timelinecur will increment as entities are recorded, if it hits 100,000 then it has to reset to 0.

global timelines=100000
global timelinecur=0


So then we dimension those arrays, both of them as entitytype, for consistancy more than anything - for example, you might have an established entity movement system, you might be using a physics plugin - in which case you might not need entity().

dim entity(256) as entitytype
dim timeline(timelines) as entitytype


Now this just adds 100 objects that are updated and tracked in the array entity() - so this little bit will create those objects and spawn them.

`Make some spheres to avoid
for e=1 to 100

entity(e).x=rnd(20)-10
entity(e).y=rnd(20)-10
entity(e).z=rnd(20)-10
entity(e).xa=rnd(360)
entity(e).ya=rnd(360)
entity(e).za=rnd(360)
entity(e).xs=sin(rnd(360))/10.0
entity(e).ys=sin(rnd(360))/10.0
entity(e).zs=sin(rnd(360))/10.0
entity(e).xas=sin(rnd(360))
entity(e).yas=sin(rnd(360))
entity(e).zas=sin(rnd(360))
entity(e).mode=1
entity(e).stamp=0

if e=1
`Player object
entity(e).x=-95
entity(e).y=-95
entity(e).z=-95
make object sphere e,0.3
color object e,rgb(255,0,0)
ghost object on e
set object collision to spheres e
else
make object cube e,15+rnd(30)
color object e,rgb(55,0,45)
set object collision on e
endif

next e


Notice that entity 1 is flagged and creates a sphere instead of a cube, as that's the player object - the entity is basically an object, and there are no stuck rules for what the entity can be. I mean, the entity array is just an array, the relationship with actual object is tenuous to say the least - in fact it assumes that entity 1 is object 1, and that's a pretty neat way to work with this technique. But, as I was typing, the entity could be a 2D sprite, it could be a 3D sound - for more complex tracking, allowing tracking of sounds and stuff you would need to add to the type, maybe a 'typeof' field to track what the entity actually is.

This just makes a border, so we know where the barriers are - the example is locked into a 200x200x200 area. Also some visual stuff goes on, like adding fog.

`Make outside border
make object cube 500,200
scale object 500,-100,-100,-100

color object 500,rgb(100,50,0)

fog on
fog color 0
fog distance 30.0,250.0


Now actually recording the data needs a couple more variables to keep track, Record is the state of the recorder, 0 is paused, 1 is recording, and 2 is rewinding. As a default the record will be active. Gametime will store the frame number of the main loop, this is rewound downto a minimum of 0. Recordtime is a vital variable, without it things would loose track really quickly. Every time the loop is recorded, this is incremented upto the maximum number of recordings (100,000, stored in timelines), so if it exceeds 100,000 it stays at 100,000 - because that's how many recordings you have and the maximum is 100,000. When rewinding, the recordtime is decremented downto 0, or a safe value (might not be a good idea to go all the way back to 0). If it hits 0, then either the recorder is right at the start, or it's looping on itself - and going beyond these limits would give weird results.

global record=1
global gametime=0
global recordtime=0


Now the main loop, pretty straightforward as the complex stuff is done in the function.

do

if mouseclick()=1 then record=2 else record=1

if record=1 and gametime>100
if object collision(1,0)>0 then record=0 : center text screen width()/2,300,"HIT - Click mouse to rewind"
endif

if record=1

`cheap movement speed calc
move object 1,1.0
entity(1).xs=(object position x(1)-entity(1).x)/3.0
entity(1).ys=(object position y(1)-entity(1).y)/3.0
entity(1).zs=(object position z(1)-entity(1).z)/3.0
move object 1,-1.0

`Rotation according to mouse moves
entity(1).xas=curvevalue(mousemovey(),entity(1).xas,10.0)
entity(1).yas=curvevalue(mousemovex(),entity(1).yas,10.0)
entity(1).zas=0
entity(1).za=0
update()
record()

endif

`Rewind with left mouseclick
if record=2 then playback()

position camera entity(1).x,entity(1).y,entity(1).z
rotate camera entity(1).xa,entity(1).ya,entity(1).za
move camera -2.0

sync
loop


Remember that entity #1 is the player, so the values for this entity are forced, otherwise the update function would interfere - it is a sloppy way of doing it, but for the example it's fine.
So if you click the mouse then RECORD=2, and the game time is decreased and the objects revert to their previous stored state. If you don't click then RECORD=1, so the game time will increment and the objects are stored.

The mechanics of the player and camera are irrelevant, it's not usually the way I would handle a player model, so it's there to get the tutorial working, hopefully though that stuff is quite straightforward.

Now that update() function, it'll step through all the entities and update their position, rotation, velocity, as long as the mode>0 - if an object dies, becomes static, then setting the mode to 0 will remove it from entity recording, however this is not covered in this example, and it should probably only be done after one last recording - otherwise it might get a bit flaky - so record the state when an object stops moving, then set it's mode, and then you'll still be able to rewind etc. It'll look after itself I think as long as your sensible with the MODE variable, maybe use a flag to signify a dying entity - set the mode to -1 if it's dying, record it's dead position, then set it's mode to 0 in the next loop if mode=-1.

So, steps through the entities, if mode>0 then increment X Y and Z according to the speeds stored in XS YS and ZS. Also increment the X Y and Z angles based on the angle speeds XAS YAS and ZAS. This function also increases the speed, which is really just an artifact of the example, trying to make it more and more challenging to avoid the cubes, so they speed up - this should probably be disabled if using this code in a proper game. Any cubes that hit the walls bounce back, and all cubes start in the centre and spread out, so there is no chance of being owned by cubes before you can do anything when starting up.


function update()

for e=1 to 100

m=entity(e).mode

if m>0

`Affect position and angle based on speed
inc entity(e).x,entity(e).xs
inc entity(e).y,entity(e).ys
inc entity(e).z,entity(e).zs
inc entity(e).xa,entity(e).xas
inc entity(e).ya,entity(e).yas
inc entity(e).za,entity(e).zas

`Increase speed slowly
entity(e).xs=entity(e).xs*1.0001
entity(e).ys=entity(e).ys*1.0001
entity(e).zs=entity(e).zs*1.0001

`Bounce against walls
if entity(e).x<-100 then entity(e).xs=abs(entity(e).xs)
if entity(e).x>100 then entity(e).xs=-abs(entity(e).xs)
if entity(e).y<-100 then entity(e).ys=abs(entity(e).ys)
if entity(e).y>100 then entity(e).ys=-abs(entity(e).ys)
if entity(e).z<-100 then entity(e).zs=abs(entity(e).zs)
if entity(e).z>100 then entity(e).zs=-abs(entity(e).zs)

`Update and show object
position object e,entity(e).x,entity(e).y,entity(e).z
rotate object e,entity(e).xa,entity(e).ya,entity(e).za
show object e
else
hide object e
endif

next e

endfunction


Now the record and playback functions, these are actually quite straightforward I think. When recording, the timelinecur variable is incremented, and if it exceeds the limit of recordings it resets to 0. It records every active entity's state, really it's the same as saving the game data to a file, except this is the equivalent of having maybe 1000 save games running in sequence, so you could step back to any point in those 1000 recordings. The entity ID relates to the object in this example, needed so that the system knows which item in the entities() array to use. So STAMP and ID are like an index of this data, there will be several ID's per STAMP, and if you step back through the recording, then setting each entity until you hit the required stamp ID will 'rewind'.

`Increases gametime and sets timeline to objects state
function record()

inc gametime,1

for e=1 to 100

m=entity(e).mode

if m>0

timeline(timelinecur).x=entity(e).x
timeline(timelinecur).y=entity(e).y
timeline(timelinecur).z=entity(e).z
timeline(timelinecur).xa=entity(e).xa
timeline(timelinecur).ya=entity(e).ya
timeline(timelinecur).za=entity(e).za
timeline(timelinecur).xs=entity(e).xs
timeline(timelinecur).ys=entity(e).ys
timeline(timelinecur).zs=entity(e).zs
timeline(timelinecur).xas=entity(e).xas
timeline(timelinecur).yas=entity(e).yas
timeline(timelinecur).zas=entity(e).zas
timeline(timelinecur).mode=entity(e).mode
timeline(timelinecur).stamp=gametime
timeline(timelinecur).id=e
inc timelinecur,1
inc recordtime,1
if recordtime>timelines then recordtime=timelines

if timelinecur>timelines then timelinecur=0
endif

next e

endfunction


`Play back, rewinds gametime and sets objects to previous state
function playback()

if gametime<1 or recordtime<1 then exitfunction

dec gametime,1
dec timelinecur,1
dec recordtime,1

while timeline(timelinecur).stamp>=gametime

e=timeline(timelinecur).id
entity(e).x=timeline(timelinecur).x
entity(e).y=timeline(timelinecur).y
entity(e).z=timeline(timelinecur).z
entity(e).xa=timeline(timelinecur).xa
entity(e).ya=timeline(timelinecur).ya
entity(e).za=timeline(timelinecur).za
entity(e).xs=timeline(timelinecur).xs
entity(e).ys=timeline(timelinecur).ys
entity(e).zs=timeline(timelinecur).zs
entity(e).xas=timeline(timelinecur).xas
entity(e).yas=timeline(timelinecur).yas
entity(e).zas=timeline(timelinecur).zas
entity(e).mode=timeline(timelinecur).mode

position object e,entity(e).x,entity(e).y,entity(e).z
rotate object e,entity(e).xa,entity(e).ya,entity(e).za

if entity(e).mode>0
show object e
else
hide object e
endif


dec timelinecur,1
if timelinecur<0 then timelinecur=timelines
dec recordtime,1

endwhile

endfunction


It's just like a time machine, it's as fragile as the worst 60's sci-fi time machine as well. If you rewind, then most likely anything significant that you do will result in a different outcome, even without random elements or physics. The example is sorta like a challenge really, see if you can record a path without getting stuck - using the rewind if things go hairy, sometimes you have to make drastic changes, sometimes tiny tweaks are enough to get through.

Ok, will leave it there and see if you have any queries once you've read it.

Health, Ammo, and bacon and eggs!
Math89
22
Years of Service
User Offline
Joined: 23rd Jan 2004
Location: UK
Posted: 28th Jul 2010 17:20
@Van B: I haven't tried doing a keyframe system for this kind of rewind system, but I have done one for a multiplayer game. For simple scenes (with a few sparse bodies), all you need is to send to the server is the basic interactions (e.g. a bullet hits the body), and then, every few seconds, the whole data for a body (such as position and rotation).
For the case of a time machine, it is obviously slightly different , as we want to move backward in time. Because of the way physics engine work, it may be true that they can't be used in a straightforward way (because of the inertia of the objects, for example), but it is definitely possible, as long as there is a keyframe every x frames (and 15 Hz seems reasonable to me). to make sure the results do not diverge
In my opinion, the best way of doing it is to interpolate between a few keyframes (and even a simple linear interpolation may be enough).
Van B
Moderator
23
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 28th Jul 2010 17:32
Yup, I know what you mean, but one thing that concerns me, is the increased calculation time. If you have only 30 or 40 objects then there's no issue, but if you have 1000 objects, then the actual time it would take to interpolate them all might cause a problem. There's also the issue that it might have to rewind through a lot of data to get an affected objects state, so it can interpolate it.

The method I'm showing is greedy, but very straightforward - and fine for most DBPro projects. If I was writing something complex, then I'd definitely look into keyframing as opposed to recording everything. Depends on the project really, how much the record facility means to the game - not all games need that accuracy of course.

Health, Ammo, and bacon and eggs!

Login to post a reply

Server time is: 2026-07-25 08:29:50
Your offset time is: 2026-07-25 08:29:50