Something of interest : Pseudo 3D Tracks V2 - Parts 1, 2, 3, 4 and 5

Previous topic - Next topic

MrTAToad

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 :

Code (glbasic) Select
// --------------------------------- //
// 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



Ian Price

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). :)
I came. I saw. I played.

MrTAToad

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.

Code (glbasic) Select
// --------------------------------- //
// 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...

mentalthink

Very very cool, and very very usefull,... Thanks a lot for the code....
<3 <3

MrTAToad

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 :)

Code (glbasic) Select
// --------------------------------- //
// 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


Ian Price

I came. I saw. I played.

mentalthink

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.


MrTAToad

A quick preview of the next part : Scenery (and I'll try and get the background graphics in as well later) :


Marmor


MrTAToad

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 :)

spacefractal

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.
Genius.Greedy Mouse - Karma Miwa - Spot Race - CatchOut - PowerUp Elevation - The beagle Jam - Cave Heroes 2023 - https://spacefractal.itch.io/

MrTAToad

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!

mentalthink

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: