This morning I woke up inspired by this blog post about how to make a pseudo-3D racing game in the style of the arcade classic Out Run.
So... long story short, here's what I came up with:
The road and terrain scrolls by like you're going fast, with curves all set by a predefined course. And it's only about 170 lines of code!
rh = 2 // raster height, in real-screen pixels
// create a couple of 1-pixel-high road images,
// with and without lane stripes
w = 600 // full width of road sprite
gfx.line 0, 0, w, 0, "#666666"
gfx.line 0, 0, 20, 0, color.white
gfx.line w-20, 0, w, 0, color.white
roadImg = []
roadImg.push gfx.getImage(0, 0, w, 1)
gfx.line w*0.33-10, 0, w*0.33+10, 0, "#EEEEEE"
gfx.line w*0.66-10, 0, w*0.66+10, 0, "#EEEEEE"
roadImg.push gfx.getImage(0, 0, w, 1)
// And same for grass. But for this one, we'll just use
// a 4x4 pixel image and stretch it.
gfx.fillRect 0, 0, 4, rh, color.green
grassImg = gfx.getImage(0, 0, 4, rh)
drawBackground = function()
img = http.get("https://i.pinimg.com/originals/e8/70/1c/e8701c982aaf5a6f9a93aa17a2cffd90.jpg")
globals.bgWidth = img.width/3
gfx.clear color.blue, img.width, 640
gfx.drawImage img, 0, 320-120
end function
clear
drawBackground
// now create a sprite for each row of the screen
sprDisp = display(4)
sprDisp.clear
grass = []
road = []
rasterZ = [] // distance (0-1) of each raster line from the camera
rasterScale = [] // scale of sprites at each raster line
rasterFog = [] // amount of fog to apply at each raster line
setup = function()
maxi = 320/rh
for i in range(0, maxi)
camHeight = 10
z = camHeight / (320/rh+5 - i)
rasterZ.push z
rasterScale.push (1/z) / (1/rasterZ[0])
if i > maxi/2 then
rasterFog.push (i-maxi/2) / (maxi/2)
else
rasterFog.push 0
end if
// grass
sp = new Sprite
sp.image = grassImg
sp.scale = [1024 / grassImg.width, 1]
sp.x = 480
sp.y = i*rh
grass.push sp
sprDisp.sprites.push sp
// road
sp = new Sprite
sp.image = roadImg[0]
sp.scale = rh
sp.x = 480
sp.y = i*rh
sp.scale = [1.5 * rasterScale[-1], rh]
sprDisp.sprites.push sp
road.push sp
end for
end function
setup
// Update the road and grass sprites along our raster lines.
tintColors = [color.white, "#DDDDDD"] // light/dark tint
fogColor = "#EEEEEE" // fog in the distance
tintLen = 0.1 // how far (in Z) our tint stripes go
stripeLen = 0.06 // how far (in Z) the lane stripes go
updateRoadDisplay = function(zOffset)
x = 480 // X position of the road, on screen
dx = 0 // change in x per raster line
for i in road.indexes
// calculate total Z along the road
z = zOffset + rasterZ[i]
// update road position
road[i].x = x
x = x + dx
dx = dx + segmentCurveRates[i > nextSegY]
// apply tint and fog
c = tintColors[z % (tintLen*2) > tintLen]
if rasterFog[i] then
road[i].tint = color.lerp(c, fogColor, rasterFog[i])
else
road[i].tint = c
end if
grass[i].tint = road[i].tint
// update sprite (for lane stripes)
strip = z % (stripeLen*2) > stripeLen
road[i].image = roadImg[strip]
end for
end function
// Scroll the background left/right as we go around curves.
updateBackground = function()
globals.heading = heading + segmentCurveRates[0] * 100
x = heading % bgWidth
if x < 0 then x = x + bgWidth
gfx.scrollX = x
end function
// Frames per second (fps) counter
fpsStart = time
fpsFrameCount = 0
updateFpsCounter = function()
now = time
globals.fpsFrameCount = fpsFrameCount + 1
if now - fpsStart > 1 then
fps = fpsFrameCount / (now - fpsStart)
text.row = 25; text.column = 59
print "FPS: " + round(fps) + " "
globals.fpsStart = now
globals.fpsFrameCount = 0
end if
end function
// define the track as a series of segments;
// for each one, store the curve (+1 = hard right, -1 = hard left)
trackSegments = [0, 0.5, -0.5, 0, 1, 1, 0, 0, 0, -0.5, -1, -1, -0.5]
curveRate = 0.04
segmentCurveRates = [0,0]
heading = 0
// start the next track segment, updating all relevant
// globals (especially segmentCurveRates, which determines
// the current and next curvature of the road)
startTrackSegment = function(n)
globals.nextSegY = rasterZ.len - 1 // position of next road segment on screen
globals.curSeg = n
nextSeg = (curSeg + 1 + trackSegments.len) % trackSegments.len
segmentCurveRates[0] = trackSegments[curSeg] * curveRate
segmentCurveRates[1] = trackSegments[nextSeg] * curveRate
text.row = 25; print "Track Segment: " + n + " "
end function
startTrackSegment 0
startNextTrackSegment = function()
startTrackSegment (curSeg + 1 + trackSegments.len) % trackSegments.len
end function
// Initial state
bottomTint = 0
stripLeft = 10
totalDistAlongTrack = 0
speed = 0.01
// Main update function
update = function()
updateRoadDisplay totalDistAlongTrack
updateBackground
globals.totalDistAlongTrack = totalDistAlongTrack + speed
globals.nextSegY = nextSegY - 200/rh * speed
if nextSegY < 0 then startNextTrackSegment
updateFpsCounter
end function
// Main loop
while true
update
yield
end while
It's fast, too — if you take out the yield
statement in the main loop, it will run at hundreds of frames per second.
Could be a good start for a racing game... feel free to take it and make something cool!