I'm converting Jakes Gorden Pseudo 3D track example programs to GLBasic. This is the first part (just straight roads).
Having to convert from Javascript to a more readable text can be a bit of problem!
Here's the current code :
// --------------------------------- //
// Project: Test2
// Start: Tuesday, April 01, 2014
// IDE Version: 12.001
CONSTANT PLAYER_STRAIGHT% = 0
CONSTANT PLAYER_LEFT% = 1
CONSTANT PLAYER_RIGHT% = 2
CONSTANT PLAYER_UPHILL_STRAIGHT% = 3
CONSTANT PLAYER_UPHILL_LEFT% = 4
CONSTANT PLAYER_UPHILL_RIGHT% = 5
TYPE tScreen
scale
x
y
w
ENDTYPE
TYPE tCamera
x
y
z
ENDTYPE
TYPE tWorld
x
y
z
ENDTYPE
TYPE tColour
rumble%
road%
grass%
lane%
ENDTYPE
TYPE tPart
world AS tWorld
camera AS tCamera
screen AS tScreen
ENDTYPE
TYPE tSegment
index%
colour AS tColour
p1 AS tPart
p2 AS tPart
ENDTYPE
TYPE TRoad
segments[] AS tSegment
LIGHT AS tColour
DARK AS tColour
SPRITES_SCALE
fps = 75.0
fpsStep = 0.0
segmentLength% = 200 // Length of a single segment
rumbleLength% = 3 // Number of segments per red/white rumble strip
roadWidth% = 2000 // Actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
cameraHeight% = 1000 // Z height of camera
cameraDepth = 0
position = 0.0 // Current camera Z position
playerX = 0 // Player x offset from center of road (-1 to 1 to stay independent of roadWidth)
playerZ = 0 // Player relative z distance from camera (computed)
lanes% = 3 // Number of lanes
fieldOfView% = 100 // angle (degrees) for field of view
speed = 0.0 // current speed
maxSpeed = 0 // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
accel = 0 // acceleration rate - tuned until it 'felt' right
breaking = 0 // deceleration rate when braking
decel = 0 // 'natural' deceleration rate when neither accelerating, nor braking
offRoadDecel = 0 // off road deceleration is somewhere in between
offRoadLimit = 0 // limit when off road deceleration no longer applies (e.g. you can always go at least t
resolution = 0.0
width%;height%
keyLeftRight%
trackLength%
drawDistance% = 100
FUNCTION Initialise%:
LOCAL tW%,tH%
GETSCREENSIZE self.width%,self.height%
DEBUG "H:"+self.height%+"\n"
self.resetRoad()
self.position=0.0
self.cameraDepth=1.0 / TAN((self.fieldOfView%/2)) // * 3.141592653/180.0)
DEBUG "Camera depth : "+self.cameraDepth+" "+self.fieldOfView%+"\n"
self.LIGHT.road%=7039851
self.LIGHT.grass%=1092112
self.LIGHT.rumble%=5592405
self.LIGHT.lane%=13421772
self.DARK.road%=6908265
self.DARK.grass%=39424
self.DARK.rumble%=12303291
self.DARK.lane%=0
self.fpsStep=1.0/self.fps
self.maxSpeed=self.segmentLength/self.fpsStep
self.accel = self.maxSpeed/5.0
self.breaking = -self.maxSpeed
self.decel = -self.maxSpeed/5.0
self.offRoadDecel = -self.maxSpeed/2.0
self.offRoadLimit = self.maxSpeed/4.0
self.speed=0.0
self.playerZ=self.cameraHeight * self.cameraDepth
self.resolution=self.height%/640.0
self.keyLeftRight%=0
GETSPRITESIZE PLAYER_STRAIGHT%,tW%,tH%
self.SPRITES_SCALE= 0.3 * (1.0/tW%)
ENDFUNCTION
FUNCTION resetRoad%:
LOCAL segment AS tSegment
DIM self.segments[0]
FOR n%=0 TO 499
segment.index%=n
segment.p1.world.x=0.0
segment.p1.world.y=0.0
segment.p1.world.z=n*self.segmentLength%
//segment.p1.camera
segment.p2.world.x=0.0
segment.p2.world.y=0.0
segment.p2.world.z=(n+1)*self.segmentLength%
IF MOD(FLOOR(n%/self.rumbleLength%),2)=0
segment.colour=self.LIGHT
ELSE
segment.colour=self.DARK
ENDIF
//segment.colour=IIF(MOD(FLOOR(n%/self.rumbleLength%),2),self.LIGHT,self.DARK)
DIMPUSH self.segments[],segment
NEXT
self.trackLength% = BOUNDS(self.segments[],0)*self.segmentLength%
ENDFUNCTION
FUNCTION findSegment AS tSegment:z
RETURN self.segments[MOD(FLOOR(z/self.segmentLength%),BOUNDS(self.segments[],0))]
ENDFUNCTION
FUNCTION increase: start,increment,maxV
LOCAL result
result=start+increment
WHILE result>=maxV
DEC result,maxV
WEND
WHILE result<0
INC result,maxV
WEND
RETURN result
ENDFUNCTION
FUNCTION accelerate:v,accel,dt
RETURN v + (accel * dt)
ENDFUNCTION
FUNCTION limit:value, minV,maxV
RETURN MAX(minV,MIN(value,maxV))
ENDFUNCTION
FUNCTION fog%:x%,y%,width%,height%
ALPHAMODE -0.9
DRAWRECT x%,y%,width%,height%,RGB(64,64,64)
ALPHAMODE -1.0
ENDFUNCTION
FUNCTION update%:dt
LOCAL dx
DEBUG dt+"\n"
DEBUG "Speed:"+self.speed+"\n"
DEBUG (dt*self.speed)+"\n"
self.position = self.increase(self.position, dt * self.speed, self.trackLength)
dx = dt * 2 * (self.speed/self.maxSpeed); // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
IF KEY(203)
DEC self.playerX,dx
self.keyLeftRight%=-1
ELSEIF KEY(205)
INC self.playerX,dx
self.keyLeftRight%=1
ELSE
self.keyLeftRight%=0
ENDIF
IF KEY(200)
self.speed=self.accelerate(self.speed,self.accel,dt)
ELSEIF KEY(208)
self.speed=self.accelerate(self.speed,self.breaking,dt)
ELSE
self.speed=self.accelerate(self.speed,self.decel,dt)
ENDIF
IF (self.playerX<-1.0 OR self.playerX>1.0) AND (self.speed>self.offRoadLimit)
self.speed=self.accelerate(self.speed,self.offRoadDecel,dt)
ENDIF
self.playerX=self.limit(self.playerX,-2,2)
self.speed=self.limit(self.speed,0.0,self.maxSpeed)
ENDFUNCTION
FUNCTION project%:p AS tPart, cameraX, cameraY, cameraZ, cameraDepth, width%, height%, roadWidth%
p.camera.x=p.world.x - cameraX
p.camera.y=p.world.y - cameraY
p.camera.z=p.world.z - cameraZ
p.screen.scale=self.cameraDepth/p.camera.z
// DEBUG "Z : "+p.camera.z+" "+cameraZ+"\n"
// DEBUG "Y : "+p.camera.y+" "+cameraY+"\n"
// DEBUG "X : "+p.camera.x+" "+cameraX+"\n"
// DEBUG "Camera depth : "+cameraDepth+" p.camera.z : "+p.camera.z+"\n"
// DEBUG "Scale : "+p.screen.scale+"\n"
// DEBUG "Width : "+width%+" Height : "+height%+"\n"
// DEBUG "Road width : "+roadWidth%+"\n"
p.screen.x=INTEGER((width%/2)+(p.screen.scale*p.camera.x*width%/2))
p.screen.y=INTEGER((height%/2)-(p.screen.scale*p.camera.y*height%/2))
p.screen.w=INTEGER(p.screen.scale*roadWidth%*width%/2)
// DEBUG p.screen.x+" "+p.screen.y+" "+p.screen.w+"\n"
//KEYWAIT
ENDFUNCTION
FUNCTION player%:width%, height%, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown
LOCAL bounce
LOCAL sprite%
bounce=1.5*(RND(2)-1)*speedPercent*resolution
IF steer<0
sprite%=IIF(updown>0,PLAYER_UPHILL_LEFT%,PLAYER_LEFT%)
ELSEIF steer>0
sprite%=IIF(updown>0,PLAYER_UPHILL_RIGHT%,PLAYER_RIGHT%)
ELSE
sprite%=IIF(updown>0,PLAYER_UPHILL_STRAIGHT%,PLAYER_STRAIGHT%)
ENDIF
DEBUG "SP:"+sprite%+"\n"
self.sprite(width%,height%,resolution,roadWidth,sprite%,scale,destX,destY+bounce,-0.5,-1)
ENDFUNCTION
FUNCTION sprite%:width%, height%, resolution, roadWidth, sprite%, scale, destX, destY, offsetX, offsetY, clipY=0.0
LOCAL destW,destH,clipH
GETSPRITESIZE sprite%,destW,destH
destW=destW*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
destH=destH*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
DEBUG "Scale : "+self.SPRITES_SCALE+" Width : "+roadWidth+"\n"
DEBUG "W:"+destW+" "+destH+"\n"
INC destX,destW*offsetX
INC destY,destH*offsetY
clipH=IIF(clipY,MAX(0,destY+destH-clipY),0)
IF clipH<destH
STRETCHSPRITE sprite%,destX,destY,destW,destH-clipH
ENDIF
ENDFUNCTION
FUNCTION render%:
LOCAL baseSegment AS tSegment
LOCAL maxy%,n%
LOCAL segment AS tSegment
// DEBUG "H2:"+self.height%+"\n"
maxy%=self.height%
// DEBUG "maxy : "+maxy%+"\n"
DEBUG "Position : "+self.position+"\n"
baseSegment = self.findSegment(self.position)
FOR n%=0 TO self.drawDistance%-1
segment=self.segments[MOD(baseSegment.index%+n%,BOUNDS(self.segments[],0))]
self.project(segment.p1,self.playerX*self.roadWidth%,self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
self.project(segment.p2,self.playerX*self.roadWidth%,self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
// DEBUG "Segment 1 camera Z : "+segment.p1.camera.z+"\n"
// DEBUG "Segment 2 screen Y : "+segment.p2.screen.y+"\n"
// DEBUG "maxy : "+maxy%+"\n"
IF segment.p1.camera.z<=self.cameraDepth OR segment.p2.screen.y>=maxy%
CONTINUE
ELSE
self.segment(self.width%,self.lanes%, _
segment.p1.screen.x, _
segment.p1.screen.y, _
segment.p1.screen.w, _
segment.p2.screen.x, _
segment.p2.screen.y, _
segment.p2.screen.w, _
1.0, _
segment.colour)
ENDIF
maxy%=segment.p2.screen.y%
NEXT
self.player(self.width%, self.height%, self.resolution, self.roadWidth% ,PLAYER_STRAIGHT%,self.speed/self.maxSpeed, _
self.cameraDepth/self.playerZ, _
self.width%/2, _
self.height, _
self.speed * SGN(self.keyLeftRight%), _
0)
// self.cameraDepth/self.playerZ, _
// self.width%/2, _
// self.height, _
// self.speed * 1, _ // (keyLeft ? -1 : keyRight ? 1 : 0),
// 0)
ENDFUNCTION
FUNCTION rumbleWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth%/MAX(6,2*lanes%)
ENDFUNCTION
FUNCTION laneMarkerWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth/MAX(32,8*lanes%)
ENDFUNCTION
FUNCTION polygon%:x1,y1,x2,y2,x3,y3,x4,y4,colour%
STARTPOLY 99,0
POLYVECTOR x1,y1,x2,y2,colour%
POLYVECTOR x2,y2,x3,y3,colour%
POLYVECTOR x3,y3,x4,y4,colour%
POLYVECTOR x4,y4,x1,y2,colour%
ENDPOLY
// DRAWRECT x1,y1,x2,y2,colour%
// DRAWRECT x3,y3,x4,y4,colour%
//DRAWLINE x1,y1,x2,y2,colour%
//DRAWLINE x3,y3,x4,y4,colour%
ENDFUNCTION
FUNCTION segment%:width%,lanes%,x1,y1,w1,x2,y2,w2,fog,colour AS tColour
LOCAL r1,r2,l1,l2,lanew1,lanew2,lanex1,lanex2
r1=self.rumbleWidth(w1,lanes%)
r2=self.rumbleWidth(w2,lanes%)
l1=self.laneMarkerWidth(w1,lanes%)
l2=self.laneMarkerWidth(w2,lanes%)
//DEBUG "R1 : "+r1%+" R2 : "+r2%+" l1 : "+l1%+" l2 : "+l2%+"\n"
ALPHAMODE -1.0
DRAWRECT 0,y2,width%,y1-y2,colour.grass%
self.polygon(x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, colour.rumble)
self.polygon(x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, colour.rumble)
self.polygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, colour.road)
IF colour.lane%
lanew1=w1*2.0/lanes%
lanew2=w2*2.0/lanes%
lanex1=x1-w1+lanew1
lanex2=x2-w2+lanew2
FOR lane%=1 TO lanes%-1
self.polygon(lanex1-l1/2.0,y1,lanex1+l1/2.0,y1,lanex2+l2/2.0,y2,lanex2-l2/2.0,y2,colour.lane%)
INC lanex1,lanew1
INC lanex2,lanew2
NEXT
ENDIF
//self.fog(0,y1,width%,y2-y1)
ENDFUNCTION
ENDTYPE
LOCAL road AS TRoad
LOCAL now,last,dt,gdt,stp=0.025
LIMITFPS -1
LOADSPRITE "Media/player_straight.png",PLAYER_STRAIGHT%
LOADSPRITE "Media/player_left.png",PLAYER_LEFT%
LOADSPRITE "Media/player_right.png",PLAYER_RIGHT%
LOADSPRITE "Media/player_uphill_straight.png",PLAYER_UPHILL_STRAIGHT%
LOADSPRITE "Media/player_uphill_left.png",PLAYER_UPHILL_LEFT%
LOADSPRITE "Media/player_uphill_right.png",PLAYER_UPHILL_RIGHT%
road.Initialise()
road.resetRoad()
now=GETTIMERALL()
last=GETTIMERALL()
WHILE TRUE
now=GETTIMERALL()
DEBUG last+" "+now+"\n"
dt=MIN(1.0,(now-last)/1000.0)
INC gdt,dt
WHILE gdt>stp
DEC gdt,stp
road.update(stp)
WEND
road.update(stp)
SMOOTHSHADING FALSE
road.render()
SHOWSCREEN
last = now
WEND
Nice and fast - perhaps too fast! :)
I created a road system in Div many years ago (in fact it was going to be a remake of OutRun). It was my last ever Div program. Unfortunately it's all gone now due to numerous computer deaths and updates. :( I've probably got the source on a CD somewhere, but I just haven't got the time or the will to find it.
I do often think about creating a 2D racer, to harken back to the old days (especially since Foppy released a motorbike racer on OUYA that got rave reviews, despite it's limited 1 track game mode). But then I remember I might still have the code somewhere and think sod it!
Looking forward to seeing the next part(s). :)
And if by magic, the next part is here.
One problem I did have was working out an acceptable easing value as Javascript uses radians.
But after a bit of experimenting, I found an acceptable value, although it's not quite the same as the original.
It is probably way too fast still - will have to look at that later.
// --------------------------------- //
// Project: Test2
// Start: Tuesday, April 01, 2014
// IDE Version: 12.001
CONSTANT PLAYER_STRAIGHT% = 0
CONSTANT PLAYER_LEFT% = 1
CONSTANT PLAYER_RIGHT% = 2
CONSTANT PLAYER_UPHILL_STRAIGHT% = 3
CONSTANT PLAYER_UPHILL_LEFT% = 4
CONSTANT PLAYER_UPHILL_RIGHT% = 5
CONSTANT ROAD_LENGTH_NONE% = 0
CONSTANT ROAD_LENGTH_SHORT% = 25
CONSTANT ROAD_LENGTH_MEDIUM% = 50
CONSTANT ROAD_LENGTH_LONG% = 100
CONSTANT ROAD_CURVE_NONE% = 0
CONSTANT ROAD_CURVE_EASY% = 2
CONSTANT ROAD_CURVE_MEDIUM% = 4
CONSTANT ROAD_CURVE_HARD% = 6
CONSTANT PI = 3.14159
TYPE tScreen
scale
x
y
w
ENDTYPE
TYPE tCamera
x
y
z
ENDTYPE
TYPE tWorld
x
y
z
ENDTYPE
TYPE tColour
rumble%
road%
grass%
lane%
ENDTYPE
TYPE tPart
world AS tWorld
camera AS tCamera
screen AS tScreen
ENDTYPE
TYPE tSegment
index%
colour AS tColour
p1 AS tPart
p2 AS tPart
curve
ENDTYPE
TYPE TRoad
segments[] AS tSegment
LIGHT AS tColour
DARK AS tColour
SPRITES_SCALE
fps = 75.0
fpsStep = 0.0
segmentLength% = 200 // Length of a single segment
rumbleLength% = 3 // Number of segments per red/white rumble strip
roadWidth% = 2000 // Actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
cameraHeight% = 1000 // Z height of camera
cameraDepth = 0
position = 0.0 // Current camera Z position
playerX = 0 // Player x offset from center of road (-1 to 1 to stay independent of roadWidth)
playerZ = 0 // Player relative z distance from camera (computed)
lanes% = 3 // Number of lanes
fieldOfView% = 100 // angle (degrees) for field of view
speed = 0.0 // current speed
maxSpeed = 0 // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
accel = 0 // acceleration rate - tuned until it 'felt' right
breaking = 0 // deceleration rate when braking
decel = 0 // 'natural' deceleration rate when neither accelerating, nor braking
offRoadDecel = 0 // off road deceleration is somewhere in between
offRoadLimit = 0 // limit when off road deceleration no longer applies (e.g. you can always go at least t
resolution = 0.0
width%;height%
keyLeftRight%
trackLength%
drawDistance% = 100
FUNCTION Initialise%:
LOCAL tW%,tH%
GETSCREENSIZE self.width%,self.height%
DEBUG "H:"+self.height%+"\n"
self.resetRoad()
self.position=0.0
self.cameraDepth=1.0 / TAN((self.fieldOfView%/2)) // * 3.141592653/180.0)
DEBUG "Camera depth : "+self.cameraDepth+" "+self.fieldOfView%+"\n"
self.LIGHT.road%=7039851
self.LIGHT.grass%=1092112
self.LIGHT.rumble%=5592405
self.LIGHT.lane%=13421772
self.DARK.road%=6908265
self.DARK.grass%=39424
self.DARK.rumble%=12303291
self.DARK.lane%=0
self.fpsStep=1.0/self.fps
self.maxSpeed=self.segmentLength/self.fpsStep
self.accel = self.maxSpeed/5.0
self.breaking = -self.maxSpeed
self.decel = -self.maxSpeed/5.0
self.offRoadDecel = -self.maxSpeed/2.0
self.offRoadLimit = self.maxSpeed/4.0
self.speed=0.0
self.playerZ=self.cameraHeight * self.cameraDepth
self.resolution=self.height%/640.0
self.keyLeftRight%=0
GETSPRITESIZE PLAYER_STRAIGHT%,tW%,tH%
self.SPRITES_SCALE= 0.3 * (1.0/tW%)
ENDFUNCTION
FUNCTION addSegment%:curve
LOCAL size%
LOCAL segment AS tSegment
size%=BOUNDS(self.segments[],0)
// DEBUG "Curve : "+curve+"\n"
segment.index%=size%
segment.curve=curve
segment.p1.world.x=0.0
segment.p1.world.y=0.0
segment.p1.world.z=size%*self.segmentLength%
segment.p1.camera.x=0.0
segment.p1.camera.y=0.0
segment.p1.camera.z=0.0
segment.p1.screen.x=0.0
segment.p1.screen.y=0.0
segment.p1.screen.w=0.0
segment.p1.screen.scale=0.0
segment.p2.world.x=0.0
segment.p2.world.y=0.0
segment.p2.world.z=(size%+1)*self.segmentLength%
segment.p2.camera.x=0.0
segment.p2.camera.y=0.0
segment.p2.camera.z=0.0
segment.p2.screen.x=0.0
segment.p2.screen.y=0.0
segment.p2.screen.w=0.0
segment.p2.screen.scale=0.0
IF FLOOR(MOD(size%/self.rumbleLength%,2))
segment.colour.road%=RGB(64,64,64)
segment.colour.grass%=RGB(0,64,0)
segment.colour.rumble%=RGB(128,128,128)
ELSE
segment.colour.road%=RGB(200,200,200)
segment.colour.grass%=RGB(0,200,0)
segment.colour.rumble%=RGB(220,220,220)
ENDIF
segment.colour.lane%=RGB(255,255,0)
DIMPUSH self.segments[],segment
ENDFUNCTION
FUNCTION addRoad%:enter, hold, leave, curve
LOCAL n%
FOR n%=0 TO enter-1;
self.addSegment(self.easeIn(0,curve,n%/enter));
NEXT
FOR n%=0 TO hold-1; self.addSegment(curve); NEXT
FOR n%=0 TO leave-1; self.addSegment(self.easeInOut(curve,0,n%/leave)); NEXT
ENDFUNCTION
FUNCTION addStraight%:num=25
self.addRoad(num,num,num,0)
ENDFUNCTION
FUNCTION addCurve%:num, curve
self.addRoad(num,num,num,curve)
ENDFUNCTION
FUNCTION addSCurves%:
self.addRoad(ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, -ROAD_CURVE_EASY%)
self.addRoad(ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_CURVE_MEDIUM%)
// self.addRoad(ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_CURVE_EASY%)
// self.addRoad(ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, -ROAD_CURVE_EASY%)
// self.addRoad(ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, ROAD_LENGTH_MEDIUM%, -ROAD_CURVE_MEDIUM%)
ENDFUNCTION
FUNCTION resetRoad%:
LOCAL segment AS tSegment
DIM self.segments[0]
self.addStraight(ROAD_LENGTH_SHORT%/4.0)
self.addSCurves()
self.addStraight(ROAD_LENGTH_LONG%)
self.addCurve(ROAD_LENGTH_MEDIUM%, ROAD_CURVE_MEDIUM%)
self.addCurve(ROAD_LENGTH_LONG%, ROAD_CURVE_MEDIUM%)
self.addStraight()
self.addSCurves()
self.addCurve(ROAD_LENGTH_LONG%, -ROAD_CURVE_MEDIUM%)
self.addCurve(ROAD_LENGTH_LONG%, ROAD_CURVE_MEDIUM%)
self.addStraight()
self.addSCurves()
self.addCurve(ROAD_LENGTH_LONG%, -ROAD_CURVE_EASY%)
segment=self.findSegment(self.playerZ)
self.segments[segment.index% + 2].colour.road% = RGB(255,255,255)
self.segments[segment.index% + 2].colour.lane% = RGB(255,255,255)
self.segments[segment.index% + 3].colour.road% = RGB(255,255,255)
self.segments[segment.index% + 3].colour.lane% = RGB(255,255,255)
FOR n%=0 TO self.rumbleLength%-1
self.segments[BOUNDS(self.segments[],0)-1-n%].colour.road% = RGB(255,255,255)
self.segments[BOUNDS(self.segments[],0)-1-n%].colour.lane% = RGB(255,255,255)
NEXT
self.trackLength% = BOUNDS(self.segments[],0)*self.segmentLength%
ENDFUNCTION
FUNCTION findSegment AS tSegment:z
RETURN self.segments[MOD(FLOOR(z/self.segmentLength%),BOUNDS(self.segments[],0))]
ENDFUNCTION
FUNCTION increase: start,increment,maxV
LOCAL result
result=start+increment
WHILE result>=maxV
DEC result,maxV
WEND
WHILE result<0
INC result,maxV
WEND
RETURN result
ENDFUNCTION
FUNCTION accelerate:v,accel,dt
RETURN v + (accel * dt)
ENDFUNCTION
FUNCTION limit:value, minV,maxV
RETURN MAX(minV,MIN(value,maxV))
ENDFUNCTION
FUNCTION easeIn:a,b,percent
RETURN a + (b-a)*POW(percent,2)
ENDFUNCTION
FUNCTION easeOut:a,b,percent
RETURN a + (b-a)*(1.0-POW(1-percent,2))
ENDFUNCTION
FUNCTION easeInOut:a,b,percent
RETURN a + (b-a)*((-COS(percent*180.0)/2.0) + 0.5)
ENDFUNCTION
FUNCTION percentageRemaining:n,total
RETURN MOD(n,total)/total
ENDFUNCTION
FUNCTION fog%:x%,y%,width%,height%
ALPHAMODE -0.9
DRAWRECT x%,y%,width%,height%,RGB(64,64,64)
ALPHAMODE -1.0
ENDFUNCTION
FUNCTION update%:dt
LOCAL dx
// DEBUG dt+"\n"
// DEBUG "Speed:"+self.speed+"\n"
// DEBUG (dt*self.speed)+"\n"
self.position = self.increase(self.position, dt * self.speed, self.trackLength)
dx = dt * 2 * (self.speed/self.maxSpeed); // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
IF KEY(203)
DEC self.playerX,dx
self.keyLeftRight%=-1
ELSEIF KEY(205)
INC self.playerX,dx
self.keyLeftRight%=1
ELSE
self.keyLeftRight%=0
ENDIF
IF KEY(200)
self.speed=self.accelerate(self.speed,self.accel,dt)
ELSEIF KEY(208)
self.speed=self.accelerate(self.speed,self.breaking,dt)
ELSE
self.speed=self.accelerate(self.speed,self.decel,dt)
ENDIF
IF (self.playerX<-1.0 OR self.playerX>1.0) AND (self.speed>self.offRoadLimit)
self.speed=self.accelerate(self.speed,self.offRoadDecel,dt)
ENDIF
self.playerX=self.limit(self.playerX,-2,2)
self.speed=self.limit(self.speed,0.0,self.maxSpeed)
ENDFUNCTION
FUNCTION project%:p AS tPart, cameraX, cameraY, cameraZ, cameraDepth, width%, height%, roadWidth%
p.camera.x=p.world.x - cameraX
p.camera.y=p.world.y - cameraY
p.camera.z=p.world.z - cameraZ
p.screen.scale=self.cameraDepth/p.camera.z
// DEBUG "Z : "+p.camera.z+" "+cameraZ+"\n"
// DEBUG "Y : "+p.camera.y+" "+cameraY+"\n"
// DEBUG "X : "+p.camera.x+" "+cameraX+"\n"
// DEBUG "Camera depth : "+cameraDepth+" p.camera.z : "+p.camera.z+"\n"
// DEBUG "Scale : "+p.screen.scale+"\n"
// DEBUG "Width : "+width%+" Height : "+height%+"\n"
// DEBUG "Road width : "+roadWidth%+"\n"
p.screen.x=INTEGER((width%/2)+(p.screen.scale*p.camera.x*width%/2))
p.screen.y=INTEGER((height%/2)-(p.screen.scale*p.camera.y*height%/2))
p.screen.w=INTEGER(p.screen.scale*roadWidth%*width%/2)
// DEBUG p.screen.x+" "+p.screen.y+" "+p.screen.w+"\n"
//KEYWAIT
ENDFUNCTION
FUNCTION player%:width%, height%, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown
LOCAL bounce
LOCAL sprite%
bounce=1.5*(RND(2)-1)*speedPercent*resolution
IF steer<0
sprite%=IIF(updown>0,PLAYER_UPHILL_LEFT%,PLAYER_LEFT%)
ELSEIF steer>0
sprite%=IIF(updown>0,PLAYER_UPHILL_RIGHT%,PLAYER_RIGHT%)
ELSE
sprite%=IIF(updown>0,PLAYER_UPHILL_STRAIGHT%,PLAYER_STRAIGHT%)
ENDIF
DEBUG "SP:"+sprite%+"\n"
self.sprite(width%,height%,resolution,roadWidth,sprite%,scale,destX,destY+bounce,-0.5,-1)
ENDFUNCTION
FUNCTION sprite%:width%, height%, resolution, roadWidth, sprite%, scale, destX, destY, offsetX, offsetY, clipY=0.0
LOCAL destW,destH,clipH
GETSPRITESIZE sprite%,destW,destH
destW=destW*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
destH=destH*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
DEBUG "Scale : "+self.SPRITES_SCALE+" Width : "+roadWidth+"\n"
DEBUG "W:"+destW+" "+destH+"\n"
INC destX,destW*offsetX
INC destY,destH*offsetY
clipH=IIF(clipY,MAX(0,destY+destH-clipY),0)
IF clipH<destH
STRETCHSPRITE sprite%,destX,destY,destW,destH-clipH
ENDIF
ENDFUNCTION
FUNCTION render%:
LOCAL baseSegment AS tSegment
LOCAL basePercentage
LOCAL maxy%,n%
LOCAL segment AS tSegment
LOCAL x,dx
// DEBUG "H2:"+self.height%+"\n"
maxy%=self.height%
// DEBUG "maxy : "+maxy%+"\n"
DEBUG "Position : "+self.position+"\n"
baseSegment = self.findSegment(self.position)
basePercentage=self.percentageRemaining(self.position,self.segmentLength%)
x=0.0
dx=-(baseSegment.curve*basePercentage)
PRINT FORMAT$(4,4,dx),0,0
//DEBUG "Base Percentage : "+basePercentage+" DX : "+dx+"\n"
// KEYWAIT
FOR n%=0 TO self.drawDistance%-1
segment=self.segments[MOD(baseSegment.index%+n%,BOUNDS(self.segments[],0))]
self.project(segment.p1,(self.playerX*self.roadWidth%*1.0)-x,self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
self.project(segment.p2,(self.playerX*self.roadWidth%*1.0)-x-dx,self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
// DEBUG "Segment 1 camera Z : "+segment.p1.camera.z+"\n"
// DEBUG "Segment 2 screen Y : "+segment.p2.screen.y+"\n"
// DEBUG "maxy : "+maxy%+"\n"
INC x,dx
INC dx,baseSegment.curve
IF segment.p1.camera.z<=self.cameraDepth OR segment.p2.screen.y>=maxy%
CONTINUE
ELSE
self.segment(self.width%,self.lanes%, _
segment.p1.screen.x, _
segment.p1.screen.y, _
segment.p1.screen.w, _
segment.p2.screen.x, _
segment.p2.screen.y, _
segment.p2.screen.w, _
1.0, _
segment.colour)
ENDIF
maxy%=segment.p2.screen.y%
NEXT
self.player(self.width%, self.height%, self.resolution, self.roadWidth% ,PLAYER_STRAIGHT%,self.speed/self.maxSpeed, _
self.cameraDepth/self.playerZ, _
self.width%/2, _
self.height, _
self.speed * SGN(self.keyLeftRight%), _
0)
// self.cameraDepth/self.playerZ, _
// self.width%/2, _
// self.height, _
// self.speed * 1, _ // (keyLeft ? -1 : keyRight ? 1 : 0),
// 0)
ENDFUNCTION
FUNCTION rumbleWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth%/MAX(6,2*lanes%)
ENDFUNCTION
FUNCTION laneMarkerWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth/MAX(32,8*lanes%)
ENDFUNCTION
FUNCTION polygon%:x1,y1,x2,y2,x3,y3,x4,y4,colour%
STARTPOLY 99,0
POLYVECTOR x1,y1,x2,y2,colour%
POLYVECTOR x2,y2,x3,y3,colour%
POLYVECTOR x3,y3,x4,y4,colour%
POLYVECTOR x4,y4,x1,y2,colour%
ENDPOLY
// DRAWRECT x1,y1,x2,y2,colour%
// DRAWRECT x3,y3,x4,y4,colour%
//DRAWLINE x1,y1,x2,y2,colour%
//DRAWLINE x3,y3,x4,y4,colour%
ENDFUNCTION
FUNCTION segment%:width%,lanes%,x1,y1,w1,x2,y2,w2,fog,colour AS tColour
LOCAL r1,r2,l1,l2,lanew1,lanew2,lanex1,lanex2
r1=self.rumbleWidth(w1,lanes%)
r2=self.rumbleWidth(w2,lanes%)
l1=self.laneMarkerWidth(w1,lanes%)
l2=self.laneMarkerWidth(w2,lanes%)
//DEBUG "R1 : "+r1%+" R2 : "+r2%+" l1 : "+l1%+" l2 : "+l2%+"\n"
ALPHAMODE -1.0
DRAWRECT 0,y2,width%,y1-y2,colour.grass%
self.polygon(x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, colour.rumble)
self.polygon(x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, colour.rumble)
self.polygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, colour.road)
IF colour.lane%
lanew1=w1*2.0/lanes%
lanew2=w2*2.0/lanes%
lanex1=x1-w1+lanew1
lanex2=x2-w2+lanew2
FOR lane%=1 TO lanes%-1
self.polygon(lanex1-l1/2.0,y1,lanex1+l1/2.0,y1,lanex2+l2/2.0,y2,lanex2-l2/2.0,y2,colour.lane%)
INC lanex1,lanew1
INC lanex2,lanew2
NEXT
ENDIF
//self.fog(0,y1,width%,y2-y1)
ENDFUNCTION
ENDTYPE
LOCAL road AS TRoad
LOCAL now,last,dt,gdt,stp=0.025
LIMITFPS -1
LOADSPRITE "Media/player_straight.png",PLAYER_STRAIGHT%
LOADSPRITE "Media/player_left.png",PLAYER_LEFT%
LOADSPRITE "Media/player_right.png",PLAYER_RIGHT%
LOADSPRITE "Media/player_uphill_straight.png",PLAYER_UPHILL_STRAIGHT%
LOADSPRITE "Media/player_uphill_left.png",PLAYER_UPHILL_LEFT%
LOADSPRITE "Media/player_uphill_right.png",PLAYER_UPHILL_RIGHT%
road.Initialise()
road.resetRoad()
now=GETTIMERALL()
last=GETTIMERALL()
WHILE TRUE
now=GETTIMERALL()
DEBUG last+" "+now+"\n"
dt=MIN(1.0,(now-last)/1000.0)
INC gdt,dt
WHILE gdt>stp
DEC gdt,stp
road.update(stp)
WEND
road.update(stp)
SMOOTHSHADING FALSE
road.render()
SHOWSCREEN
last = now
WEND
I've always wanted to do a game like Outrun/Lotus Esprit, but the main problem has been graphics...
Very very cool, and very very usefull,... Thanks a lot for the code....
<3 <3
No problem!
This is the third part : Hills & trough's. I had to sort the easing calculation for this - and worked out why it wasn't working properly : I was multiplying the result of COS by 180/PI rather than the initial value :)
// --------------------------------- //
// Project: Test2
// Start: Tuesday, April 01, 2014
// IDE Version: 12.001
CONSTANT PLAYER_STRAIGHT% = 0
CONSTANT PLAYER_LEFT% = 1
CONSTANT PLAYER_RIGHT% = 2
CONSTANT PLAYER_UPHILL_STRAIGHT% = 3
CONSTANT PLAYER_UPHILL_LEFT% = 4
CONSTANT PLAYER_UPHILL_RIGHT% = 5
CONSTANT ROAD_LENGTH_NONE% = 0
CONSTANT ROAD_LENGTH_SHORT% = 25
CONSTANT ROAD_LENGTH_MEDIUM% = 50
CONSTANT ROAD_LENGTH_LONG% = 100
CONSTANT ROAD_HILL_NONE% = 0
CONSTANT ROAD_HILL_LOW% = 20
CONSTANT ROAD_HILL_MEDIUM% = 40
CONSTANT ROAD_HILL_HIGH% = 60
CONSTANT ROAD_CURVE_NONE% = 0
CONSTANT ROAD_CURVE_EASY% = 2
CONSTANT ROAD_CURVE_MEDIUM% = 4
CONSTANT ROAD_CURVE_HARD% = 6
CONSTANT PI = 3.14159
CONSTANT COLOURS_SKY% = 7526382
CONSTANT COLOURS_TREE% = 20744
CONSTANT COLOURS_FOG% = 20744
TYPE tScreen
scale
x
y
w
ENDTYPE
TYPE tCamera
x
y
z
ENDTYPE
TYPE tWorld
x
y
z
ENDTYPE
TYPE tColour
rumble%
road%
grass%
lane%
ENDTYPE
TYPE tPart
world AS tWorld
camera AS tCamera
screen AS tScreen
ENDTYPE
TYPE tSegment
index%
colour AS tColour
p1 AS tPart
p2 AS tPart
curve
ENDTYPE
TYPE TRoad
segments[] AS tSegment
LIGHT AS tColour
DARK AS tColour
SPRITES_SCALE
fps = 75.0
fpsStep = 0.0
segmentLength% = 200 // Length of a single segment
rumbleLength% = 3 // Number of segments per red/white rumble strip
roadWidth% = 2000 // Actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
cameraHeight% = 1000 // Z height of camera
cameraDepth = 0
position = 0.0 // Current camera Z position
playerX = 0 // Player x offset from center of road (-1 to 1 to stay independent of roadWidth)
playerZ = 0 // Player relative z distance from camera (computed)
lanes% = 3 // Number of lanes
fieldOfView% = 100 // angle (degrees) for field of view
speed = 0.0 // current speed
maxSpeed = 0 // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
accel = 0 // acceleration rate - tuned until it 'felt' right
breaking = 0 // deceleration rate when braking
decel = 0 // 'natural' deceleration rate when neither accelerating, nor braking
offRoadDecel = 0 // off road deceleration is somewhere in between
offRoadLimit = 0 // limit when off road deceleration no longer applies (e.g. you can always go at least t
resolution = 0.0
width%;height%
keyLeftRight%
trackLength%
drawDistance% = 100
FUNCTION Initialise%:
LOCAL tW%,tH%
GETSCREENSIZE self.width%,self.height%
self.position=0.0
self.cameraDepth=1.0 / TAN((self.fieldOfView%/2)) // * 3.141592653/180.0)
DEBUG "Camera depth : "+self.cameraDepth+" "+self.fieldOfView%+"\n"
self.LIGHT.road%=7039851
self.LIGHT.grass%=1092112
self.LIGHT.rumble%=5592405
self.LIGHT.lane%=13421772
self.DARK.road%=6908265
self.DARK.grass%=39424
self.DARK.rumble%=12303291
self.DARK.lane%=0
self.fpsStep=1.0/self.fps
self.maxSpeed=self.segmentLength/self.fpsStep
self.accel = self.maxSpeed/5.0
self.breaking = -self.maxSpeed
self.decel = -self.maxSpeed/5.0
self.offRoadDecel = -self.maxSpeed/2.0
self.offRoadLimit = self.maxSpeed/4.0
self.speed=0.0
self.playerZ=self.cameraHeight * self.cameraDepth
self.resolution=self.height%/640.0
self.keyLeftRight%=0
GETSPRITESIZE PLAYER_STRAIGHT%,tW%,tH%
self.SPRITES_SCALE= 0.3 * (1.0/tW%)
ENDFUNCTION
FUNCTION addSegment%:curve,y
LOCAL size%
LOCAL segment AS tSegment
size%=BOUNDS(self.segments[],0)
// DEBUG "Curve : "+curve+"\n"
segment.index%=size%
segment.curve=curve
segment.p1.world.x=0.0
segment.p1.world.y=self.lastY()
segment.p1.world.z=size%*self.segmentLength%
segment.p1.camera.x=0.0
segment.p1.camera.y=0.0
segment.p1.camera.z=0.0
segment.p1.screen.x=0.0
segment.p1.screen.y=0.0
segment.p1.screen.w=0.0
segment.p1.screen.scale=0.0
segment.p2.world.x=0.0
segment.p2.world.y=y
segment.p2.world.z=(size%+1)*self.segmentLength%
segment.p2.camera.x=0.0
segment.p2.camera.y=0.0
segment.p2.camera.z=0.0
segment.p2.screen.x=0.0
segment.p2.screen.y=0.0
segment.p2.screen.w=0.0
segment.p2.screen.scale=0.0
IF FLOOR(MOD(size%/self.rumbleLength%,2))
segment.colour=self.LIGHT
ELSE
segment.colour=self.DARK
ENDIF
segment.colour.lane%=RGB(255,255,0)
// DEBUG "> : "+segment.p1.world.y+" "+segment.p2.world.y+"\n"
DIMPUSH self.segments[],segment
ENDFUNCTION
FUNCTION addRoad%:enter, hold, leave, curve, y
LOCAL startY,endY,total
LOCAL n%
startY=self.lastY()
endY=startY+(INTEGER(y)*self.segmentLength%)
total=enter+hold+leave
// DEBUG "Start Y : "+startY+" endY : "+endY+"\n"
// KEYWAIT
FOR n%=0 TO enter-1;
self.addSegment(self.easeIn(0,curve,n%/enter),easeInOut(startY,endY,n%/total));
NEXT
// DEBUG "Holding ---\n"
FOR n%=0 TO hold-1
self.addSegment(curve,self.easeInOut(startY,endY,(enter+n%)/total));
NEXT
// DEBUG "---\n"
FOR n%=0 TO leave-1
self.addSegment(self.easeInOut(curve,0,n%/leave),self.easeInOut(startY,endY,(enter+hold+n)/total))
NEXT
ENDFUNCTION
FUNCTION addStraight%:num=25
self.addRoad(num,num,num,0,0)
ENDFUNCTION
FUNCTION addHill%:num,height
self.addRoad(num,num,num,0,height)
ENDFUNCTION
FUNCTION addCurve%:num, curve,height
self.addRoad(num,num,num,curve,height)
ENDFUNCTION
FUNCTION addLowRollingHills%:num,height
self.addRoad(num, num, num, 0, height/2)
self.addRoad(num, num, num, 0, -height)
self.addRoad(num, num, num, 0, height)
self.addRoad(num, num, num, 0, 0)
self.addRoad(num, num, num, 0, height/2)
self.addRoad(num, num, num, 0, 0)
ENDFUNCTION
FUNCTION addSCurves%:
self.addRoad(ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, -ROAD_CURVE_EASY, ROAD_HILL_NONE)
self.addRoad(ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_CURVE_MEDIUM, ROAD_HILL_MEDIUM)
self.addRoad(ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_CURVE_EASY, -ROAD_HILL_LOW)
self.addRoad(ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, -ROAD_CURVE_EASY, ROAD_HILL_MEDIUM)
self.addRoad(ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, ROAD_LENGTH_MEDIUM, -ROAD_CURVE_MEDIUM, -ROAD_HILL_MEDIUM)
ENDFUNCTION
FUNCTION addDownhillToEnd%:num=200
self.addRoad(num, num, num, -ROAD_CURVE_EASY, -lastY()/self.segmentLength);
ENDFUNCTION
FUNCTION resetRoad%:
LOCAL segment AS tSegment
DIM self.segments[0]
self.addStraight(ROAD_LENGTH_SHORT/2)
self.addHill(ROAD_LENGTH_SHORT, ROAD_HILL_LOW%)
self.addLowRollingHills(ROAD_LENGTH_SHORT,ROAD_HILL_LOW%)
self.addCurve(ROAD_LENGTH_MEDIUM, ROAD_CURVE_MEDIUM, ROAD_HILL_LOW)
self.addLowRollingHills(ROAD_LENGTH_SHORT,ROAD_HILL_LOW%)
self.addCurve(ROAD_LENGTH_LONG, ROAD_CURVE_MEDIUM, ROAD_HILL_MEDIUM)
self.addStraight()
self.addCurve(ROAD_LENGTH_LONG, -ROAD_CURVE_MEDIUM, ROAD_HILL_MEDIUM)
self.addHill(ROAD_LENGTH_LONG, ROAD_HILL_HIGH)
self.addCurve(ROAD_LENGTH_LONG, ROAD_CURVE_MEDIUM, -ROAD_HILL_LOW)
self.addHill(ROAD_LENGTH_LONG, -ROAD_HILL_MEDIUM)
self.addStraight()
self.addDownhillToEnd()
segment=self.findSegment(self.playerZ)
self.segments[segment.index% + 2].colour.road% = RGB(255,255,255)
self.segments[segment.index% + 2].colour.lane% = RGB(255,255,255)
self.segments[segment.index% + 3].colour.road% = RGB(255,255,255)
self.segments[segment.index% + 3].colour.lane% = RGB(255,255,255)
FOR n%=0 TO self.rumbleLength%-1
self.segments[BOUNDS(self.segments[],0)-1-n%].colour.road% = RGB(255,255,255)
self.segments[BOUNDS(self.segments[],0)-1-n%].colour.lane% = RGB(255,255,255)
NEXT
self.trackLength% = BOUNDS(self.segments[],0)*self.segmentLength%
ENDFUNCTION
FUNCTION lastY:
LOCAL size%
size%=BOUNDS(self.segments[],0)
IF size%=0
RETURN 0
ELSE
RETURN self.segments[size%-1].p2.world.y
ENDIF
ENDFUNCTION
FUNCTION findSegment AS tSegment:z
RETURN self.segments[MOD(FLOOR(z/self.segmentLength%),BOUNDS(self.segments[],0))]
ENDFUNCTION
FUNCTION increase: start,increment,maxV
LOCAL result
result=start+increment
WHILE result>=maxV
DEC result,maxV
WEND
WHILE result<0
INC result,maxV
WEND
RETURN result
ENDFUNCTION
FUNCTION accelerate:v,accel,dt
RETURN v + (accel * dt)
ENDFUNCTION
FUNCTION limit:value, minV,maxV
RETURN MAX(minV,MIN(value,maxV))
ENDFUNCTION
FUNCTION easeIn:a,b,percent
RETURN a + (b-a)*POW(percent,2)
ENDFUNCTION
FUNCTION easeOut:a,b,percent
RETURN a + (b-a)*(1.0-POW(1-percent,2))
ENDFUNCTION
FUNCTION easeInOut:a,b,percent
LOCAL cosV,v
// DEBUG "A:"+a+" B:"+b+" Percent:"+percent+"\n"
cosV=(percent*PI)*(180.0/PI)
v=a + (b-a)*((-COS(cosV)/2.0) + 0.5)
// DEBUG "Value : "+v+"\n"
RETURN v
ENDFUNCTION
FUNCTION percentageRemaining:n,total
RETURN MOD(n,total)/total
ENDFUNCTION
FUNCTION interpolate:a,b,percent
RETURN a+(b-a)*percent
ENDFUNCTION
FUNCTION fog%:x%,y%,width%,height%
ALPHAMODE -0.9
DRAWRECT x%,y%,width%,height%,RGB(64,64,64)
ALPHAMODE -1.0
ENDFUNCTION
FUNCTION update%:dt
LOCAL dx
// DEBUG dt+"\n"
// DEBUG "Speed:"+self.speed+"\n"
// DEBUG (dt*self.speed)+"\n"
self.position = self.increase(self.position, dt * self.speed, self.trackLength)
dx = dt * 2 * (self.speed/self.maxSpeed); // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
IF KEY(203)
DEC self.playerX,dx
self.keyLeftRight%=-1
ELSEIF KEY(205)
INC self.playerX,dx
self.keyLeftRight%=1
ELSE
self.keyLeftRight%=0
ENDIF
IF KEY(200)
self.speed=self.accelerate(self.speed,self.accel,dt)
ELSEIF KEY(208)
self.speed=self.accelerate(self.speed,self.breaking,dt)
ELSE
self.speed=self.accelerate(self.speed,self.decel,dt)
ENDIF
IF (self.playerX<-1.0 OR self.playerX>1.0) AND (self.speed>self.offRoadLimit)
self.speed=self.accelerate(self.speed,self.offRoadDecel,dt)
ENDIF
self.playerX=self.limit(self.playerX,-2,2)
self.speed=self.limit(self.speed,0.0,self.maxSpeed)
ENDFUNCTION
FUNCTION project%:p AS tPart, cameraX, cameraY, cameraZ, cameraDepth, width%, height%, roadWidth%
p.camera.x=p.world.x - cameraX
p.camera.y=p.world.y - cameraY
p.camera.z=p.world.z - cameraZ
p.screen.scale=self.cameraDepth/p.camera.z
// DEBUG "Z : "+p.camera.z+" "+cameraZ+"\n"
// DEBUG "Y : "+p.camera.y+" "+cameraY+"\n"
// DEBUG "X : "+p.camera.x+" "+cameraX+"\n"
// DEBUG "Camera depth : "+cameraDepth+" p.camera.z : "+p.camera.z+"\n"
// DEBUG "Scale : "+p.screen.scale+"\n"
// DEBUG "Width : "+width%+" Height : "+height%+"\n"
// DEBUG "Road width : "+roadWidth%+"\n"
p.screen.x=INTEGER((width%/2)+(p.screen.scale*p.camera.x*width%/2))
p.screen.y=INTEGER((height%/2)-(p.screen.scale*p.camera.y*height%/2))
p.screen.w=INTEGER(p.screen.scale*roadWidth%*width%/2)
// DEBUG p.screen.x+" "+p.screen.y+" "+p.screen.w+"\n"
//KEYWAIT
ENDFUNCTION
FUNCTION player%:width%, height%, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown
LOCAL bounce
LOCAL sprite%
bounce=1.5*(RND(2)-1)*speedPercent*resolution
IF steer<0
sprite%=IIF(updown>0,PLAYER_UPHILL_LEFT%,PLAYER_LEFT%)
ELSEIF steer>0
sprite%=IIF(updown>0,PLAYER_UPHILL_RIGHT%,PLAYER_RIGHT%)
ELSE
sprite%=IIF(updown>0,PLAYER_UPHILL_STRAIGHT%,PLAYER_STRAIGHT%)
ENDIF
DEBUG "SP:"+sprite%+"\n"
self.sprite(width%,height%,resolution,roadWidth,sprite%,scale,destX,destY+bounce,-0.5,-1)
ENDFUNCTION
FUNCTION sprite%:width%, height%, resolution, roadWidth, sprite%, scale, destX, destY, offsetX, offsetY, clipY=0.0
LOCAL destW,destH,clipH
GETSPRITESIZE sprite%,destW,destH
destW=destW*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
destH=destH*scale*(width%/2.0)*self.SPRITES_SCALE*roadWidth
DEBUG "Scale : "+self.SPRITES_SCALE+" Width : "+roadWidth+"\n"
DEBUG "W:"+destW+" "+destH+"\n"
INC destX,destW*offsetX
INC destY,destH*offsetY
clipH=IIF(clipY,MAX(0,destY+destH-clipY),0)
IF clipH<destH
STRETCHSPRITE sprite%,destX,destY,destW,destH-clipH
ENDIF
ENDFUNCTION
FUNCTION render%:
LOCAL baseSegment AS tSegment,playerSegment AS tSegment
LOCAL basePercentage,playerY,playerPercent
LOCAL maxy%,n%
LOCAL segment AS tSegment
LOCAL x,dx
// DEBUG "H2:"+self.height%+"\n"
maxy%=self.height%
// DEBUG "maxy : "+maxy%+"\n"
DEBUG "Position : "+self.position+"\n"
baseSegment = self.findSegment(self.position)
playerSegment=self.findSegment(self.position+self.playerZ)
basePercentage=self.percentageRemaining(self.position,self.segmentLength%)
playerPercent=self.percentageRemaining(self.position+self.playerZ,self.segmentLength%)
playerY= self.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
x=0.0
dx=-(baseSegment.curve*basePercentage)
PRINT FORMAT$(4,4,dx),0,0
//DEBUG "Base Percentage : "+basePercentage+" DX : "+dx+"\n"
// KEYWAIT
FOR n%=0 TO self.drawDistance%-1
segment=self.segments[MOD(baseSegment.index%+n%,BOUNDS(self.segments[],0))]
self.project(segment.p1,(self.playerX*self.roadWidth%*1.0)-x,playerY+self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
self.project(segment.p2,(self.playerX*self.roadWidth%*1.0)-x-dx,playerY+self.cameraHeight%,self.position%,self.cameraDepth,self.width%,self.height%,self.roadWidth%)
// DEBUG "Segment 1 camera Z : "+segment.p1.camera.z+"\n"
// DEBUG "Segment 2 screen Y : "+segment.p2.screen.y+"\n"
// DEBUG "maxy : "+maxy%+"\n"
INC x,dx
INC dx,baseSegment.curve
IF segment.p1.camera.z<=self.cameraDepth OR segment.p2.screen.y>=maxy%
CONTINUE
ELSE
self.segment(self.width%,self.lanes%, _
segment.p1.screen.x, _
segment.p1.screen.y, _
segment.p1.screen.w, _
segment.p2.screen.x, _
segment.p2.screen.y, _
segment.p2.screen.w, _
1.0, _
segment.colour)
ENDIF
maxy%=segment.p2.screen.y%
NEXT
self.player(self.width%, self.height%, self.resolution, self.roadWidth% ,PLAYER_STRAIGHT%,self.speed/self.maxSpeed, _
self.cameraDepth/self.playerZ, _
self.width%/2, _
self.height- (self.cameraDepth/self.playerZ * interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * self.height/2.0), _
self.speed * SGN(self.keyLeftRight%), _
playerSegment.p2.world.y - playerSegment.p1.world.y)
// self.cameraDepth/self.playerZ, _
// self.width%/2, _
// self.height, _
// self.speed * 1, _ // (keyLeft ? -1 : keyRight ? 1 : 0),
// 0)
ENDFUNCTION
FUNCTION rumbleWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth%/MAX(6,2*lanes%)
ENDFUNCTION
FUNCTION laneMarkerWidth:projectedRoadWidth%,lanes%
RETURN projectedRoadWidth/MAX(32,8*lanes%)
ENDFUNCTION
FUNCTION polygon%:x1,y1,x2,y2,x3,y3,x4,y4,colour%
STARTPOLY 99,0
POLYVECTOR x1,y1,x2,y2,colour%
POLYVECTOR x2,y2,x3,y3,colour%
POLYVECTOR x3,y3,x4,y4,colour%
POLYVECTOR x4,y4,x1,y2,colour%
ENDPOLY
// DRAWRECT x1,y1,x2,y2,colour%
// DRAWRECT x3,y3,x4,y4,colour%
//DRAWLINE x1,y1,x2,y2,colour%
//DRAWLINE x3,y3,x4,y4,colour%
ENDFUNCTION
FUNCTION segment%:width%,lanes%,x1,y1,w1,x2,y2,w2,fog,colour AS tColour
LOCAL r1,r2,l1,l2,lanew1,lanew2,lanex1,lanex2
r1=self.rumbleWidth(w1,lanes%)
r2=self.rumbleWidth(w2,lanes%)
l1=self.laneMarkerWidth(w1,lanes%)
l2=self.laneMarkerWidth(w2,lanes%)
//DEBUG "R1 : "+r1%+" R2 : "+r2%+" l1 : "+l1%+" l2 : "+l2%+"\n"
ALPHAMODE -1.0
DRAWRECT 0,y2,width%,y1-y2,colour.grass%
self.polygon(x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, colour.rumble)
self.polygon(x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, colour.rumble)
self.polygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, colour.road)
IF colour.lane%
lanew1=w1*2.0/lanes%
lanew2=w2*2.0/lanes%
lanex1=x1-w1+lanew1
lanex2=x2-w2+lanew2
FOR lane%=1 TO lanes%-1
self.polygon(lanex1-l1/2.0,y1,lanex1+l1/2.0,y1,lanex2+l2/2.0,y2,lanex2-l2/2.0,y2,colour.lane%)
INC lanex1,lanew1
INC lanex2,lanew2
NEXT
ENDIF
//self.fog(0,y1,width%,y2-y1)
ENDFUNCTION
ENDTYPE
LOCAL road AS TRoad
LOCAL now,last,dt,gdt,stp=0.025
LIMITFPS -1
LOADSPRITE "Media/player_straight.png",PLAYER_STRAIGHT%
LOADSPRITE "Media/player_left.png",PLAYER_LEFT%
LOADSPRITE "Media/player_right.png",PLAYER_RIGHT%
LOADSPRITE "Media/player_uphill_straight.png",PLAYER_UPHILL_STRAIGHT%
LOADSPRITE "Media/player_uphill_left.png",PLAYER_UPHILL_LEFT%
LOADSPRITE "Media/player_uphill_right.png",PLAYER_UPHILL_RIGHT%
road.Initialise()
road.resetRoad()
now=GETTIMERALL()
last=GETTIMERALL()
WHILE TRUE
now=GETTIMERALL()
DEBUG last+" "+now+"\n"
dt=MIN(1.0,(now-last)/1000.0)
INC gdt,dt
WHILE gdt>stp
DEC gdt,stp
road.update(stp)
WEND
road.update(stp)
SMOOTHSHADING FALSE
road.render()
SHOWSCREEN
last = now
WEND
Youtube link : https://www.youtube.com/watch?v=Zlpll6c03-Y (https://www.youtube.com/watch?v=Zlpll6c03-Y)
Good stuff. :good: For inspiration, check out: https://www.youtube.com/channel/UCy9fx8XnsX_hP9UsPt4k5IA
Excellent stuffage :)
Thanks Mr-T just yesterday I want comment this point... I think whit this we can done a great game... Thanks a lot, very very usefully.
No problem!
A quick preview of the next part : Scenery (and I'll try and get the background graphics in as well later) :
great , i like this stuff !
Good to hear :)
This is the next stage, and has road-side objects and a background. The background warps unfortunately - must be mis-reading the Javascript code for that, and will have to look at it later.
What could be done in future is "rent" billboard space :)
This screenshot looks nice. There is a cool "Outrun" game with named "Final Freeway" for iOS which could been get some inspiration from. That is a 2d Outrun style game. But scene here also looks nice.
I'm using the graphics that Code inComplete used for his example programs - for a proper game, you would need a decent/proper/professional artist.
The last part will have the vehicles in!
Very impressive, I think from here can see the light some interesting projects, not it's necessary a car game.... somethink like F-Zero can be cool too!!!
Thanks MrT this stuff have a lot of quality.. :booze:
Things that would be good to add (aside from more efficient code) :
A) Different sides having different coloured background (and possibly emulate height even more by being able to set the height for that)
B) Tunnels
C) Split tracks
D) Track sections having different road and line widths
Got it all up and running now with vehicles moving and over taking.
The main problem (aside from the shuddering - I think that is due to how GLBasic scales stuff), is that collision against objects at the side are based on the size of the original graphic and not what it should be... It would require a fair bit of code to deal with that unfortunately (Javascript just fetches the current sprite size).
As you'll see in the video, I've modified the track somewhat :)
F-Zero I think works on a slightly different principle - namely its tracks are 3D transformed bitmaps
Video : http://www.dailymotion.com/video/x1z3e9u_racing-game-demo-with-glbasic_tech
Smooth video. :) I guess you should be able to easily flip the traffic sprites from right to left lane side view, at least for now.
How about some Road Rash sprites? :D
Not had a chance to test (or respond before now), but I'm really liking what I'm seeing :)
This is such a good thread, I think perhaps it would be good to put it in the tutorials section for posterity as well. :nw:
Quote from: matchy on 2014-Jun-11
Smooth video. :) I guess you should be able to easily flip the traffic sprites from right to left lane side view, at least for now.
How about some Road Rash sprites? :D
If you draw some for me :)
Each vehicle needs 9 graphics : straight, left,right ,up+left,up+right,down+left,down+right - shouldn't take you long to do 5 or so vehicles :)
Quote from: MrTAToad on 2014-Jun-11
Quote from: matchy on 2014-Jun-11
How about some Road Rash sprites? :D
If you draw some for me :)
Hang on (no pun intended), where did you get those Outrun sprites because I meant rips?
I didn't get them anywhere - I "borrowed" them from the original authors website... He, in turn, "borrowed" them from http://pixel.garoux.net/game/44 (http://pixel.garoux.net/game/44)
Sure but I'm more inclined to automate it than manual pixel draw. I'd would approach it by rendering a 3D vehicle to 2D as opposed to pixel art. :P Perhaps I'll get a better chance to try out the engine but for now, who's got some vehicles? ;) :D
As for, say, billboards, they can be easily produced internally also.
A more import and dynamic example, also something that I need, is implementing configurable plants/trees/fauna types. :sick:
This looks great, quite way too fast here.
Yep Matchy, render the car and get the output to pixel art style would be best
I wonder maybe if the system could be up gradable to, let´s say, spline curves instead of just horizontal lines.
Would it be good for gaming?
It is way too fast - at the moment I'm upgrading the engine so that the left and right sides can have different coloured backgrounds, track width and number of lines, and well as variable "rumble" widths.
I'll release a more playable version later on...
The extra speed is not bad, you could probably split screen 4x easily on pc :good:
Certainly could!
I've got a more playable version now. Need to correct the collision detection with the sides next - its another area that needs optimising.
Wow, this is really coming along!
Multi-layered parallax background scrolling too?
Plays so smooth.
Possible to have forks in the road?
Jumps? Rivers and bridges?
Can't wait to see where this goes.
Jumps, this is begging for jumps! :good:
I think the background needs improving - the layers don't moving terribly convincingly at the moment.
Got an example of multiple tracks : http://www.dailymotion.com/video/x1z9z10_pseudo-3d-split-tracks_tech (http://www.dailymotion.com/video/x1z9z10_pseudo-3d-split-tracks_tech)
To fork/join them it would be just a matter of changing the distance between them and decide on a rendering order. Dealing with the animated grass would be harder and I can see that being a dealt with as an array of quads.
Experimenting with tunnels - it turns out that the left and right sides of the tracks will need to be done back to front (like vehicles)
video is looking good on split roads.
Other then the background, you might want to overlay some degradee to create an aerial perspective.
Lotus 2 is the best example of this game type I have seen so far. maybe a closer to that perspective?
It would preferably be user-definable. In the previous program I had it that as the road got wider, the perspective slowly changed.
Tunnels are being a pain, especially with rendering vehicles in a tunnel.
If anyone has any ideas on how to sort that, I would be most greatful :)
By the looks of it, the tunnel face (and possibly exit) and vehicles will need to be sorted by the Z value. If a tunnel face is present then no objects before this will be displayed...
I think the thing to do first is to allow vertical positioning - this would allow clouds as well...
Anyway, list of features needed :
- Straight roads - Done
- Bends - Done
- Hills - Done
- Troughs/Slopes - Done
- Variable road width - Done. Currently perspective changes, which needs to be stopped
- Split roads - The basis for this has been setup, but things like moving player and vehicles to the different sections hasn't been dealt with. In addition, the grass section would need to be moved to a different structure.
- Vertical positioning of billboards - This now allows things like clouds, and forms the basis of tunnels roofs too. The only downside is that tunnel roofs are of fixed size, so will only cover section of track. This could be solved by modifying the billboard structure so that it knows which are sprites and which could be drawn using standard draw functions. This sorts out clipping problems, and allows tunnels to follow the road exactly. Tunnel example : http://www.dailymotion.com/video/x1zc3pi_example-of-a-tunnel-in-pseudo-3d_tech
- Bridges - exactly the same as tunnels
- Water/mud etc - would be no problem colouring sections of track and grass area - the start/end lines are examples of this
Looking very nice indeed! :good:
I would not know how to do the tunnel sorting, I´d have to try understand the code first. ;/
My idea for tunnels seems to be fine - see my previous post for more details!
This just seems so tedious! So much work to recreate that retro driving style.
In pure 3D mode, it would be so much easier to create a simple driving game.
I read an article a few years back on this retro 3d technique and was intrigued . . . but not enough to actually do it!
Kudos, you have great patience wise one. :nw:
What type of game play are you planning, just the usual driving / racing?
I wonder what other driving types could be done with this?
Bikes riding down a path?
Boats / Jet Skis on a river?
Flying / hovering, but then why the road? Clouds?
This could be used to make the next viral runner game - but only driving.
Move over Flappy Birds. Touch screen to drive left, let go and it drives right by itself (one touch movement control, like flappy birds!)
Kind of only kidding, that would be hard, as you would always have to be turning one way or the other. But hey, people like hard.
Better maybe to have touch left and right side of screen to turn. Or tilt.
Place obstacles in your path, make the road width get smaller, increase driving speed - see how far you can drive!
Flappy was a hit - and for no reason! This at least sounds much more fun.
Edit: Call it "DriRun"! Well - it sounded better in my head.
Whilst doing it in proper 3D may seem easier, but you've got the trouble of designing the tracks, camera positioning and collision detection. Plus, of course, you are hoping that the machine is fast enough...
I would like to do something along the lines of Lotus Turbo Challenge 2 or a Trailblazer type game. Aside from the graphics, another problem will be how to create levels....
This type of thing was used in quite a few arcade games - Space Harrier (just need to modify the ground), Outrun, Line Of Fire, Afterburner/G-Loc or even 3D Deathchase...
yep, I like lotus 2, that would be a great feat!
Other then harrier, this was also used on the more obscure 8-bit games. I like this Technic.
I´m specially found of FEEDBACK on the MSX, though a lot simpler.
Lotus 2 four players with two Amiga linked. Those were the days. :)
So how does it currently run on other platforms, line Android/win32?
Yes in 3D it's easy I'm doing testings in Shiva and its really easy, about make the traks really you need only 2 or 3, then you can make any track.
But the good point it's how says Erico it's like lotus, I think if MrT make a game more far than only run the tracks the game will be explendid, something like you have to buy wheels, tires, can buy cars, really make a strategy, the game can be awesome...
I try yesterday to play a bit and I like it a lot, very faster and very playable.
I'm updating it to be a bit more efficient at the moment - its a multy-pass rendering system now (mainly so that tunnels and what-not could be displayed in the correct Z order).
I think I've spent enough time on this now - there is one fairly serious problem (that I mentioned originally) is that the car bounces around on hills/slopes - I think its due to the segments Y positions being incorrect for some reason. I have no idea why it happens or how to fix it unfortunately.
I've finally found out what was causing the jitter! Only taken several days :)
Need to re-do the rendering routine again now :)
Looks awesome, seriously it brings that retro racer feel right back.
Yes, these types of games are usually pretty fun.
Glad you like it!
Wow, that's cool.
For the sprites, you might use a 3D model and render it from different angles?
Or do photos of a real car and reduce it to 16 colours...
Thanks! For the graphics side I would need someone to design them.
i see multiple test app and a gbas in this thread...
Could you put them together in a package and update it to the top of the thread?
Better yet, its in the Showcase - its a slightly earlier version (before I fixed the positioning problem), but everything should be there.