Log In  


Cart #spritestack-0 | 2020-07-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
35

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!

35


@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+40
sin(angle), -- direction
-angle-0.25) -- rotation angle

Ie, negate the angle and remove 90 degrees. :D

Oh boy. #honestGameDev


1

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.


1

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?


or just use pico CAD


picocad is 3D modelling, not the same technique


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...

Cart #spriterotate-0 | 2023-05-04 | Code ▽ | Embed ▽ | No License


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.

Cart #kisubesusu-0 | 2023-05-05 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


@johanp, could you share your updated code that uses tline?
(seen used in a gif you posted on twitter)


1

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


Exactly, just pass the tile coordinates into rspr2



[Please log in to post a comment]