I need help for optimaizetion

SDK Version: 1.13.2(Mac)

I'm making a shoot 'em up game.
I'm currently struggling with optimization. Please help me.
I want to keep a stable frame rate while keeping as many enemy planes and bullets on screen as possible.
Any tips for this?
Mar-14-2023 18-20-04

If you are coding in Lua, this thread includes some hints you can use to optimize your shoot'em up:

2 Likes

Thank you Daeke! I'll check it.
Your comment is too helpful to me.

Would using an image table help improve frames?
I tried using an image table as a test, but it doesn't seem to have changed much whether I'm using it incorrectly.
If you have any experience using image tables, does anyone have any stories to tell?

I'ts hard to say without knowing more about how you're using the API, but if you're already using images I don't think you'd see any difference with image tables--they're just images packed together in a single file. The first thing you'll want to do is sample the game with the profiler and see what code is taking the most CPU. It looks like there's some talk about that in the thread that Daeke posted, but let me know if you need any help with that!

2 Likes

I'm testing it by deploying 10 enemy planes and changing what I update.
Whenever the update items increase, the proportion of games in device info increases exponentially.
Would you give me some advice?


Here's what the update checks for:

  1. Is it on the screen?
  2. Warp (changes position when moving farther than a certain distance)
  3. Animation
  4. hit check (Not use CollideRect just distance check from player)
  5. Events (attack cooldown, fuel level check, etc.)

And This is my code

import "player"
import 'gameManager/soundManager'

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

class('Enemy').extends(gfx.sprite)

local imageTable = gfx.imagetable.new('images/base')
-- local animationLoop = gfx.animation.loop.new(100, imageTable)

local enemyImages = {}
for i = 1, 2 do
	enemyImages[i] = imageTable[i + 18]
end

local dieImages = {}
for i = 1, 5 do
    dieImages[i] = imageTable[i + 31]
end


function Enemy:init()
    self.enemyImages = enemyImages
    self.dieImages = dieImages
    --"basic", "driller", "kidnapper", "kamikaze", "three", "jumper", "ship", "mother", "octo"
    self.type = "basic"
    self.target = nil
    self.bulletPools = ufoBullets
    self.frame = 1
    self.inScreen = false
    self.warpTimer = 0
    self.canWarp = false
    self.used = false;
    self.wasHit = false;
    self.moveSpeed = 0
    self.speedUpvol = 2.0
    self.acc = 0.1
    self.aniTime = 0
    self.attackTime = 0
    self.gasTime = 0
    self.gas = 20000
    self.noGas = nil
    self.targetCheckGab = 0
    self.targetCheckTime = 10
    self.attackGap = 5000
    self.scoreVol = 10
    self.maxLife = 0
    self.spawnGab = 5000
    self.life = 0
    self.dir_x = 0
    self.dir_y = 0
    self:setZIndex(1900)
    self:checkImage(self.enemyImages)
    local width, height = self:getSize()
    local reWidth, reHeight = width * 0.7, height * 0.7
    self:setCollideRect((width-reWidth)*0.5, (height-reHeight)*0.5, reWidth, reHeight)
    self:setGroups(10)
    --table.insert(enemy, self)
end

function Enemy:enable(x, y)
    self.target = playerInstance
    self.wasHit = false
    self.targetAngle = 0
    self.inScreen = false
    self.canWarp = false
    self.used = true;
    self.aniTime = pd.getCurrentTimeMilliseconds()
    self.gasTime = self.aniTime
    self.attackTime = self.aniTime
    self.noGas = false
    self.targetCheckGab = self.aniTime
    self.life = self.maxLife
    self.dir_x = -vectorX
    self.dir_y = -vectorY
    self.angle_homing = 0
    self.distance = 100
    self:moveTo(x, y)
    self:setSize(36, 36)
    self:setVisible(true)
    self:add()
end

--Do animation
function Enemy:doAnimation()
    if self.life < 1 then
        if timer(self.aniTime, 50) then
            self.aniTime = pd.getCurrentTimeMilliseconds()
            self.frame += 1
            if self.frame >= 5 then
                self:die(self.x, self.y)
            end
            self:checkImage(self.dieImages)
        end
    else
        if timer(self.aniTime, 200) then
            self.aniTime = pd.getCurrentTimeMilliseconds()
            self.frame += 1
            if self.frame > 2 then
                self.frame = 1
            end
            self:checkImage(self.enemyImages)
        end
    end
end

--Change image by self.frame
function Enemy:checkImage(img)
    local myImage = img
    self:setImage(myImage[self.frame])
end

--Remove an enemy
function Enemy:disable()
    self.used = false;
    activeUFOCount -= 1
    -- print("적기: "..activeUFOCount)
    self:remove()
    -- if self.type ~= "kidnapper" and self.type ~= "octo" then
    --     enemySpawn(self.spawnGab, self.type)
    -- end
    --"remove")
end

--Process after enemy death
function Enemy:die(x, y)
    self.used = false
    enemyDieCheck()
    -- makeFx(x, y, 5)
    self:remove()
    -- if self.type ~= "kidnapper" and self.type ~= "octo" then
    --     enemySpawn(self.spawnGab, self.type)
    -- end
end

--Get damage
function Enemy:getDamage(damage)
    if self.life < 1 then
        return
    end

    self.wasHit = true
    local function hit()
        self.wasHit = false
        self:setVisible(true)
    end

    --print("hit")
    self:setVisible(false)
    SoundManager:playSound(1.0, 1, SoundManager.kSoundHit)
    self.life -= damage

    if self.life < 1 then
        -- self:setSize(36, 36)
        addScore(self.scoreVol)
        if self.type == "mother" then
            self:die(self.x, self.y)
        else
            self.frame = 1
            self.aniTime = pd.getCurrentTimeMilliseconds()
            makeAlien(self.x, self.y)
            self:setVisible(true)
        end
        
        
        -- self:die(self.x, self.y)
    else
        pd.timer.performAfterDelay(10, hit)
    end
end

function Enemy:targetCheck()
    local currentX, currnetY = self:getPosition() --My position
    local targetX, targetY = playerInstance:getPosition() --Target position
    
    local distance_x = targetX  - currentX
    local distance_y = targetY  - currnetY

    local angle_self   = math.atan(self.dir_y , self.dir_x);
    local angle_target = math.atan(distance_y, distance_x);

    local angle_abs = math.abs(angle_target - angle_self);
    
    local angle_diff = 0;
    
    if angle_abs < 6.28318548 - angle_abs then
        angle_diff = angle_target - angle_self
    else
        if angle_target - angle_self > 0 then
            angle_diff = -6.28318548 + (angle_target - angle_self)
        else
            angle_diff= 6.28318548 + (angle_target - angle_self)
        end
    end

    self.angle_homing = angle_self + (angle_diff * self.acc)

    if math.abs(self.angle_homing) > 3.14159274 then
        if self.angle_homing > 0 then
            self.angle_homing += -6.28318548
        else
            self.angle_homing += 6.28318548
        end
    end
end

--Attack
function Enemy:fire()
    local bullet = ObjPools:getObj(self.bulletPools)
    if bullet == nil then
        return
    end
    local angle = getRadian(posX, posY, self.x, self.y)
    local degree = angle * 180 / math.pi
    local vX = math.cos(degree * math.pi/180)
    local vY = math.sin(degree * math.pi/180)
    bullet:enable(self.x, self.y, vX, vY, degree)
end

--Gas check
function Enemy:gasCheck()
    if timer(self.gasTime, self.gas) then
        self.noGas = true
    end
end

--Attack check
function Enemy:attackCheck()
    if self.attackGap == 0 or gameOver then
        return
    end

    if timer(self.attackTime, self.attackGap) then
        local gapX, _ = self:getSize()
        self.attackTime = pd.getCurrentTimeMilliseconds()

        if onScreen(self.x, self.y, gapX) then
            self:fire()
        end
    end
end

--Event setting
function Enemy:event()

    if self.type == "kidnapper" or self.type == "jumper" then
        return
    end

    if self.noGas == false then
        self:gasCheck()
        if enemyAttackCheck() then
            -- self:attackCheck()
        end
    end
end

--Moving
function Enemy:move()
    if gameOver == false then
        self:targetCheck()
    end

    self:warpCheck()

    local moveSpeed = deltaTime * self.moveSpeed

    if self.noGas then
        self:moveBy(self.dir_x * moveSpeed * self.speedUpvol, self.dir_y * moveSpeed * self.speedUpvol)
    else
        self.dir_x = math.cos(self.angle_homing);
        self.dir_y = math.sin(self.angle_homing);
        self:moveBy(self.dir_x * moveSpeed, self.dir_y * moveSpeed )
        -- self:moveWithCollisions(self.x + self.dir_x * moveSpeed, self.y + self.dir_y * moveSpeed )
    end
end

--Hit check
function Enemy:hitCheck()
    local distance = getDistance(posX, posY, self.x, self.y)

    if distance <= 18 and gameOver == false then 
        if playerInstance:getInvicible() == false then
            self:getDamage(2)
        end
        getDamage()
    end
end

--Warp check
function Enemy:warpCheck()
    local gapX, _ = self:getSize()
    if onScreen(self.x, self.y, 200 + gapX) then
        if self.canWarp == true then
            self.canWarp = false
            self.warpTimer = 0
            -- print("dont warp")
        end
    else
        if self.noGas or self.dropDown then
            print("delete!!")
            self:disable()
            return
        end

        if self.canWarp == false then
            self.canWarp = true
            self.warpTimer = pd.getCurrentTimeMilliseconds()
        else
            if timer(self.warpTimer, 4000)then
                -- print("warp!!")
                enemyWarp(self)
            end
        end
    end
end

-- Screen check
function Enemy:screenCheck()
      local gapX, _ = self:getSize()
      self.inScreen = onScreen(self.x, self.y, gapX)
      if self.inScreen then
          self:setVisible(true)
        --   self:doAnimation()
          -- print("In")
      else
          -- print("Out")
          self:setVisible(false)
        --   if self.type == "jumper" then
        --       self:doAnimation()
        --   end
      end
end

function Enemy:update()
    Enemy.super.update(self)
    self:doAnimation()
    self:event()

    if self.life < 1 then
        return
    end

    self:move()
    self:hitCheck()
    self:warpCheck()
    self:screenCheck()
end

Have you looked at sampler?

This would give you a clearer idea of what time is going to which function.

Thank you for your tip.