How to make a one time animation ?

Started by Gijsdemik, April 23, 2020, 16:33:18

Previous topic - Next topic

Gijsdemik

Hi guys,
i am back at programming my game, and it goes well i am currently programming the combat system.
it took me almost 3 years of learning blitzmax and working on my game from time to time. since i have a lot of free time
i am finishing my game. you all helped me before and i really appreciate that
i have encounter a new issue that i cannot seem to get right.

How can i display one time animations? and how to properly time it. it got a kinda working when you press p the animation comes in display only random frames and disappears after a few tries the game crashes.
I want to display a one time animation like when creature a hits creature b a kaboom or a POW is shown.
Or if i click my mouse to move and so on.


I am really excited that i can actually play a round of combat after al these years. and when the test program works i can adept it easily to my world editor and other object info. sidenote: i mention every one that helped me in the source code and when the game is done they will be mention in the game to

Matty

In a variable store,for each animation the following data:

Islooped,boolean
Currentframe, float
Currentmode, integer
Startframe, integer
Endframe, integer
Framespeed, float

So let us say that you have three modes that currentmode can store...idle, walk and attack.
Idle and walk are looped, attack is not.
Player pushes 'attack' button:
If currentmode = idle or walk then
Set currentmode to attack
Set currentframe to startframe of the attack
Endif

Then every drawing frame do this:
Currentframe increment by framespeed.
If currentframe bigger than endframe then:
If looped go back to startframe otherwise set currentmode to idle and currentframe to startframe

Before drawing set variable drawframe to a floored integer value of currentframe and draw.

That's a simple mechanism.

Derron

instead of "currentFrame, startFrame, endFrame" you could also have

currentIndex, frames[] (array), frameTimes[] (array)

so frames do not necessarily need to be 0,1,2,3,4.. they could also be 0,1,0,1,2,3,0,1,0 ...
This allows more complex animations to be "mixed" (maybe even randomly) from existing sprite frames.
the "frameTimes" allow individual frame speeds. So you could have a "blink" animation like this:
frames = [0,1] '(non blink, blink)
frameTimes = [1000, 75] '1 second non blinking, 75ms blinking


Instead of setting them to "idle" when finished (and non-looped you could just mark the animation as ended. It is up to the animation manager (except the animation does it) to chain things together.

if currentAnimation.Finished() then SetAnimation(idleAnimation)
(you could even add a "if rand(0,100)" there to randomize what animation is coming next - idle1, idle2, ...)

Next to "loop" you might also add "pingpong" - so an animation "1,2,3" would do "1,2,3,2,1,2,3,2,1...". Or "pingPongInclusive" for "1,2,3,3,2,1,1,2,3,3,2,1...".


bye
Ron

Matty

Interesting Derron.  Your method is more flexible but of more interest to me is the mindset or philosophical difference behind the two approaches-why each of us gravitates to one type of solution or another.  I feel your method is better in the general case but that mine is extremely simple to implement.  And yet while I see your method as superior I am likely to stick for myself to the method I described.

iWasAdam

#4
you're both sortof talking about the same thing in different ways...

for any timeline. movement/playback is usually always A > B > C > D, etc. The time it takes to get to the next FRAME could be different and frames could be looped E.G. [a,b,c,<] [a,b,c,<] [a,b,c,<]
You could also add pingping and reversed playback, but that would usually be for the entire timeline, or as with matty a start and end frame


What happen at a frame is where the fun begins
so with Matty you are just using the timeline directly and it is a simple process both in terms of programming and for the user to understand

with Derrons the complexity allows great freedom, but at the cost of programming - lots of different things would need to be handled - frames and time almost becomes individual entities themselves and the timeline concept breaks down. access for the user becomes complex as it is not really a timeline but a series of related frames.


Getting back to the original question:one time animation like when creature a hits creature b a kaboom
You have a timeline that runs for a specified amount of time lets assume 1 second
you have 4 frames of animation (swipe)
1000 (millisecs) / 4 = 250 millisecs
frame 1 = 0 millisecs
frame 2 = 250 millisecs
frame 3 = 500 millisecs
frame 4 = 750 millisecs
frame 5 = 1000 millisecs END condition

what you define into a frame (3d movement, sprite, etc) becomes your choice

What you do at the END condition is also up to you, you could loop the animation, or reverse play, or trigger another animation (say a kaboom animation)

The actual question then becomes 'how do I program a simple animation system'?

the simplist way is to have a number of frames that are triggered at a certain time. 25 or 30 per second is the standard. You just need to fill in the data in these frames to make it 'do' stuff. It's simple to program and simple to execute :)

Derron

frames are coupled to time ... so my approach still works with a timeline (albeit it is possible to break it up - of course).

Having custom "times" for a frame index can be expressed in multiple ways. Assume we have to define each millisecond indidually, you would define
frames: 1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3...
But if you instead would write the frame + the duration of this frame you would shorten this long line to:
frames: 1,2,3, ...
times: 7,8,5, ...

Still any algorithm could find out when which frame has to be used - also in the "future". So we still have a timeline.

Complexity of the whole thing is not that much more. It allows to split "animation logic" from "design logic" (when finishing scratch-your-backidling, play the find-a-booger-idle-anim). So it should not be hardcoded in the animation code - except maybe when just prototyping or if you know you _never_ will adjust that or reuse the code (I am of course doing too much for "code reusal"). If you know you are ever only simply looping over given frames with constant frame times, then go with Matty's code and keep it simple. But as soon as you might add in some more stuff here and there you could probably endup having a "field animation:TAnimation" in your type at the end.


Regarding "what happens". You could have registered "hooks" for certain steps in the animation - so eg hook into the timeline so when "2 seconds are gone" (might be 3 seconds already - if computer lagged a bit or so) a registered callback is executed. Should of course only be used for graphical stuff (logic needs to run separate from render - to avoid issues in gpu-heavy scenes).

Gijsdemik

Hi guys, Thanks for helping me out
I know i am late in my response but i had not to the time to programm.
now i have a few weeks for my self so i am at it again.

I feel a bit dumb when it comes to this, i made two diff rant Types and decided on the last one which uses an external clock input for the frame time. i cant seem to really get a counter increment using the timerticks functions which counts when a timer ticks.

I have these two types set up and i tried a bunch of diff rant programmings could some one help me on how to add the external clock frame speed into my DoFx()?
In my program i already have a working external clock trough maxgui i tried adapting that same code to my Type but it would not work. so i have a frameclock thats based on 8 frames(wich is max for my game anyhow) so i thought of adding that
because creating a new clock for every effect uses more resources than really necessary. thing is it wont retrigger or start on first animation frame but that is something to work out later

Below here are the two types i tried
this is my old one
Type AnimationEffect

Field FileUrl:String
Field Size:Int
Field Frames:Int
Field FirstFrame:Int
Field LastFrame:Int
Field X:Int
Field Y:Int

Method AnimationLoad()
Global AttackAnimation:TImage=LoadAnimImage(FileUrl,Size,Size,Frames,FILTEREDIMAGE+MASKEDIMAGE)
End Method

Method DoFX()
Local AnimationTimer:TTimer=CreateTimer(30,Null)
Local PlayFrame=1
If TimerTicks(AnimationTimer) PlayFrame=++1
DrawImage AttackAnimation,X,Y,PlayFrame
If PlayFrame = LastFrame ;PLayFrame=0 ; StopTimer(AnimationTimer)
End Method
End Type
'load effect
Global Kaboom:AnimationEffect= New AnimationEffect
Kaboom.FileUrl="F:\Kapow.png"
Kaboom.Size=25
Kaboom.FirstFrame=1
Kaboom.LastFrame=8
Kaboom.x=120
Kaboom.y=120
'executed at gameloop/event
Kaboom.DoFX()

And the last one i think would be better it still needs an external clock timer i could not figure out how to add that

Type AnimationEffect
Field ThisPicture:TImage
Field FileUrl:String
Field Size:Int
Field Frames:Int
Field FirstFrame:Int
Field LastFrame:Int
Field X:Int
Field Y:Int
Field PlayFrame:Int


Method AnimationLoad()
ThisPicture=LoadAnimImage(Self.FileUrl,Size,Size,Frames,FILTEREDIMAGE+MASKEDIMAGE)
End Method

Method DoFX()

For Local I:Int =FirstFrame To LastFrame
DrawImage ThisPicture,X,Y,PlayFrame
If PlayFrame=> LastFrame ;PlayFrame=0;
Next

End Method
End Type
'load effect
Global Kaboom:AnimationEffect= New AnimationEffect
Kaboom.FileUrl="F:\Kapow.png"
Kaboom.Size=25
Kaboom.FirstFrame=1
Kaboom.LastFrame=8
Kaboom.x=120
Kaboom.y=120
'executed at gameloop/event
Kaboom.DoFX()


Thanks again for the help i must be missing something here


Gijsdemik

Hi All,

I ended up using this code. although i think it could be done more efficient and better it does the trick. since my game is getting more complex i feel i am approaching things the wrong way.
i cant say for sure as i dont have any insight in game programming of others or how to write nice code any how this works for now.

Global CountT8:Int
Global MyClock:Int

Global Ztimer:TTimer = CreateTimer( 15 )
Function DoMeOnce(X:Int,Y:Int,T:Int,MX:Int,Pic:Int)
CountT8=CountT8+WaitTimer(ZTimer)
If CountT8=MX Then CountT8=0
DrawImage PicArr[Pic],X,Y,CountT8
End Function

Global Do:Int=0
Local DoFx:Int

Repeat

If KeyHit(Key_1)=True Then DoFx=1
If KeyHit(Key_2)=True Then DoFx=2
If KeyHit(Key_3)=True Then DoFx=3

If KeyHit(Key_Z)=True And Do= 0 Then Do=DO+1
If DO=1 Then CountT8=0 Do=2
If DO=2 Then DoMeOnce 300,300,WaitTimer( Ztimer ),FrmMax[DoFx],DoFx
If CountT8 = FrmMax[DoFx]-1 Then DO=0


DrawText DoFx,200,100
Flip
Cls

Until KeyDown(Key_X)
 

Any sources on how people use blitzmax to code games is very welcome at this point.

Kryzon

#8
Your earlier AnimationEffect type/class is the right way to go, compartmentalizing things. It's good to have classes with limited responsibilities, because if something goes wrong (and it always does) you will know where to look to fix it.
BlitzMax is not as popular as it used to be in the past, so for the most of it you're going to have to rely on content from the old BlitzBasic ecosystem (most are offline, as seen by the Web Archive links below):
PS: That WaitTimer() call blocks the whole program. If you want multiple sprites updating at different times and rates then you need a different animation update logic, with a proper main loop and each sprite handling its own update rather than things out in the open with globals (i.e. your main loop doesn't know what frames each sprite is in, it just says "hey sprites, advance your frames one step, whatever they are", compartmentalized), and that's a whole topic altogether. 
Sprite animation is an intermediate topic, maybe you should put it on wait until doing a smaller project so you can get more familiar with a game structure and the BlitzMax language. The things you learn in BlitzMax will be applicable to other languages and game engines so it's a win-win for you.

Gijsdemik

Thank you for the response.
I had quite a hard time getting my animation to use the clock.
i had to use an older external clock when i tried adding a new one for the animations
they behaved slow and strange even when i setted the clock real fast.
most of the time the program would crash.

I am going to look in to the information you send me so i can read how it supposed to be done
:)