09-06-2025, 08:09 AM
Here's a better animation example. This is pretty much how I do it in Neon Breath and Robowack 3.
I got a weird runtime error related to the % operator when testing this. Because of it, I relased a new n7 version without posting about it (https://naalaa.com/n7/N7_250906.zip). If you get a runtime error when testing this example, you can download the new n7 version and see if it helps.
Put this code in the same folder as the original example. Basicly, in a game, you would let every object (like enemies) have an "AnimationController" to help you with its animations.
I got a weird runtime error related to the % operator when testing this. Because of it, I relased a new n7 version without posting about it (https://naalaa.com/n7/N7_250906.zip). If you get a runtime error when testing this example, you can download the new n7 version and see if it helps.
Put this code in the same folder as the original example. Basicly, in a game, you would let every object (like enemies) have an "AnimationController" to help you with its animations.
Code:
' http://tfc.duke.free.fr/coding/md2-specs-en.html
' ------------------------------------------------
include "s3d.n7"
'#win32
' Display settings.
constant RES = 480 ' Vertical resolution.
constant SCALE = 1 ' Window scale.
constant FULLSCREEN = 0 ' Fullscreen if 1
constant DISPLAY_FPS = 1 ' Display fps if 1.
constant FOV = 45 ' Vertical field of view in degrees.
constant MOUSE_SENS = 1 ' Mouse sensitivity.
' For delta time.
visible vLastTick = 0
' Some animations. You can look for other animations in the output when the program starts.
visible vPlayerWalkAnim = [40, 41, 42, 43, 44, 45]
visible vPlayerPunchAnim = [198, 199, 200, 201, 202, 203, 204, 205, 206, 207]
visible vPlayerCrouchPunchAnim = [208, 209, 210, 211, 212]
visible vPlayerWaveAnim = [112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]
visible vSaluteAnim = [84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94]
' Create window.
set window "MD2", RES*screenw()/screenh(), RES, FULLSCREEN, SCALE
set redraw off
' Load md2 model.
info = []
scale = 0.04
model = LoadMD2("assets/doomguy/tris.md2", true, scale, -scale, scale, false, info)
texture = loadimage("assets/doomguy/texture.png")
' Print list of available frames.
for i = 0 to sizeof(info.frames) - 1
pln i + ": " + info.frames[i]
next
' Create an AnimationController, with some functions that help us manage animations for an object.
anim = AnimationController()
' Start playing the looped walk animation.
anim.Play(vPlayerWalkAnim, 5, true)
' Flag player walking. If player is walking we can press keys to do other stuff.
walking = true
' Init s3d with the window as render target, a field of view of FOV degrees, near clip plane at
' distance 0.1 and far clip plane at 10.
S3D_SetView(primary, rad(FOV), 0.1, 10)
' Rotation angle for the mesh.
rotY = 225
distance = 3
dragging = false
lastMouseX = 0
lastMouseY = 0
' Loop until user presses esc.
while not keydown(KEY_ESCAPE, true)
' Get time passed since last frame.
dt = DeltaTime()
' Update mesh animation.
' AnimationController.Update returns false if a non-looped animation has finished playing. In
' that case, restart the walk animation.
if not anim.Update(dt)
anim.Play(vPlayerWalkAnim, 5, true)
walking = true
endif
' Check keys if walking.
if walking
if keydown(KEY_1, true)
' Start punch animation and unflag walking.
anim.Play(vPlayerPunchAnim, 10, false)
walking = false
elseif keydown(KEY_2, true)
anim.Play(vPlayerCrouchPunchAnim, 10, false)
walking = false
elseif keydown(KEY_3, true)
anim.Play(vPlayerWaveAnim, 10, false)
walking = false
elseif keydown(KEY_4, true)
anim.Play(vSaluteAnim, 10, false)
walking = false
endif
endif
' Rotate and zoom when left mouse button is pressed.
if mousebutton(0)
if dragging
mx = mousex()
my = mousey()
rotY = rotY - (mx - lastMouseX)*MOUSE_SENS
distance = min(max(distance + 0.05*(my - lastMouseY)*MOUSE_SENS, 0.5), 10)
lastMouseX = mx
lastMouseY = my
else
dragging = true
lastMouseX = mousex()
lastMouseY = mousey()
endif
else
dragging = false
endif
' Dark blueish background.
set color 16, 16, 32
cls
' Clear transformation and depth buffer.
S3D_Clear()
' Translate and rotate.
S3D_Translate(0, 0.25, distance)
S3D_RotateY(rad(rotY))
S3D_Texture(texture)
'S3D_BlendMesh(model, anim.f0, anim.f1, anim.b)
anim.Render(model)
' Write information and instructions.
set color 255, 255, 255
set caret 0, 0
wln "Rotate and zoom by moving the mouse while holding down left mouse button"
wln "Press 1 to punch, 2 to crouch punch, 3 to wave and 4 to salute"
wln
wln "Press Esc to quit"
' Display.
DisplayFps(dt)
' Update window.
redraw
wait 1
wend
' AnimationLoop
' -------------
' Just a helper.
function AnimationLoop(frames)
anim = []
anim.frames = frames
anim.param = 0
anim.frame0 = frames[0]
anim.frame1 = frames[1]
anim.blend = 0
anim.Forward = function(k)
k = k*sizeof(.frames)
.param = (.param + k)%sizeof(.frames)
.frame0 = .frames[int(.param)]
.frame1 = .frames[int((.param) + 1)%sizeof(.frames)]
.blend = .param%1
endfunc
return anim
endfunc
' DeltaTime
' ---------
' Return delta time in seconds since last call.
function DeltaTime()
t = clock()
dt = (min(t - vLastTick, 100))/1000
vLastTick = t
return dt
endfunc
' DisplayFps
' ----------
' Display number of frames per second in bottom right corner based on delta time in seconds.
function DisplayFps(dt)
if DISPLAY_FPS
set caret width(primary) - fwidth(" "), height(primary) - fheight()
set justification right
set color 0, 255, 0
write "FPS: " + str(round(1/dt), 3)
set justification left
endif
endfunc
' LoadMdD2
' --------
' Create a mesh, possibly with multiple frames, from MD2 file.
function LoadMD2(filename, swapYZ, scaleX, scaleY, scaleZ, invertFaces, info)
assert typeof(info) = TYPE_UNSET or typeof(info) = TYPE_TABLE, "LoadMD2: info parameter must be unset or a table"
' Load all data from file.
f = openfile(filename, true)
if not file(f) return unset
ident = fread(f, 32, true)
if ident <> 844121161 return unset
version = fread(f, 32, true)
if version <> 8 return unset
skinWidth = fread(f, 32, true)
skinHeight = fread(f, 32, true)
frameSize = fread(f, 32, true)
numSkins = fread(f, 32, true)
numVertices = fread(f, 32, true)
numSt = fread(f, 32, true)
numTris = fread(f, 32, true)
numGlCmds = fread(f, 32, true)
numFrames = fread(f, 32, true)
offsetSkins = fread(f, 32, true)
offsetSt = fread(f, 32, true)
offsetTris = fread(f, 32, true)
offsetFrames = fread(f, 32, true)
offsetGlCmds = fread(f, 32, true)
offsetEnd = fread(f, 32, true)
uvs = []
if numSt
file seek f, offsetSt, SEEK_SET
for i = 0 to numSt - 1
uvs[sizeof(uvs)] = [fread(f, 16, true)/skinWidth, fread(f, 16, true)/skinHeight]
next
endif
tris = []
if numTris
file seek f, offsetTris, SEEK_SET
for i = 0 to numTris - 1
v0 = fread(f, 16, true); v1 = fread(f, 16, true); v2 = fread(f, 16, true)
uv0 = fread(f, 16, true); uv1 = fread(f, 16, true); uv2 = fread(f, 16, true)
if invertFaces tris[sizeof(tris)] = [v2, v1, v0, uv2, uv1, uv0, unset]
else tris[sizeof(tris)] = [v0, v1, v2, uv0, uv1, uv2, unset]
next
endif
frames = []
if typeof(info) info.frames = []
if numFrames
file seek f, offsetFrames, SEEK_SET
for i = 0 to numFrames - 1
frame = []
frames[sizeof(frames)] = frame
sx = fread(f, 64, false); sy = fread(f, 64, false); sz = fread(f, 64, false)
tx = fread(f, 64, false); ty = fread(f, 64, false); tz = fread(f, 64, false)
name = ""
done = false
for j = 0 to 15
c = fread(f, 8, true)
if c = 0 done = true
if not done name = name + chr(c)
next
if typeof(info) info.frames[sizeof(info.frames)] = name
frame.verts = []
for j = 0 to numVertices - 1
x = tx + fread(f, 8, false)*sx
y = ty + fread(f, 8, false)*sy
z = tz + fread(f, 8, false)*sz
if swapYZ frame.verts[sizeof(frame.verts)] = [scaleX*x, scaleY*z, scaleZ*y]
else frame.verts[sizeof(frame.verts)] = [scaleX*x, scaleY*y, scaleZ*z]
tmp = fread(f, 8, false) ' Not interested in normal index.
next
next
endif
free file f
' Build mesh.
mesh = S3D_CreateMesh(frames[0].verts, uvs, unset, tris)
if numFrames > 1
for i = 1 to numFrames - 1
S3D_AddMeshFrame(mesh, frames[i].verts)
next
endif
return mesh
endfunc
' AnimationController
' -------------------
function AnimationController()
a = []
a.f = unset ' frames
a.f0 = 0 ' frame 0
a.f1 = 0 ' frame 1
a.p = 1 ' param
a.b = 1 ' blend
a.spd = 0 ' speed
a.ospd = 0 ' old speed
a.l = false ' looped
a.s = 0 ' state
' Play
' ----
a.Play = function(frames, spd, looped)
.f = frames
'.mf = sizeof(.f) - 1
if .spd then .ospd = .spd
else .ospd = spd
.spd = spd
.l = looped
if .b < 1
.s = 2 ' finish blend
else
.f0 = .f1
.f1 = .f[0]
.b = 0
.s = 3 ' blend old frame to first frame
endif
endfunc
' SetSpeed
' --------
a.SetSpeed = function(spd)
.spd = spd
endfunc
' Update
' ------
a.Update = function(dt)
if .s = 2 ' finish blend
.b = .b + 2*.ospd*dt
if .b >= 1
.f0 = .f1
.f1 = .f[0]
.b = 0
.s = 3
endif
elseif .s = 3 ' blend old frame to first frame
.b = .b + 2*.spd*dt
if .b >= 1
.f0 = .f[0]
.f1 = .f[0]
.p = 0
.b = 0
.s = 1
endif
elseif .s = 1 ' animate
if .l ' looped.
.p = (.p + .spd*dt)%sizeof(.f)
.f0 = .f[int(.p)]
.f1 = .f[int((.p) + 1)%sizeof(.f)]
.b = .p%1
else
.p = .p + .spd*dt
if .p >= sizeof(.f) - 1 ' done.
.s = 0
.b = 1
.f0 = .f[sizeof(.f) - 1]
.f1 = .f[sizeof(.f) - 1]
else
.b = .p%1
' crash on f1 line when low fps
.f0 = .f[int(.p)]
.f1 = .f[int(.p) + 1]
endif
endif
endif
return .s <> 0
endfunc
' Render
' ------
a.Render = function(mesh)
S3D_BlendMesh(mesh, .f0, .f1, .b)
endfunc
return a
endfunc
