How I make Brutal Pico Race – First part

Brutal pico race uses an hybrid rendering system, combining differents technics for road an ships rendering. On the fly vertices computing and triangle rasterizer based on old school racing games for the road and voxel based ships.

Here is the first article about How I Make Brutal Pico Race. I try to give some technicals insight which are the result of a fight between a game idea and Pico 8 possibilities (or constraints).

Next time I’ll talk about the ships, and how I integrate them into the road system.

The idea

Inspiration

I had the idea of Brutal pico race after my subconscient combine differents things I saw in differents places. The first piece came from the running man from Neo Tokyo (not the movie with Arnold Schwarzenegger). The Lorn title Sega Sunset fits well to this short film.

Work emerging from tweeter and other places

At this time, I was looking for a tool to give my children an idea of what is computer programming. I had pico 8 with a voxatron package, I though it was a good start ! But finally, I don’t think they are as interested as me ! But by learning about pico 8 I found very interesting projects and a great community and I got trapped !

If I had to keep only 3 projects, I would talk about Zepton and Alone in pico 8 with his very detailled postmortem and Lands of Yocta. I was stunned ! We can do that with pico 8 ?

So, months after discovering the running man from Neo Tokyo, and after seeing some pico8 projects, I started to demake this race. First step, the road !

The road

Brutal pico race, first IA test
Brutal pico race, the road…

Data

The road is an array of road blocks. Each road block has its own properties. To draw the road, I use x,y,z coordonates, fx for curve effect, c for color and t for the road type. The x coordonate was used in the firsts prototypes, but it has been replaced by the fx curve effect. I guess I could save some little tokens here ! So fx property is used to simulate curves, like old-school racing games, before real 3D.  And, that’s all for the road. In Brutal Pico Race, a road block is 64 units long, that’s an arbitrary choice. All this information are handled in the “init_all_road” function.

So basically, a race track is a straight line, looping.

Hybrid rendering

The track is rendered from the the farthest to the closest road block compared to a camera, with weak perspective projection. This way, a 3D world is projected on a 2D screen.

projected x = 3D X/ 3D Z
projected y = 3D Y/ 3D Z

I did not use matrix projection because it have a cost. I try to code only what is necessary. Also, there is no rotation for the track.  We project in 2D the 4 “vertices” of each road block. At this point we have coordonates for a 2D polygon. It is drawn with a rasterizer which fills triangle. I ended up with the 163 tokens one from @p01 which is fast and light.

By applying a cos or sin function to X and Y world coordonates, and making the camera translate on Z, we obtain the following result (Note the little CPU cost) :

Below there is an excerpt focusing on road drawing block by block. I carefully make each vertex to be computed once. So for each road block, only 2 vertices are computed. The first iteration of the loop doesn’t draw anything, it has to wait for the 2nd iteration to have 4 vertices.

function drawroad(cam,ird,p)
 -- calculating road
 local r,rn,v,vn,vz=
  nil,nil--render info
 -- first partial offset
 local irdn=ird+1
 local irdlast=ird+nb_rb-1
 
 -- ready to draw road
 for i=irdlast,ird-1,-1 do
  v,vn=road[i%#road+1],
   road[(i+1)%#road+1]
  vz,xl,xr,iy,ylgt=
   v.z+flr(i/#road)*trklen,
   v.x-56,v.x+56,v.y,
   lerp(v.y,vn.y,0.5)
  -- clip
  dz=max(vz-cam.z,7)

  prc=(cam.z+7-vz)/rdz
  -- clipping
  if dz==7 then
   --interpolation
   xl=lerp(xl,vn.x-56,prc)
   xr=lerp(xr,vn.x+56,prc)
   iy=lerp(v.y,vn.y,prc)
  end
  -- current part
  fct=scale/dz

  r={
   x1=cx+fct*(xl-cam.x),
   x2=cx+fct*(xr-cam.x),
   y=cy-fct*(iy-cam.y),
  }
  -- drawing road
  if rn~=nil then 
   -- drawing triangles
   otri(r.x1,r.y,r.x2,r.y,rn.x1,rn.y,v.c)
   otri(rn.x1,rn.y,rn.x2,rn.y,r.x2,r.y,v.c)
  end
  -- next
  rn=r
 end
end

Half-pipe system

So, now that we can draw a straigth road, with no rotation but with a potentially different altitude for each road block, and that we are not constraint by shapes thanks to the polygon filling, we can imagine a way to enhanced the road. I had in mind that the road must be like a half pipe, to make the ships climb on the road when it turns. It is now possible ! One road block is composed of 6 parts, each parts with a different altitude. All the vertices are computed from the middle of the road (x=0,y=0,z=z road block), in the world coordonate system by changing x and y value for each vertex. Then those new vertices are projected on screen and here is the magic.

Then I wonder how many of these road block can pico8 draw ? So I made a full halfpipe to see how it is handled, and it perform well for 30 FPS. As I was aiming for splitscreen multiplayer, having the CPU under 50% was a good news, even if I also knew that I wasn’t drawing all I need for the race.

Resources :

Leave a Reply

Your email address will not be published. Required fields are marked *