Hey all,
I got a lot of questions on how I did the sprite stacking for my train demo, so I put together a simple example.
In short, objects are sliced vertically and drawn accordingly.
Then it's a matter of drawing them on top of each other, offsetting them more and more for each one.
To get the nice voxel/3D effect you also need to rotate the stack. In the example above the rotation function is courtesy of Frederic Souchu
Have fun!
@johanp This is amazing!
I’ve always been fascinated with this technique.
I’ve seen this effect on a JavaScript game on codepen many years ago called taxi apocalypse:
https://codepen.io/Gthibaud/details/WXxMdb
By https://codepen.io/Gthibaud
I’ve also stumbled across a great online tool to generate these stacked sprites on https://spritestack.io/
example: https://spritestack.io/sprite/1078
Now that I know how it’s done in Pico-8, I’m looking forward to experimenting with some game ideas!
Not sure if this is the right place to ask for help?
I am still learning about pico-8 and game coding in general.
I spent some time using this sprite stack trick to make a 3d pixel art DeLorean, but I cannot figure out how to make it drive around the screen in the direction it is pointing, I've had a look at code from other carts for examples but can't make any sense of it, any help in the right direction would be great!
example of issue:
code I am working with:
function _init() dln={} dln.angle=0 dln.x=0 dln.y=0 end |
function _update() if (btn(0)) dln.angle-=0.01 if (btn(1)) dln.angle+=0.01 if (btn(2)) then dln.x -= cos(angle) dln.y -= sin(angle) end end |
function _draw() cls(2) newangx=dln.x + cos(dln.angle) * 3 newangy=dln.y + sin(dln.angle) * 3 -- draw_dln(dln.x+56,dln.y+62,dln.angle) draw_dln(newangx+56,newangy+62,dln.angle) -- debug print("a "..dln.angle,1,(8*0)) print("y "..dln.y,1,(8*1)) print("x "..dln.x,1,(8*2)) print("nax "..newangx,1,(8*3)) print("nay "..newangy,1,(8*4)) end |
function draw_dln(x,y,a) -- draw stack for i=0,6 do local sx = 16 * (i%8) local sy = 16 * flr(i/8) rspr(sx,sy, x, y-i*1, a, 2) end end |
-- sprite rotation by @fsouchu ... |
Hey there @2358,
glad you like it and is putting it to good use!
To be honest, I always stumble on this too, and usually spend 5-10 minutes trying to get it right. The exact thing happened to me when I did the example above! :D
Trig in pico8 doesn't behave exactly like in the real world. From the wiki:
Important: PICO-8 measures the angle in a clockwise direction on the Cartesian plane, with 0.0 to the right, 0.25 downward, and so on. This is inverted from the convention used in traditional geometry, though the inversion only affects sin(), not cos().
In the cart above I've drawn the bus sprite so that it is facing/heading right. To get that working in code, I do this:
draw_bus(
56+40cos(angle), -- direction
64+40sin(angle), -- direction
-angle-0.25) -- rotation angle
Ie, negate the angle and remove 90 degrees. :D
Oh boy. #honestGameDev
Whoo! it drives! thank you so much! @johanp
I did not realise the orientation was measured like that, but it all makes sense now.
I also messed up on the button angle codes (now fixed), I shouldn't be trying to code so early in the morning while half asleep, so easy to overlook these things.
@johanp, might want to enclose that tidbit about trigonometry quirks into a code block, "matching" * are treated as italics.
Here's the rotation function modified to allow for non-square sprites
-- rotate a sprite -- col 15 is transparent -- sx,sy - sprite sheet coords -- x,y - screen coords -- a - angle -- w - width in tiles -- h - height in tiles function rspr(sx,sy,x,y,a,w,h) local l=w if(l<h)l=h local ca,sa=cos(a),sin(a) local srcx,srcy local ddx0,ddy0=ca,sa local mask=shl(0xfff8,(l-1)) w*=4 h*=4 l*=4 ca*=l-0.5 sa*=l-0.5 local dx0,dy0=sa-ca+w,-ca-sa+h w=2*w-1 h=2*h-1 l=2*l-1 for ix=0,l do srcx,srcy=dx0,dy0 for iy=0,l do if (band(bor(srcx,srcy),mask)==0 and srcx<=w+1 and srcy<=h+1) then if (w*8>ix and h*8>iy) then local c=sget(sx+srcx,sy+srcy) -- set transparent color here if (c!=15) pset(x+ix,y+iy,c) end end srcx-=ddy0 srcy+=ddx0 end dx0+=ddx0 dy0+=ddy0 end end |
Hi @2358 any chance you could share your cart with the updated version the method for moving the sprite? I'm struggling...
would anyone have a link to sprite stacking using tline rather than pset?
Hi @Dezorian
I have not worked on this in years, and half the code is not mine,
I got a ton of help from other coders including @freds72
but here is an iteration of the file that I worked on that adds tilt
up/down to change the perspective angle |
left/right to rotate/steer |
z/x to drive/reverse |
I've been told that this method is a poor mans 3d but it's a neat effect,
I am sure there are way better ways of coding this too but hopefully this helps you out...
I'd not heard of this technique until I saw this post but I gave it a go with tline.
It's not super general and you'll see it's really inefficient (I'm recalculating loads of values I could just cache and reuse but meh.) No idea if it'll be helpful and I have no doubt there are better ways to do it. But it does work, for whatever that's worth.
@johanp, could you share your updated code that uses tline?
(seen used in a gif you posted on twitter)
Here you go.
I think it is a mix of freds72 and Enargy's code that I've mashed together. Maybe.
function rspr2(tx,ty,sx,sy,a,sz) local scale=sz*8 local c,s=cos(a),-sin(a) local p={ {x=0,y=0,u=tx,v=ty}, {x=scale,y=0,u=tx+sz,v=ty}, {x=scale,y=scale,u=tx+sz,v=ty+sz}, {x=0,y=scale,u=tx,v=ty+sz} } local w=(scale-1)/2 for _,v in pairs(p) do local x,y=v.x-w,v.y-w v.x=c*x-s*y v.y=s*x+c*y end tquad(p,sx,sy) end function tquad(v,dx,dy) local p0,spans=v[4],{} local x0,y0,u0,v0=p0.x+dx,p0.y+dy,p0.u,p0.v for i=1,4 do local p1=v[i] local x1,y1,u1,v1=p1.x+dx,p1.y+dy,p1.u,p1.v local _x1,_y1,_u1,_v1=x1,y1,u1,v1 if(y0>y1) x0,y0,x1,y1,u0,v0,u1,v1=x1,y1,x0,y0,u1,v1,u0,v0 local dy=y1-y0 local dx,du,dv=(x1-x0)/dy,(u1-u0)/dy,(v1-v0)/dy if(y0<0) x0-=y0*dx u0-=y0*du v0-=y0*dv y0=0 local cy0=ceil(y0) -- sub-pix shift local sy=cy0-y0 x0+=sy*dx u0+=sy*du v0+=sy*dv for y=cy0,min(ceil(y1)-1,127) do -- open span? local span=spans[y] if span then local a,au,av,b,bu,bv=span.x,span.u,span.v,x0,u0,v0 if(a>b) a,au,av,b,bu,bv=b,bu,bv,a,au,av local ca,cb,dab=ceil(a),ceil(b)-1,b-a local sa,dau,dav=ca-a,(bu-au)/dab,(bv-av)/dab -- sub-pix shift if ca<=cb then tline(ca,y,cb,y,au+sa*dau,av+sa*dav,dau,dav) --pset(ca,y,11) --pset(ceil(b)-1,y,11) end else spans[y]={x=x0,u=u0,v=v0} end x0+=dx u0+=du v0+=dv end x0,y0,u0,v0=_x1,_y1,_u1,_v1 end end |
fantastic – thank you!
this version requires that the sprites are copied into map data, correct? otherwise tline can’t work
[Please log in to post a comment]