How to properly animate my game world

BlitzMax Forums/BlitzMax Programming/How to properly animate my game world

blackwater(Posted 2012) [#1]
Hello everyone. I'm currently working on a turn based RPG game that might be done in the year 2030 :)

In the meantime, I've come across a design question that I'm going to need some help with. How would I go about animating the game world in a way that doesn't create lag or be cumbersome?

Take for example my current game world. Right now, I'm only drawing what's on the screen even though a game level extends well beyond that. Currently I redraw the screen when something happens, such as scrolling, or clicking on an icon, displaying text, etc.

Now I'm faced with coding animated sprites. Animating sprites will require constantly updating the screen at some interval. The other problem I see happening is I'm sure sprites will need to be animated at different speeds. Example, my explosion needs a new cell displayed every 500MS but a creature animation requires animation at every 100MS.

Can anyone send me in the general direction of how to best process this? Should I just update my entire game screen every 5MS all the time and create points on when to update an individual sprite? If that's the case, what Blitz function should I be looking at? Is there a better way?

Thanks in advance for anyone's help :)


Midimaster(Posted 2012) [#2]
You should use timestamps with the command MILLISECS(). Give a future event a timestamp of Now+2sec and let it happen, when this time is reached:

here is a sample with tow individual events:
Graphics 800,600
Repeat
    Cls
    If Event_1_Time<MilliSecs()
         Event_1_Time=MilliSecs()+500
         A=A+1
    EndIf
    If Event_2_Time<MilliSecs()
         Event_2_Time=MilliSecs()+100
         B=B+1
    EndIf
    DrawText  "A=" + A + "    B=" + B, 100, 100
    Flip 1
Until KeyHit(Key_Escape)





blackwater(Posted 2012) [#3]
Does the Millisec value return to zero 0 at all, in which case the check will fail sill it will not be less then millsecs?


blackwater(Posted 2012) [#4]
Midimaster I also want to say thank you! I did exactly that and I'm updating my game screen perfectly as well as updating my animations at their own speed. It's a very simple solution but something I overlooked.


nitti(Posted 2012) [#5]
" Does the Millisec value return to zero" I don' believe that, I though it was the amount of millisecs since 1970 so it won't become 0, (the difference on th other hand..)


GfK(Posted 2012) [#6]
Millisecs() returns the amount of milliseconds since system startup. It will become a negative value after roughly 24 days of up-time.

To expand on what midimaster said, create a 10Hz timer (or even a 1Hz timer), and use that for scheduling future events. It's what I do and it works really well. The good part is, although its value is still a signed int and will therefore still wrap around to a negative number just like Millisecs() does, this won't happen for about nine months with a 10Hz timer, or about seven years with a 1Hz timer.

As if that isn't good enough, you can reset the timer to zero (myTimer._ticks = 0) whenever it's convenient so the wrap-around issue need never arise.


Zethrax(Posted 2012) [#7]
If done right the wrap-around with the Millisecs() function should never become an issue. Check my comment in the linked page below for more info.

http://www.blitzbasic.com/b3ddocs/command.php?name=Millisecs&ref=2d_cat


GaryV(Posted 2012) [#8]
I am not positive, but I think if you search, you can find an old post GfK did with a code example of what he is describing.


blackwater(Posted 2012) [#9]
GfK, here is where I get lost. How can I use a 1mz or 10 mz timer to update an animation that needs to change frames every 200ms or so? A 10mz timer, as I understand it, will tick much slower then that.


Midimaster(Posted 2012) [#10]
thats all not important! Folks talking about a seldom strange situation of MILLISECS() which you never will see... Millisecs() will only return to Zero after 24 days of computer is running continuosly.


Basic is:

MILLISECS() return you the time since the computer started in millisecs. So you always get a value for "Now". If you want to wait for a certain moment 2 seconds later you only have to save "Now" into a variable and add 2*1000 to this and wait until the MILLISECS() reaches this time:

Graphics 800,600
Future%=MilliSecs()+2*500
Repeat
    Cls
    If Future<MilliSecs()
         DrawText  "Time has reached" , 100, 100        
    EndIf
    Flip 1
Until KeyHit(Key_Escape)



If you want to do thing periodic you only have to "reset" you variable FUTURE% when reached the target:

Graphics 800,600
Future%=MilliSecs()+1000
Repeat
    Cls
    If Future<MilliSecs()
         Future=MilliSecs()+1000
         DrawText  "Time has reached" , 100, 100        
    EndIf
    Flip 1
Until KeyHit(Key_Escape)



If you need a timer that switches every 200msec a animated image you do it like the same:

Graphics 800,600
Repeat
    Cls
    If Future<MilliSecs()
         Future=MilliSecs()+200 
         Steps=Steps+1
         If Steps=10 Steps=0		
    EndIf
	'DrawImage Animation, 100,100,Steps
	SetColor Steps*10+100,0,0
	DrawRect 100,500,50,-30-Steps*10
    Flip 1
Until KeyHit(Key_Escape)




ABOUT TIMERS:

I would use the CreateTimer() Function to get a constant FLIP frame rate in your games, but not to animate individual characters. There is no need to use CREATETIMER() if you want to work with MILLISECS():

Graphics 800,600
FPS=CreateTimer(60)
Repeat
    Cls
    DrawText  "This is a game running on 60Hz" , 100, 100        
    Flip 0
    WaitTimer FPS
Until KeyHit(Key_Escape)



Derron(Posted 2012) [#11]

Folks talking about a seldom strange situation of MILLISECS() which you never will see... Millisecs() will only return to Zero after 24 days of computer is running continuosly.


ronny@ronnyPC ~ $ uptime
 14:00:53 up 43 days, 21:21,  2 users,  load average: 0.65, 0.93, 0.89



Instead of using "Millisecs" you could do it a bit differently:
- each game-update call is "calculating" the time gone since last update
- if you want to time something:
- - object.timer (holding rest until "action" in milliseconds)
- - object.time (holding the interval)
- - object.update(timesincelastupdate)
- - - object.timer :- timesincelastupdate
- - - if object.timer < 0 then object.action
- - object.action is a method which resets object.timer to object.time

if doing a game pause you wont update the game. If hitting "resume" you just set the "last update call time" to the current one and the game will continue flawless.

The game-update call could be linked to a TTimer if needed but that is an individual decision - if not you will end up using Millisecs() ONE time in the code - in the game-update-time-calculation.


bye
Ron


edit: Here is the current "TTimer"-Object I use to animate certain things.

'for things happening every X moments
Type TTimer
	field interval:int		= 0		'happens every ...
	field intervalToUse:int	= 0		'happens every ...
	field actionTime:int	= 0		'plus duration
	field randomness:int	= 0		'value the interval can "change" on GetIntervall() to BOTH sides - minus and plus
	field timer:int			= 0		'time when event last happened

	Function Create:TTimer(interval:int, actionTime:int = 0, randomness:int = 0)
		local obj:TTimer = new TTimer
		obj.interval	= interval
		obj.actionTime	= actionTime
		obj.randomness	= randomness
		'set timer
		obj.reset()
		return obj
	End Function

	Method GetInterval:int()
		return self.intervalToUse
	End Method

	Method SetInterval(value:int, resetTimer:int=false)
		self.interval = value
		if resetTimer then self.Reset()
	End Method

	Method SetActionTime(value:int, resetTimer:int=false)
		self.actionTime = value
		if resetTimer then self.Reset()
	End Method

	'returns TRUE if interval is gone (ignores action time)
	'action time could be eg. "show text for actiontime-seconds EVERY interval-seconds"
	Method doAction:int()
		local timeLeft:int = Millisecs() - (self.timer + self.GetInterval() )
		return ( timeLeft > 0 AND timeLeft < self.actionTime )
	End Method

	'returns TRUE if interval and duration is gone (ignores duration)
	Method isExpired:int()
		return ( self.timer + self.GetInterval() + self.actionTime <= Millisecs() )
	End Method

	Method reachedHalftime:int()
		return ( self.timer + 0.5*(self.GetInterval() + self.actionTime) <= Millisecs() )
	End Method

	Method expire()
		self.timer = -self.GetInterval()
	End Method

	Method reset()
		self.intervalToUse = self.interval + rand(-self.randomness, self.randomness)

		self.timer = Millisecs()
	End Method

End Type


Maybe it helps.

Last edited 2012


blackwater(Posted 2012) [#12]
Here is what I just did and it seems to work fine, haven't really tested it. Now of course this probably isn't a representation of real time passing but for my purposes it doesn't matter, I just need a way to control how fast things get checked and so on. Going to post here for comments or maybe it will help someone else out

global MasterTimer:long

Repeat
UpdateTimers()

WaitEvent
' event stuff here
forever

Method UpdateTimers()
MasterTimer:+1

If Timer < MasterTimer
Timer = MasterTimer + 7 ' the 7 can be any number, this controls the speed


' do stuff here
End If

If MasterTimer = 10000000 ' this number can be any number actually
Timer:-MasterTimer ' just deduct the master timer amount from all existing timers

MasterTimer = 0
End If

Last edited 2012


Midimaster(Posted 2012) [#13]
@ blackwater:
no further comments from my side... good luck!

@folks:
I think he is a beginner. Could you please write him even more complicated code to demonstrate him your skills, please?