BASIC

Author Topic: Something of interest : Pseudo 3D Tracks V2 - Parts 1, 2, 3, 4 and 5  (Read 16214 times)

MrTAToad

  • Guest
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

« Last Edit: 2014-Jun-11 by MrTAToad »

Offline Ian Price

  • Administrator
  • Prof. Inline
  • *******
  • Posts: 4144
  • On the shoulders of giants.
    • View Profile
    • My Apps
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

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

Offline mentalthink

  • Prof. Inline
  • *****
  • Posts: 3366
  • Integrated Brain
    • View Profile
Very very cool, and very very usefull,... Thanks a lot for the code....
 <3 <3

MrTAToad

  • Guest
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
« Last Edit: 2014-Jun-09 by MrTAToad »

Offline matchy

  • Prof. Inline
  • *****
  • Posts: 1542
    • View Profile

Offline Ian Price

  • Administrator
  • Prof. Inline
  • *******
  • Posts: 4144
  • On the shoulders of giants.
    • View Profile
    • My Apps
Excellent stuffage :)
I came. I saw. I played.

Offline mentalthink

  • Prof. Inline
  • *****
  • Posts: 3366
  • Integrated Brain
    • View Profile
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

  • Guest
No problem!

MrTAToad

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


Offline Marmor

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 908
  • 96A285CC
    • View Profile
    • my youtube channel
great , i like this stuff !

MrTAToad

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

Offline spacefractal

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 3611
    • View Profile
    • Space Fractal
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.
Greedy Mouse - Karma Miwa - Spot Race - CatchOut - Android Extras - is on a vacation trip, home before end of few days in jan.

MrTAToad

  • Guest
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!
« Last Edit: 2014-Jun-11 by MrTAToad »

Offline mentalthink

  • Prof. Inline
  • *****
  • Posts: 3366
  • Integrated Brain
    • View Profile
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: