Struggling with Pseudo3D racing game engine

Okay. Started looking into it. Don't have a solution yet, but I think I know where the problem lays, so I'll share what I've found out so far.
I decided to check the values of the npcCar on each frame, by adding this code to the end of the addNPCsprite function:

print("Scale: " ..tscale .. ", Position X: " .. px .. ", Position Y: " .. py .. ", Width: ".. w .. ", Height: ".. h)

The result was this:

Scale: 0.5699478, Position X: 194.8186, Position Y: 223.6269, Width: 51.81343, Height: 51.81343
Scale: 0.658682, Position X: 194.012, Position Y: 239.7604, Width: 59.88019, Height: 59.88019
Scale: 0.5744121, Position X: 194.7781, Position Y: 224.4386, Width: 52.21928, Height: 52.21928
Scale: 0.6606601, Position X: 193.994, Position Y: 240.12, Width: 60.06001, Height: 60.06001
Scale: 0.5789469, Position X: 194.7368, Position Y: 225.2631, Width: 52.63154, Height: 52.63154
Scale: 0.6707311, Position X: 193.9025, Position Y: 241.9511, Width: 60.97556, Height: 60.97556
Scale: 0.5835539, Position X: 194.695, Position Y: 226.1007, Width: 53.05036, Height: 53.05036
Scale: 0.6727823, Position X: 193.8838, Position Y: 242.3241, Width: 61.16203, Height: 61.16203

This was when also driving (e.g. when the bug showed up).
It's clearly visible that the scale changes from ~0.58 to ~0.67 on each frame, which causes the flickering. No matter the distance to the player sprite, there always seems to be a scale issue like this. The same issue happens no matter the distance the two devices are appart (that being said, the number difference in scale changes based on the distance to the player)

The big question now is why does this happen?
The scale value comes from the project function, so the problem could lay there. However, this specific bit of code in the addNPCsprite function is probably the actual cause of the error:

if ((bg.roadPosition-sumct)<=0) or ((bg.roadPosition-sumct)>1) then return end

@Antares could you explain what you're doing there?

Also big sidenote:
I sideloaded this project onto my playdate and it runs at 4-5 fps and crashes after a few seconds because of an out of memory exception. So you definitely have a memory leak in here somewhere.

1 Like

@PizzaFuel First of all, thank you so much for your time and help :pray:t2: I really appreciate that!

I'm a classic example of a meme person "I have no idea why my code works" so this line of code if my weird way to ensure that for each animation frame the NPC car is being added only once. It checks the difference between NPC car position (bg.roadPosition) and current camera segment position (sumct) but I see instead of that, I could just write:

if not (((bg.roadPosition-sumct)==0)) then return end

Without that line of code I had this issue:

glitch

That memory leak is worrying me though :playdate_tear: Could you check different versions on your device? Here is a zip with version without any sprites around the road at all, only with tree sprites, and third with trees and road signs. All of them without that NPC car. Im trying to figure out when the game got broken by my amateur coding :hear_no_evil:
RacerPack.zip (271.5 KB)

Tested it out:
The versions with trees have the same issue. The version called "RacetNoSprites.pdx"runs with solid 29-30 fps and looks extremely smooth. Let it run for a two minutes, and found no issues with it.

So the issue came when you added the trees. Definitely try to get those working on the playdate first before focusing on the car.

Out of curiosity: Am I right in the assumption that you don't have your physical playdate yet and this is one of your first coding projects using the sdk? (I did see in your posts up there that you didn't have a playdate a few weeks ago, so I assume that's still the same?)

1 Like

I'm still waiting for my device to arrive. I'm 54XXX. Its "processing" right now. And this racing engine is actually my third demo :smile: First two were super simple:

Koko & Kebi Tennis

Koko & Kebi Vacuum Invaders

I want to prototype small games from different genres before I will figure out what I really want to do. Probably super short JRPG like Dragon Quest 1 for Famicom. Racing catches my attention though, as it looks cool, has much retro vibe and I could do a train simulator with that engine.

Also it looks like sprite calling kills the CPU. I was afraid of that :frowning:

2 Likes

Alrighty! That's dope! Really like the look and scale of those two demos!
Looking really cool so far for your third project on the playdate :smiley:

May I give you a bit of advice, as someone who also tried to get pseudo 3d projects runing before they had a physical playdate? (You can take this or leave it. Not here to discourage you.)

I would highly recommend you to wait with developing 3d stuff until your PlayDate arrives. The reasons for that are very simple:

  • 3D Games need a solid working base before you start adding any features: If you have the whole game done and only start debugging it on device at the end it'll drive you mad trying to find the sources of perfomance issues.
  • Debugging every feature once added is very easy once you have your device, but very time consuming if others have to do it for you.
  • If you want to add 3d stuff in lua you need a deep understanding of the SDKs strenghts and weaknesses. Especially around memory and sprite management.

I went through pretty much the same problems you currently have a year ago and learned this lesson the hard way. That's why I'd highly recommend using your current PlayDate-less time creating a ton of smaller projects and getting them working well. Then using that knowledge on your 3d game once you have the chance to freely debug it :wink:
Maybe you can even publish a smaller polished PlayDate game or two on itch to get your name out there!

2 Likes

Thank you! These hints are very reasonable and to be honest, deep in my heart I knew that this pseudo3D is a bit of overkill for a noob, but I'm a "try harder" type :joy: I think I can use part of this code though for some dumber version of Sky Destroyer or Space Harrier. I think it could work better if there were just one cat running endlessly to the horizon, and some tuna cans to collect and get points :joy:
spaceharrier

1 Like

I feel you so much. Same! :joy:
Had the exact same thought back then and eventually made this game, which is pretty much what you're talking about:

1 Like

OMG, I've seen your game on itch when I was doing research and actually, I thought "Oh, so psuedo3D is possible here, let's do a racer then" :joy:

1 Like

Guess this is fate then xD
Best of luck to ya! Looking forward to whatever you code up next :wink:

2 Likes

Thanks! I will keep you updated. Also, I think I will also try to push that code someday to Love2D and make a mobile Pseudo3D racer. At last my crappy code won't be so much problem for modern smartphones :smile:

1 Like

If you’re still looking for help with the sprite performance issue, here are a few technical pointers from things I noticed in your code. These will probably end up being pretty important regardless of whether you continue this project or work on a smaller one.

  1. When you pass a file path to a sprite or image initializer, it reloads that file from disk. It looks like right now your code does that for every sprite, every frame! Generally, it’s best to only load each image file once at the start and then reference that everywhere you need it. That way you aren’t constantly reading from disk and also aren’t filling up RAM with multiple copies of the same image. This one is going to be a performance killer on any platform, not just playdate.

  2. Playdate SDK Sprites are meant to persist their state and have the sprite system automatically call their :draw() method when necessary. This is different from pico8 etc where a sprite is basically just a graphic. I think playdate.graphics.image is more akin to what pico8 calls a sprite. I think for any pseudo-3d game, you’re going to want to have your code calling the :draw() method of images, instead of creating a fresh set of sprites every frame just to delete them when the next frame starts.

2 Likes

Thank you for these amazing insights! @daprice This is very helpful for my future project also, as I didn't think abut reading sprites from disk though. I guess its a noob mistake :sweat_smile: I was thinking now about reducing amount of sprites, and even rendering the game in half of PD resolution (like mode7 demo project does) but these two advices can be very handy. I understand first one fully, but I will need to dig more on the second hint:

– i should use images instead of sprites
– load them once and then refer only to variable containing them
– use "playdate.graphics.image:drawScaled(x, y, scale, [yscale])" for drawing them scaled

Is that correct? And anyway, do you have any idea how to avoid this blinking for NPC drivers? :thinking:

Yeah that sounds like a good starting point. At least, that’s how I would approach it, but it may not be the only (or even best) way.

One problem you might run into is that scaling images to non-integer sizes is expensive/slow on the playdate (this is a playdate-specific limitation that other platforms with GPUs don’t really have). I think other devs doing pseudo 3d stuff usually end up having to create multiple pre-scaled versions of their images, then in place of drawScaled they pick the closest one from a table and call its draw(x, y). But I think it’s worth a try using drawScaled to see how it performs, then test and go from there.

1 Like

Thank you once again! I've seen using pre-scalled stuff on platforms like Game Boy or NES, which didn't support sprite scalling. In this approach I guess I would have first use print() for my "scale" variable to predefine which should be pick to draw.

I still don't have any idea for moving NPCs though. I mean currently they are just redrawn in new cordinates, when they get into new road segments. And I know I should implement additional math in scaling them, which will consider not only their road segment but but also their placement related to the player.

This would be easier if I would fully understand the math from pico8 tutorial but currently im like ~60% of knowing whats happening there :sweat_smile: I mean I understand the concept of "cursor" going through segments of the road table and drawing different trapezoids considering fake3D projections math, but I still don't get how its possible that moving camera can move fluidly through road (not one segment per step) but my NPC driver can't and he teleports to each segment one by one.

Hey @PizzaFuel @daprice I optimized my code to load graphic files only once. I also changed sprites to images. As I use only "playdate.graphics.image:drawScaled" I prepared already flipped sprites of tres and road signs for the opposite side of the road. Here is my current code:

import "CoreLibs/sprites"
import "CoreLibs/graphics"

local pd <const> = playdate
local gfx <const> = pd.graphics

--[[The road is made out of "corners" (for our purposes we will call straight bits "corners" as well). 
	Corners need to curve in the direction the road turns, so we will simulate this by building them out of smaller straight “segments”.
	We can define the track as an array of these corners. Each corner has a segment count "ct" and a value indicating how much the direction turns between each segment “tu”.
	So tu=0 creates a straight piece, tu=1 will turn to the right, -1 left etc. For simplicity we'll ignore hills and valleys for now.--]]


gfx.sprite.setAlwaysRedraw(true) -- ensures the entire background gets drawn in one go every frame, important if the background drawing callback does anything complex
gfx.setBackgroundColor(gfx.kColorWhite)
gfx.sprite.setBackgroundDrawingCallback(
	function( x, y, width, height )
        drawScreen()
	end
)

local leftSakuraTreeImage = gfx.image.new('images/left_sakura_tree')
local rightSakuraTreeImage = gfx.image.new('images/right_sakura_tree')
local leftTurnSignImage = gfx.image.new('images/left_turn_sign')
local rightTurnSignImage = gfx.image.new('images/right_turn_sign')
local playerCarImage = gfx.image.new('images/car')


local bg_left_sakura_tree = {
    img = "left_sakura_tree",
    pos={4.5,0},     -- position relative to side of road
    siz={3,4.5},     -- size
    spc=5,            -- spacing
    bgscl=0.06,        -- param to scale sprite
    flpr=true
}

local bg_right_sakura_tree = {
    img = "right_sakura_tree",
    pos={2,0},     -- position relative to side of road
    siz={3,4.5},     -- size
    spc=5,            -- spacing
    bgscl=0.06,        -- param to scale sprite
    flpr=true
}

local bg_left_turn_sign = {
    img = "left_turn_sign",
    pos={2,0},-- position relative to side of road
    siz={1.5,1.5},-- size
    spc=2,-- spacing
    bgscl=0.03,-- param to scale sprite
    flpr=true        -- flip when on right hand side

}

local bg_right_turn_sign = {
    img = "right_turn_sign",
    pos={2,0},-- position relative to side of road
    siz={1.5,1.5},-- size
    spc=2,-- spacing
    bgscl=0.03,-- param to scale sprite
    flpr=true        -- flip when on right hand side

}

local npcSprite = {
    img = "car",
    siz={1,1},
    bgscl=0.02,
    roadPosition=0,
    maxRoadSegments=0, -- moment when road position of the NPC should be reset to 1. A value is added in calculateRoad() function
    speed=0,
}


local road={
    {ct=40,tu=0,bgl=bg_left_sakura_tree,bgr=bg_right_sakura_tree},
    {ct=20,tu=.25,bgl=bg_left_turn_sign,bgr=bg_right_sakura_tree},
    {ct=40,tu=0,bgl=bg_left_sakura_tree,bgr=bg_right_sakura_tree},
    {ct=20,tu=-.25,bgl=bg_left_sakura_tree,bgr=bg_right_turn_sign},
    --{ct=20,tu=0,bgl=bg_tree,bgr=bg_tree},
}   


local drawnSprites = {}

local playerCarSpeed = 0

function npcDrive()
    if (npcSprite.roadPosition > npcSprite.maxRoadSegments) then npcSprite.roadPosition = 1 
    elseif npcSprite.speed > 1 then npcSprite.speed = 1 npcSprite.roadPosition += 1
    else npcSprite.speed += 0.2
    end
end

function calculateRoad()

    -- calculate the # of segments
    -- in the road before each corner.
    -- this is useful for spacing things
    -- out evenly along the road

    local sumct=0
    local i = 1
    while i <= #road do
    local corner = road[i]
    corner.sumct = sumct
    sumct = sumct + corner.ct
    i = i + 1
    end
    npcSprite.maxRoadSegments=sumct
end

function getsumct(cnr,seg)
	return road[cnr].sumct+seg-1
end

-- camera
   local camcnr,camseg=1,1
   local camx,camy,camz=0,0,0

-- function to turning 2D into psuedo 3D
function project(x,y,z)
	local scale=200/z
	return x*scale+200,y*scale+120,scale
end

-- advance along road, goes trhough segments of each corners and jump to another corner after last segment

function advance(cnr,seg)
	seg += 1
	if (seg > road[cnr].ct) then
		seg = 1
		cnr += 1
		if (cnr > #road) then
			cnr = 1
		end
	end
	return cnr, seg
end

function skew(x,y,z,xd,yd)
	return x+z*xd,y+z*yd,z
end

function drawScreen()

    -- cleaning screen from sprites added in previous animation frame
    --gfx.sprite.removeSprites(drawnSprites)

	-- direction
	local camang=camz*road[camcnr].tu
	local xd,yd,zd=-camang,0,1
	
	-- skew camera to account for direction 
	local cx,cy,cz=skew(camx,camy,camz,xd,yd)
	
	-- cursor, relative to skewed camera
	local x,y,z=-cx,-cy+2,-cz+2
	
	-- road position
	local cnr,seg=camcnr,camseg
	
	 -- previous projected position
	 local ppx,ppy,pscale=project(x,y,z)


     -- array of sprites to draw
    local spritesToDraw={} 
	
	-- draw forward
    for i=1, 30 do
		-- project (turns x, y, z into 3D using a scale)
		local px,py,scale=project(x,y,z)
		
        -- draw road
        local sumct=getsumct(cnr,seg)
        drawroad(px,py,scale,ppx,ppy,pscale,sumct)

        -- add background sprites
        addbgsprite(spritesToDraw,sumct,road[cnr].bgl,-1,px,py,scale)
        addbgsprite(spritesToDraw,sumct,road[cnr].bgr,1,px,py,scale)
        addNPCsprite(spritesToDraw,sumct,npcSprite,0,px,py,scale)


        --[[ add background sprites to draw
        if (sumct%6)==0 then
        -- left tree
        local tx,ty=px-4.5*scale,py
        local tw,th=1.5*scale,3*scale
        local tscale=0.06*scale
        table.insert(spritesToDraw,{x=tx,y=ty,w=tw,h=th,s=tscale})
        
        -- right tree
        tx=px+6.5*scale
        table.insert(spritesToDraw,{x=tx,y=ty,w=tw,h=th,s=tscale})
       end]]--

        
		-- move forward
		x+=xd
		y+=yd
		z+=zd
		
		-- turn, cnr means corners which are elements from road table
		xd+=road[cnr].tu
		
		-- advance along road, goes trhough segments of each corners and jump to another corner after last segment
		cnr, seg = advance(cnr,seg)

        -- track previous projected position
		ppx,ppy,pscale = px, py, scale

	end 

    -- draw background sprites in reverse order
    for i=#spritesToDraw,1,-1 do
    drawbgsprite(spritesToDraw[i])
    end
end



function addbgsprite(spritesToDraw,sumct,bg,side,px,py,scale)
    if(not bg) then return end
    if((sumct%bg.spc)~=0) then return end -- checks if sprite should be drawn or not
   
    -- find position
    px+=3*scale*side
    if bg.pos then
       px+=bg.pos[1]*scale*side
       py+=bg.pos[2]*scale
       --py+=-10
    end
   
    -- pico8 calculate size for proper placement
    local w,h=bg.siz[1]*scale,bg.siz[2]*scale
   
    -- flip horizontally?
    local flp=side>0 and bg.flpr
   
    -- set proper scaling
    local tscale=bg.bgscl*scale

    -- add to sprite array
        table.insert(spritesToDraw,{
            x=px,y=py,w=w,h=h,
            img=bg.img,
            s=tscale,
            flp=flp
        })
end

function addNPCsprite(spritesToDraw,sumct,bg,side,px,py,scale)
    if(not npcSprite) then return end
    --if ((bg.roadPosition-sumct)<=0) or ((bg.roadPosition-sumct)>1) then return end
    if not (((bg.roadPosition-sumct)==0)) then return end
    -- find position
    --px+=3*scale*side
    px-=1.5*scale
    if bg.pos then
       px+=bg.pos[1]*scale*side
       py+=bg.pos[2]*scale
    end
   
    -- pico8 calculate size for proper placement
    local w,h=bg.siz[1]*scale,bg.siz[2]*scale
   
    -- flip horizontally?
    local flp=side>0 and bg.flpr
   
    -- set proper scaling
    local tscale=bg.bgscl*scale

    -- add to sprite array
        table.insert(spritesToDraw,{
            x=px,y=py,w=w,h=h,
            img=bg.img,
            s=tscale,
            flp=flp
        })
end


function drawbgsprite(s)
    
-- checks whhich image should be put into drawing function
        if s.img == "left_sakura_tree" then leftSakuraTreeImage:drawScaled((s.x-s.w/2), (s.y-s.h), s.s) end
        if s.img == "right_sakura_tree" then rightSakuraTreeImage:drawScaled((s.x-s.w/2), (s.y-s.h), s.s) end
        if s.img == "left_turn_sign" then leftTurnSignImage:drawScaled((s.x-s.w/2), (s.y-s.h), s.s) end 
        if s.img == "right_turn_sign" then rightTurnSignImage:drawScaled((s.x-s.w/2), (s.y-s.h), s.s) end 
        if s.img == "car" then playerCarImage:drawScaled((s.x-s.w/2), (s.y-s.h), s.s) end 
    end  

    --local image = gfx.image.new(s.img)
    --local spriteToBeDrawn = gfx.sprite.new(image)
    --if s.flp then spriteToBeDrawn:setImageFlip(gfx.kImageFlippedX) end 
    --spriteToBeDrawn:setScale(s.s)
    --spriteToBeDrawn:moveTo(s.x-s.w/2,s.y-s.h)
    --spriteToBeDrawn:add()
    --table.insert(drawnSprites, spriteToBeDrawn)
    --end


function drawroad(x1,y1,scale1,x2,y2,scale2,sumct)

    if(math.floor(y2)<math.ceil(y1))then return end
   
    -- draw ground
    gfx.setColor(gfx.kColorBlack)
    gfx.setPattern({ 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0 }) 
    if((sumct%6)>=3) then 
        gfx.setColor(gfx.kColorWhite) 
        gfx.setPattern({ 0xee, 0xbb, 0xee, 0xbb, 0xee, 0xbb, 0xee, 0xbb })  
    end
    gfx.fillRect(0,math.ceil(y1),400, (y1-y2))
    
    -- main road
       local w1,w2=3*scale1,3*scale2            -- start and end widths
       gfx.setColor(gfx.kColorBlack)
       gfx.setPattern({ 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 })
       --drawTrapezium(x1,y1,w1,x2,y2,w2)
       gfx.fillPolygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2)
       
    -- center line markings
    gfx.setColor(gfx.kColorBlack)
    if (sumct%4)==0 then
        local mw1, mw2 = .1*scale1, .1*scale2
        gfx.fillPolygon(x1-mw1, y1, x1+mw1, y1, x2+mw2, y2, x2-mw2, y2)
        end
    
    -- shoulders
    gfx.setColor(gfx.kColorWhite)
    if((sumct%2)==0) then gfx.setColor(gfx.kColorBlack) end
    local sw1,sw2= 0.2*scale1, 0.2*scale2

    drawTrapezium(x1-w1,y1,sw1,x2-w2,y2,sw2)
    --gfx.fillPolygon(x1-w1, y1, x1+sw1, y1, x2+sw2, y2, x2-sw2, y2)
    drawTrapezium(x1+w1,y1,sw1,x2+w2,y2,sw2) 
    --gfx.fillPolygon(x1-sw1, y1, x1+sw1, y1, x2+sw2, y2, x2-sw2, y2)]]--
   
end

function drawTrapezium(x1,y1,w1,x2,y2,w2)
    -- draw a trapezium by stacking horizontal lines
       local h=y2-y1 -- height
       local xd,wd=(x2-x1)/h,(w2-w1)/h -- width and x deltas
       local x,y,w=x1,y1,w1  -- current position
    local yadj=math.ceil(y)-y
    x+=yadj*xd
    y+=yadj
    w+=yadj*wd 
    while y<y2 do
     -- draw horizontal line
     gfx.drawLine(x-w,y,x+w,y)
           x+=xd
           y+=1
           w+=wd
       end
   end

function roadUpdate()
	--camz+=0.3 -- speed of the car
    camz+=playerCarSpeed
	if camz>1 then
	 camz-=1
	 camcnr,camseg=advance(camcnr,camseg)
	end
end


-- adds and removes speed value which is then used in roadUpdate()
function speedingPlayersCar()
    if pd.buttonIsPressed(pd.kButtonA) and playerCarSpeed<=0.6 then
        playerCarSpeed+=0.03 
    elseif playerCarSpeed>0 then playerCarSpeed-=0.01 
    elseif playerCarSpeed<=0 then playerCarSpeed=0
    end
end


-- car and cat drivers
local sprite = gfx.sprite.new(playerCarImage)
sprite:setZIndex(32767)
--sprite:setScale(1)
sprite:moveTo(250, 210)
sprite:add()

local i=0

function pd.update()
    gfx.clear()
    npcDrive()
    speedingPlayersCar()
    calculateRoad()
    roadUpdate()
	gfx.sprite.update()
	pd.drawFPS(0,0)
    i +=1
    if i==nil then pd.stop()end
end

I will be very thankful if any of You will check how updated version runs on the device :smile: In the meantime, author of original pico8 tutorial answered my questions about method for drawing NPC cars on the road. Here is what he proposed:

So basically the issue is that the road drawing currently only projects at the end of each segment. Which works fine for the background objects (trees etc), as they are aligned with the segment boundaries, but not for the NPC car, which can be midway along a segment.
You need to do a couple of things:
– There's a check in the addNPCsprite as to whether the NPC car is on the segment being drawn. I think this needs to be moved into the main drawing loop.
– Then when you detect the NPC car is on the segment, you need to calculate its 3D screen position.
– So you get the fractional distance along the segment, which I think is the fractional part of npcsprite.roadPosition (say call that "f")
– Then you need to calculate where that position along the current segment is, something like:
local npcx,npcy,npcz=x+fxd,y+fyd,z+f*zd
– Then call project that position: e.g.:
local npcpx,npcpy,npcscale=project(npcx,npcy,npcz)
– The pass that projected position to your addNPCsprite routine, which should (hopefully) now draw the car in the right place and scale.

It has a lot of sense for me and I though that I understand, and made a function which I initiated in the drawScreen() function just before that "Draw forward" loop.

function calculateNPCposition(npcSprite,x,y,z,xd,yd,zd,cnr,seg)
        local sumct=getsumct(cnr,seg)
        if not (((npcSprite.roadPosition-sumct)==0)) then return end
        local f = npcSprite.segmentFraction
        local npcx,npcy,npcz=x+f*xd,y+f*yd,z+f*zd
        npcSprite.npcpx,npcSprite.npcpy,npcSprite.npcscale=project(npcx,npcy,npcz)
end 

Also I modified npcSprite to be like this:

local npcSprite = {
    img = "images/car",
    siz={1.0,1.0},
    bgscl=0.011,
    roadPosition=1,
    segmentFraction=0,
    maxRoadSegments=0, -- moment when road position of the NPC should be reset to 1. A value is added in calculateRoad() function
    speed=1,
    npcx=0,
    npcy=0,
    npcscale=0,
}

And moving NPC function to be like this:

function npcDrive()
    if (npcSprite.roadPosition > npcSprite.maxRoadSegments) then npcSprite.roadPosition = 1 
    elseif npcSprite.segmentFraction >= 1 then npcSprite.segmentFraction -= 1 npcSprite.roadPosition += 1
    else npcSprite.segmentFraction += npcSprite.speed
    end
end

Unfortunately, after passed modified values to the drawing function I had weird results. So probably I messed something strongly somewhere. Maybe You have a clue how to fix it? I'm attaching current version of my game package in the post, but without this NPC car changes.
Koko & Kebi Racing (image wersion).zip (55.0 KB)

Yep. That did the trick. This now runs on solid 19-22 fps.
Way better than before!

1 Like

Great! And thank you for testing! Also huge thanks for @daprice for that hint! This is giving me hope :smile: Of course there are probably ways to optimize more, but on the basic level its cool. Maybe I wont implement hills or some other advanced effects to save some power, but now I need to focus on that NPC blinking problem. I cant figure it out though :sweat_smile:

1 Like

Jesus Christ, I think I did it :hear_no_evil: Now it's blinking much less.
Koko & Kebi Racer with smooth NPC drivers

I think it's time for more optimization and the rest of the stuff - AI, collision detection, etc. I guess more obstacles will appear :sweat_smile:

Here is the recent package If someone wants to try it on the device:
Karting - images - NPC car.zip (54.6 KB)

3 Likes

Phenomenal progress! Well done — keep it up!

1 Like

Thank you! :pray:t2: My stubbornness is one thing, but this great community support is that, what helps me to keep going. And after all it gives a lot of satisfaction. I always thought that I’m too dumb for programming and game development, even while I finished It studies and work everyday as an UX designer :sweat_smile: It turns out that I was just procrastinating.

3 Likes