NaaLaa

Full Version: Defender
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Another game I started working on but most likely won't finish. I think I just wanted to implement "wrapped scrolling" - the level is six screens wide, but you can still travel endlessly in any direction. It's an unfinished Defender (Atari 2600) clone Smile

Prevent the green aliens from obducting the dudes. The red spaceships just fly around randomly and shoot at you.

[Image: denaalaafender.jpg]

Edit  Attached the full game (denaalaafender.n7) from a post further down (denaalaafender.zip is the old version).
Very cool!! I have not seen this game in many years! If memory serves correctly, I didn't play very well back then either... lol

Much appreciate the memories!
(01-18-2024, 06:55 PM)johnno56 Wrote: [ -> ]Very cool!!  I have not seen this game in many years!  If memory serves correctly, I didn't play very well back then either... lol

Much appreciate the memories!

Maybe I'll finish it up after all this weekend. Lives, levels and ramped difficulty. I updated the zip because I realized it was quite annoying that the player ship "bounced" when hitting the ground.
"bounce" is good... better than "boom"... lol
Here's a more complete game, now with all assets generated in game. It requires atleast n7 version 24.01.19 Smile

Code:
' Denaalaafender
' --------------
' I've never played Defender. This game is just based on my memory of Youtube videos. For the fun
' of it, all assets in this game are generated by code.
'
' Use the arrow keys to move and spacebar to shoot
' Destroy all enemy spaceships to complete a level
' Try to help the humans from being obducted by the aliens
' You get bonus points for each human alive at the end of a level

include "list.n7"
include "file.n7"

#win32

' Images.
visible vPlayerImage, vPlayerBulletImage
visible vDudeImage, vParachuteImage
visible vGrabberImage, vEnemyImage, vEnemyBulletImage

' Sound effects.
visible vShootSound, vExplosionSound, vSmallExplosionSound, vLargeExplosionSound, vEnemyShootSound

' Top score.
visible vTopScore = 0

' Level.
visible VIEW_H = 120, VIEW_W = 160
visible vLevelWidth = VIEW_W*6, vScroll
visible vStars = List(), vTerrain = List(), vParticles = List()
' Player and score.
visible vPlayer, vPlayerBullets = List()
visible vScore
' Grabbers.
visible vGrabbers = List()
visible vMaxGrabbersOnScreen, vGrabberTimer, vGrabberMinDelay, vGrabberRndDelay
' Enemies.
visible vEnemies = List(), vEnemyBullets = List()
visible vLevelEnemies, vEnemiesLeft, vMaxEnemiesOnScreen, vEnemyTimer, vEnemyMinDelay, vEnemyRndDelay
visible vEnemyShootTimer, vEnemyShootMinDelay, vEnemyShootRndDelay
' Dudes.
visible vDudes = List()

' Create window and disable automatic redraw.
set window "Denaalaafender", VIEW_W, VIEW_H, true, 3
set redraw off

' Create and set save folder.
folder = GetPathCombined(GetAppDataDirectory(), "naalaa7")
CreateDirectory(folder)
saveFilename = GetPathCombined(folder, "denaalaafender.bin")
f = openfile(saveFilename, true)
if typeof(f)
    vTopScore = fread(f, 32)
    free file f
endif

LoadAssets()

do
    if not Title()  end
    level = 0
    lives = 3
    vScore = 0
   
    do
        InitLevel(level, lives)
        scrollOffset = 0
        vScroll = vPlayer.x + width(vPlayerImage)/2 - VIEW_W/2
       
        ' Game loop, use delta time for smoother updates.
        lastTick = clock()
        do
            t = clock()
            dt = (t - lastTick)/1000
            lastTick = t
           
            quit = keydown(KEY_ESCAPE, true)
           
            ' Add grabber?
            if vEnemiesLeft or vEnemies.size > 1
                vGrabberTimer = vGrabberTimer - dt
                if vGrabberTimer <= 0 and vGrabbers.size < vMaxGrabbersOnScreen
                    vGrabberTimer = vGrabberMinDelay + rnd()*vGrabberRndDelay
                    dude = GetGrabableDude()
                    if dude  vGrabbers.Add(Grabber(dude))
                endif
            endif
       
            ' Add regular enemy?
            if vEnemiesLeft
                vEnemyTimer = vEnemyTimer - dt
                if vEnemyTimer <= 0 and vEnemies.size < vMaxEnemiesOnScreen
                    vEnemyTimer = vEnemyMinDelay + rnd()*vEnemyRndDelay
                    vEnemies.Add(Enemy())
                    vEnemiesLeft = vEnemiesLeft - 1
                endif
            endif
           
            ' Add enemy bullet?
            vEnemyShootTimer = vEnemyShootTimer - dt
            if vEnemyShootTimer <= 0
                if AddEnemyBullet(level > 3)
                    vEnemyShootTimer = vEnemyShootMinDelay + rnd()*vEnemyShootRndDelay
                else
                    vEnemyShootTimer = vEnemyShootMinDelay/2
                endif
            endif
       
            ' Update player and sprite lists.
            vPlayer.Update(dt)
            UpdateSprites(vPlayerBullets, dt)
            UpdateSprites(vDudes, dt)
            UpdateSprites(vGrabbers, dt)
            UpdateSprites(vEnemies, dt)
            UpdateSprites(vEnemyBullets, dt)
            UpdateSprites(vParticles, dt)
         
            ' Adjust view depending on player's direction.
            if vPlayer.cel = 0  wso = -0.6*VIEW_W*0.5
            else  wso = 0.6*VIEW_W*0.5
            i = 1.5*dt
            scrollOffset = scrollOffset*(1 - i) + wso*i
            vScroll = vPlayer.x + width(vPlayerImage)/2 - VIEW_W/2 + scrollOffset
           
            ' Draw background.
            set color 0, 0, 0
            cls
            set color 97, 29, 0
            for i = 0 to vTerrain.size - 1  DrawLine(vTerrain[i][0], vTerrain[i][1],
                    vTerrain[i][2], vTerrain[i][3])
            for i = 0 to vStars.size - 1  DrawPixel(vStars[i].x, vStars[i].y, 79, 79, 79)
       
            ' Draw sprites.
            DrawSprites(vDudes)
            DrawSprites(vGrabbers)
            DrawSprites(vEnemyBullets)
            DrawSprites(vEnemies)
            DrawSprites(vPlayerBullets)
            DrawSprites(vParticles)
            vPlayer.Draw()
       
            ' Draw map.   
            DrawMap()
           
            ' Draw player stamina.
            x = 10
            y = 3
            set color 102, 102, 102
            draw rect x, y, 19, 4
            if vPlayer.stamina > 0
                set color 193, 224, 254
                for i = 0 to vPlayer.stamina - 1  draw rect x + 2 + i*4, y + 1, 3, 2, true
            endif
           
            ' Draw enemies left.
            x = VIEW_W - 28
            y = 3
            set color 102, 102, 102
            draw rect x, y, 18, 4
            w = ceil(16*(vEnemiesLeft + vEnemies.size)/vLevelEnemies)
            set color 181, 50, 32, 128
            draw rect x + 1, y + 1, w, 2, true
           
            ' Score
            set caret VIEW_W/2, VIEW_H - fheight() + 3
            set color 102, 102, 102
            center vScore
         
            redraw
            wait 1
        until quit or (vPlayer.stamina < 0 or (vEnemiesLeft = 0 and vEnemies.size = 0)) and
                vParticles.size = 0
               
        ' Quit or show mesage.
        set color 0, 0, 0
        cls
        if quit
            break
        elseif vPlayer.stamina < 0
            lives = lives - 1
            set caret VIEW_W/2, (VIEW_H - fheight())/2
            set color 181, 50, 32           
            if lives <= 0
                ' New top score?
                if vScore > vTopScore
                    vTopScore = vScore
                    f = createfile(saveFilename, true)
                    if typeof(f)
                        write file f, vTopScore, 32
                        free file f
                    endif
                endif
                center "GAME OVER!"
            else
                center "LIFE LOST!"
            endif
        else
            level = level + 1
            bonus = vDudes.size*100
            vScore = vScore + bonus
            set caret VIEW_W/2, VIEW_H/2 - fheight()
            set color 93, 239, 48           
            center "LEVEL COMPLETE!"
            set color 254, 254, 254
            x = (VIEW_W - (width(vDudeImage) + fwidth(" x " + vDudes.size + " = " + bonus)))/2
            draw image vDudeImage, x, VIEW_H/2 + fheight()
            x = x + width(vDudeImage)
            set caret x, VIEW_H/2 + fheight()
            write " x " + vDudes.size + " = " + bonus
        endif
        ' Show message.
        redraw
        wait 4000
    until lives <= 0
loop

' LoadAssets
' ----------
function LoadAssets()
    ' Images.
    vPlayerImage = ImageFromMonoData([
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1],
        [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
        [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]],
        254, 254, 254, 1, 2)
    vPlayerBulletImage = ImageFromMonoData([
        [0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 1, 1, 1, 1, 1, 1, 0],
        [1, 1, 1, 1, 1, 1, 1, 1],
        [0, 1, 1, 1, 1, 1, 1, 0]],
        189, 191, 0, 1, 2)
    vDudeImage = ImageFromMonoData([
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 1, 0, 0],
        [0, 1, 1, 1, 0],
        [1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1],
        [0, 0, 1, 0, 0],
        [0, 1, 0, 1, 0],
        [1, 0, 0, 0, 1]],
        161, 27, 205, 1, 1)
    vParachuteImage = ImageFromMonoData([
        [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
        [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0],
        [0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0],
        [0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0],
        [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1],
        [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
        [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
        [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0]],
        100, 176, 254, 1, 1)
    vGrabberImage = ImageFromMonoData([
        [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
        [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
        [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1],
        [0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
        [0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
        [0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0]],
        0, 144, 50, 4, 1)
    vEnemyImage = ImageFromMonoData([
        [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
        [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]],
        181, 50, 32, 4, 1)
    vEnemyBulletImage = ImageFromMonoData([
        [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
        [1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1],
        [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]],
        251, 195, 254, 4, 1)

    ' Sound effects.
    vShootSound = CreateSquareSfx(0.25, 500, 100, 0.75, 8000)
    vExplosionSound = CreateNoiseSfx(0.5, 0.9, 0.1, 8000)
    vLargeExplosionSound = CreateNoiseSfx(1.0, 0.5, 0.05, 8000)
    vSmallExplosionSound = CreateNoiseSfx(0.25, 0.75, 0, 8000)
    vEnemyShootSound = CreateSineSfx(0.3, 300, 50, 0.5, 8000)
endfunc

' Title
' -----
function Title()
    set color 0, 0, 0
    cls
    set caret VIEW_W/2, VIEW_H/2 - fheight()*2
    set color 254, 254, 254
    center "DENAALAAFENDER"
    center "TOP SCORE"
    center vTopScore
    center
    set color 100, 176, 254
    center "PRESS SPACEBAR"
    redraw
    do
        quit = keydown(KEY_ESCAPE, true)
        wait 16
    until quit or keydown(KEY_SPACE, true)
    return not quit
endfunc

' InitLevel
' ---------
function InitLevel(level, lives)
    ' Create some stars and moutains.
    randomize level
    vStars.Clear()
    for i = 1 to 100  vStars.Add([x: rnd(vLevelWidth), y: rnd(VIEW_H - 40)])
    vTerrain.Clear()
    for i = 0 to 5
        x = i*VIEW_W
        h = 10 + rnd(30)
        vTerrain.Add([x, VIEW_H, x + VIEW_W/2, VIEW_H - h])
        vTerrain.Add([x + VIEW_W/2, VIEW_H - h, x + VIEW_W, VIEW_H])
    next
   
    ' Setup player.
    vPlayer = Player(vLevelWidth/2, VIEW_H/2)
    vPlayer.dx = 0
    vPlayer.dy = 0
    vPlayerBullets.Clear()
   
    ' Grabber settings and list.
    vGrabberMinDelay = max(5 - level*0.5, 3)
    vGrabberRndDelay = max(4 - level*0.25, 2)
    vGrabberTimer = vGrabberMinDelay
    vMaxGrabbersOnScreen = 5 + level
    vGrabbers.Clear()
   
    ' Enemies.
    vEnemyMinDelay = max(5 - level*0.5, 2)
    vEnemyRndDelay = max(4 - level*0.25, 2)
    vEnemyTimer = vEnemyMinDelay + vEnemyRndDelay
    vMaxEnemiesOnScreen = 5 + level
    vLevelEnemies = 5 + int(level*1.5)
    vEnemiesLeft = vLevelEnemies
    vEnemies.Clear()
   
    ' Enemy bullets timers.
    vEnemyShootMinDelay = max(4 - level*0.5, 3)
    vEnemyShootRndDelay = max(3 - level*0.25, 1)
    vEnemyShooterTimer = vEnemyShootMinDelay
    vEnemyBullets = List()
   
    ' Place dudes.
    vDudes.Clear()
    for i = 1 to 10
        x = rnd(vLevelWidth) + 0.5
        for j = 0 to vTerrain.size - 1; t = vTerrain[j]
            if x >= t[0] and x <= t[2]
                y = t[1] + (x - t[0])*(t[3] - t[1])/(t[2] - t[0])
                vDudes.Add(Dude(x, y))
                break
            endif
        next
    next
   
    ' Particles
    vParticles.Clear()
   
    ' Show message.
    set color 0, 0, 0
    cls
    set caret VIEW_W/2, VIEW_H/2 - fheight()
    set color 254, 254, 254
    center "LEVEL " + (level + 1)
    center
    center "LIVES " + lives
    redraw
    wait 3000
endfunc

' Player
' ------
function Player(x, y)
    s = Sprite(vPlayerImage, 0)
    s.stamina = 4
    s.dx = 0
    s.dy = 0
    s.canShoot = true
    s.blinkTimer = 0
    s.SpriteDraw = s.Draw
    s.Center(x, y)
   
    ' Update
    ' ------
    s.Update = function(dt)
        if this.stamina < 0  return
       
        ' Collision, just blink for a while.
        if this.blinkTimer <= 0
            if this.HitByAnySprite(vEnemies, false) or
                    this.HitByAnySprite(vGrabbers, false) or
                    this.HitByAnySprite(vEnemyBullets, true)
                this.stamina = this.stamina - 1
                if this.stamina >= 0
                    this.blinkTimer = 3
                    play sound vSmallExplosionSound
                else
                    AddExplosion(this.x + this.w/2, this.y + this.h/2, 254, 254, 254, 40)
                    play sound vLargeExplosionSound
                    return
                endif
            endif
        else
            this.blinkTimer = max(this.blinkTimer - dt, 0)
        endif
   
        ' Move.
        if this.dx > 0  this.dx = max(this.dx - 100*dt, 0)
        elseif this.dx < 0  this.dx = min(this.dx + 100*dt, 0)
        if this.dy > 0  this.dy = max(this.dy - 100*dt, 0)
        elseif this.dy < 0  this.dy = min(this.dy + 100*dt, 0)
        if keydown(KEY_LEFT)
            this.dx = max(this.dx - 200*dt, -200)
            this.cel = 0
        elseif keydown(KEY_RIGHT)
            this.dx = min(this.dx + 200*dt, 200)
            this.cel = 1
        endif
        if keydown(KEY_UP)  this.dy = max(this.dy - 200*dt, -200)
        if keydown(KEY_DOWN)  this.dy = min(this.dy + 200*dt, 200)
        this.x = (this.x + this.dx*dt)%vLevelWidth
        this.y = this.y + this.dy*dt
        if this.y < 0
            this.y = 0
            this.dy = 0
        elseif this.y + this.h > VIEW_H
            this.y = VIEW_H - this.h
            this.dy = 0
        endif
       
        ' Shoot, require release of spacebar between shots.
        if keydown(KEY_SPACE)
            if this.canShoot
                this.canShoot = false
                if this.cel = 0  dx = -300
                else  dx = 300
                vPlayerBullets.Add(Bullet(vPlayerBulletImage,
                        this.x + this.w/2, this.y + 4,
                        dx, 0))
                play sound vShootSound, 0.25
            endif
        else
            this.canShoot = true
        endif
    endfunc
   
    ' Draw
    ' ----
    s.Draw = function()
        if this.stamina >= 0 and this.blinkTimer%0.2 < 0.1  this.SpriteDraw()
    endfunc
   
    return s
endfunc

' Bullet
' ------
function Bullet(img, x, y, dx, dy)
    s = Sprite(img, 0)
    s.dx = dx
    s.dy = dy
    s.Center(x, y)
   
    ' Update
    ' ------
    s.Update = function(dt)
        this.cel = (this.cel + dt*15)%cels(this.img)
        this.x = this.x + this.dx*dt
        this.y = this.y + this.dy*dt
        this.x = this.x%vLevelWidth
        return this.Visible()
    endfunc
   
    return s
endfunc

' Dude
' ----
' Stand stills, gets obducted and uses a parachute ...
function Dude(x, y)
    s = Sprite(vDudeImage, 0)
    s.baseY = int(y - s.h)
    s.dy = 0
    s.grabber = unset
    s.SetPosition(x - s.w/2, s.baseY)
   
    s.SetGrabber = function(grabber)
        this.grabber = grabber
    endfunc
   
    ' Update
    ' ------
    s.Update = function(dt)
        if this.grabber
            this.y = this.grabber.y + this.grabber.h
        elseif this.y < this.baseY
            this.y = min(this.y + 25*dt, this.baseY)
        endif
        return true
    endfunc
   
    ' Draw
    ' ----
    s.Draw = function()
        set color 255, 255, 255
        DrawImage(this.img, this.x, this.y, 0)
        if this.y < this.baseY and not this.grabber
            DrawImage(vParachuteImage, this.x - 4, this.y - height(vParachuteImage), 0)
        endif
    endfunc
   
    return s
endfunc

' Grabber
' -------
' Obducts dudes.
function Grabber(dude)
    s = Sprite(vGrabberImage, 0)
    s.dude = dude
    s.SetPosition(dude.x -2, -8)
   
    ' Update
    ' ------
    s.Update = function(dt)
        ' Die?
        if this.HitByAnySprite(vPlayerBullets, true)
            this.dude.grabber = unset
            play sound vExplosionSound
            AddExplosion(this.x + this.w/2, this.y + this.h/2, 0, 144, 50, 40)
            return false
        endif
       
        ' If the dude's grabber has been set, the grabber should try to leave the screen.
        if this.dude.grabber
            this.cel = 3
            this.y = this.y - 10*dt
            if this.y < -(this.h + this.dude.h)
                vDudes.Remove(this.dude)
                return false               
            endif
        ' Grabber has yet not picked up its targeted dude.
        else
            this.cel = (this.cel + 4*dt)%4       
            this.y = this.y + 15*dt
            ' Grab dude?
            if this.y + this.h >= this.dude.y
                this.dude.grabber = this
            endif
        endif
     
        return true
    endfunc
   
    return s
endfunc

' GetGrabableDude
' ---------------
' Return a dude that is free to grab or unset if none was found.
function GetGrabableDude()
    if vDudes.size
        list = []
        for i = 0 to vDudes.size - 1
            ok = true
            if vGrabbers.size  for j = 0 to vGrabbers.size - 1  if vGrabbers[j].dude = vDudes[i]
                ok = false
                break
            endif
            if ok  list[sizeof(list)] = vDudes[i]
        next
        if sizeof(list)  return list[rnd(sizeof(list))]
    endif
    return unset
endfunc

' Enemy
' -----
' Enemies appear at random positions and move around randomly.
function Enemy()
    s = Sprite(vEnemyImage, 0)
   
    ' Position.
    s.x = rnd(vLevelWidth)
    s.y = -s.h
    ' Movement and wanted movement, for smoothness.
    s.dx = 0
    s.dy = 0
    s.wdx = 0
    s.wdy = 0
    ' Timer until next turn.
    s.turnTimer = 0
   
    ' SetRandomDirection
    ' ------------------
    s.SetRandomDirection = function()
        this.turnTimer = 2 + rnd()*2
        ' Limit angle range based on vertical position.
        if this.y < VIEW_H/4  a = 180 + rnd(180)
        elseif this.y > 3*VIEW_H/4  a = rnd(180)
        else  a = rnd(360)
        this.wdx = cos(rad(a))*50
        this.wdy = sin(rad(a))*50
    endfunc
   
    ' Update
    ' ------
    s.Update = function(dt)
        ' Die?
        if this.HitByAnySprite(vPlayerBullets, true)
            play sound vExplosionSound
            AddExplosion(this.x + this.w/2, this.y + this.h/2, 181, 50, 32, 40)
            vScore = vScore + 25
            return false
        endif
       
        i = 1.5*dt
        this.dx = this.dx*(1 - i) + this.wdx*i
        this.dy = this.dy*(1 - i) + this.wdy*i
        this.x = (this.x + this.dx*dt)%vLevelWidth
        this.y = this.y + this.dy*dt
        if this.y < 0
            this.wdy = |this.wdy|
        elseif this.y + this.h > VIEW_H
            this.y = VIEW_H - this.h
            this.dy = 0
            this.wdy = -|this.wdy|
        endif
       
        this.turnTimer = this.turnTimer - dt
        if this.turnTimer <= 0  this.SetRandomDirection()
        this.cel = (this.cel + 4*dt)%4
       
        return true
    endfunc
   
    s.SetRandomDirection()
   
    return s
endfunc

' AddEnemyBullet
' --------------
' Add enemy bullet.
function AddEnemyBullet(grabbersMayShoot)
    ' Create a list of visible enemies and grabbers.
    list = []
    if vEnemies.size  for i = 0 to vEnemies.size - 1  if vEnemies[i].Visible()
        list[sizeof(list)] = vEnemies[i]
    endif
    if grabbersMayShoot
        if vGrabbers.size  for i = 0 to vGrabbers.size - 1  if vGrabbers[i].Visible()
            list[sizeof(list)] = vGrabbers[i]
        endif
    endif
    ' Pick one if list isn't empty.
    if sizeof(list)
        e = list[rnd(sizeof(list))]
        px = vPlayer.x + vPlayer.w/2
        py = vPlayer.y + vPlayer.h/2
        ex = e.x + e.w/2
        ey = e.y + e.h/2
        dy = py - ey
        ' Shortest horizontal distance with wrapping concidered.
        dx = px - ex
        dxt = px + vLevelWidth - ex
        if |dxt| < |dx|  dx = dxt
        dxt = px - vLevelWidth - ex
        if |dxt| < |dx|  dx = dxt
       
        ' 60 pixels per second.
        k = 60/sqr(dx*dx + dy*dy)
        vEnemyBullets.Add(Bullet(vEnemyBulletImage, ex, ey, dx*k, dy*k))
        play sound vEnemyShootSound, 0.125
       
        return true
    else
        return false
    endif
endfunc

' Particle
' --------
' Quick hack in the last moment.
function Particle(x, y, r, g, b)
    a = rnd()*2*PI
    spd = 40 + rnd(40)
    dist = rnd()*4
    return [
            x: x + cos(a)*dist, y: y + sin(a)*dist, r: r, g: g, b: b,
            dx: cos(a)*spd, dy: sin(a)*spd,
            duration: 1 + rnd()*2,
            Update: function(dt)
                this.duration = this.duration - dt
                this.x = (this.x + this.dx*dt)%vLevelWidth
                this.y = this.y + this.dy*dt
                this.dy = min(this.dy + 60*dt, 80)
                return this.duration > 0
            endfunc,
            Draw: function()
                DrawPixel(this.x, this.y, this.r, this.g, this.b)
            endfunc
        ]
endfunc

' AddExplosion
' ------------
function AddExplosion(x, y, r, g, b, particles)
    for i = 1 to particles  vParticles.Add(Particle(x, y, r, g, b))
endfunc

' Sprite
' ------
function Sprite(img, cel)
    return [
            ' Data
            ' ----
            img: img, cel: cel,
            x: 0, y: 0,
            w: width(img), h: height(img),
            c: [255, 255, 255, 255],
           
            ' SetPosition
            ' -----------
            SetPosition: function(x, y)
                this.x = x
                this.y = y
                if this.x < 0  this.x = this.x + vLevelWidth
                elseif this.x >= vLevelWidth  this.x = this.x - vLevelWidth
            endfunc,
           
            ' Center
            ' ------
            Center: function(x, y)
                this.SetPosition(x - width(this.img)/2, y - height(this.img)/2)
            endfunc,
           
            ' SetColor
            ' --------
            SetColor: function(r, g, b, a)
                this.c[0] = r
                this.c[1] = g
                this.c[2] = b
                this.c[3] = a
            endfunc,
           
            ' Update
            ' ------
            Update: function(dt)
            endfunc,
           
            ' Draw
            ' ----
            Draw: function()
                set color this.c
                DrawImage(this.img, this.x, this.y, this.cel)
            endfunc,
           
            ' Overlaps
            ' --------
            Overlaps: function(s)
                return this.x + this.w > s.x and this.x < s.x + s.w and
                        this.y + this.h > s.y and this.y < s.y + s.h
            endfunc,
           
            ' Visible
            ' -------
            Visible: function()
                return Visible(this.x, this.y, this.w, this.h)
            endfunc,
           
            ' HitByAnySprite
            ' --------------
            ' Return > 0 if this sprite overlaps with any sprite in list. If 'delete' is true,
            ' the overlapping sprites are removed from the list.
            HitByAnySprite: function(list, delete)
                hits = 0
                if list.size
                    i = 0
                    while i < list.size
                        if this.Overlaps(list[i])
                            hits = hits + 1                       
                            if delete  list.Remove(list[i])
                            else i = i + 1
                        else
                            i = i + 1
                        endif
                    wend
                endif
                return hits
            endfunc
        ]
endfunc

' UpdateSprites
' -------------
' Update all sprites in list.
function UpdateSprites(list, dt)
    if list.size
        i = 0
        while i < list.size
            if list[i].Update(dt)  i = i + 1
            else  list.RemoveByIndex(i)
        wend
    endif
endfunc

' DrawSprites
' -----------
' Draw all sprites in list.
function DrawSprites(list)
    if list.size  for i = 0 to list.size - 1  list[i].Draw()
endfunc

' DrawMap
' -------
' Draw mini map.
function DrawMap()
    scale = 12
    mapw = vLevelWidth/scale
    maph = VIEW_H/scale
    mapx = (VIEW_W - mapw)/2
    mapy = 0
    set color 0, 0, 0, 128
    draw rect mapx, mapy, mapw, maph, true
    set clip rect mapx, mapy, mapw, maph
    DrawMapList(vEnemies, mapx, mapy, mapw, maph, scale, 181, 50, 32)
    DrawMapList(vDudes, mapx, mapy, mapw, maph, scale, 161, 27, 205)
    DrawMapList(vGrabbers, mapx, mapy, mapw, maph, scale, 0, 144, 50)
    set color 254, 254, 254
    draw pixel mapx + mapw/2, mapy + (vPlayer.y + vPlayer.h/2)/scale
    clear clip rect
    set color 102, 102, 102
    draw rect mapx - 1, mapy - 1, mapw + 2, maph + 2
   
    ' DrawMapList
    ' -----------
    function DrawMapList(list, mapx, mapy, mapw, maph, scale, r, g, b)
        if list.size
            set color r, g, b
            px = vPlayer.x + vPlayer.w/2
            for i = 0 to list.size - 1
                y = (list[i].y + list[i].h/2)/scale
                x = ((list[i].x + list[i].w/2 - px)/scale + mapw/2)%mapw
                draw pixel mapx + x, mapy + y
            next
        endif
    endfunc
   
endfunc

' DrawPixel
' ---------
function DrawPixel(x, y, r, g, b)
    set color r, g, b
    draw pixel floor(x - vScroll), y
    if x < VIEW_W  draw pixel floor(x + vLevelWidth - vScroll), y
    elseif x >= vLevelWidth - VIEW_W  draw pixel floor(x - vLevelWidth - vScroll), y
endfunc

' DrawImage
' ---------
function DrawImage(img, x, y, cel)
    draw image img, floor(x - vScroll), y, cel
    if x < VIEW_W  draw image img, floor(x + vLevelWidth - vScroll), y, cel
    elseif x >= vLevelWidth - VIEW_W  draw image img, floor(x - vLevelWidth - vScroll), y, cel
endfunc

' DrawLine
' --------
function DrawLine(x1, y1, x2, y2)
    draw line floor(x1 - vScroll), y1, floor(x2 - vScroll), y2
    if x1 < VIEW_W  draw line floor(x1 + vLevelWidth - vScroll), y1, floor(x2 + vLevelWidth - vScroll), y2
    elseif x2 >= vLevelWidth - VIEW_W  draw line floor(x1 - vLevelWidth - vScroll), y1, floor(x2 - vLevelWidth - vScroll), y2
endfunc

' Visible
' -------
' Return true if rectangle is on screen.
function Visible(x, y, w, h)
    if y + h < 0 or y > VIEW_H  return false
   
    ' Check one area.
    if vScroll >= 0 and vScroll < vLevelWidth - VIEW_W
        return x + w > vScroll and x < vScroll + VIEW_W
    ' Overlap left from right.
    else if vScroll < 0
        return x + w > 0 and x < vScroll + VIEW_W or
                x + w > vLevelWidth + vScroll and x < vLevelWidth
    ' Overlap right from left.
    else if vScroll >= vLevelWidth - VIEW_W
        return x + w > vScroll and x < vLevelWidth or
                x + w > 0 and x < vScroll + VIEW_W - vLevelWidth
    endif
endfunc

' CreateSineSfx
' -------------
function CreateSineSfx(duration, startFreq, endFreq, fadeOut, sampleRate)
    data = []
    a = 0
    da = 2*PI*startFreq/sampleRate
    dda = (2*PI*endFreq/sampleRate - 2*PI*startFreq/sampleRate)/(duration*sampleRate)
    vol = 1
    fadeOut = fadeOut*duration*sampleRate
    fadeOutDelta = 1/(duration*sampleRate - fadeOut)
    for i = 0 to duration*sampleRate - 1
        data[i] = sin(a)*vol
        a = a + da
        da = da + dda
        if i > fadeOut  vol = vol - fadeOutDelta
    next
    return createsound(data, data, sampleRate)
endfunc

' CreateSquareSfx
' ---------------
function CreateSquareSfx(duration, startFreq, endFreq, fadeOut, sampleRate)
    data = []
    a = 0
    da = 2*PI*startFreq/sampleRate
    dda = (2*PI*endFreq/sampleRate - 2*PI*startFreq/sampleRate)/(duration*sampleRate)
    vol = 1
    fadeOut = fadeOut*duration*sampleRate
    fadeOutDelta = 1/(duration*sampleRate - fadeOut)
    for i = 0 to duration*sampleRate - 1
        sa = sin(a)
        if sa < 0  sa = -1
        elseif sa > 0 sa = 1
        data[i] = sa*vol
        a = a + da
        da = da + dda
        if i > fadeOut  vol = vol - fadeOutDelta
    next
    return createsound(data, data, sampleRate)
endfunc

' CreateNoiseSfx
' --------------
function CreateNoiseSfx(duration, pitch, fadeOut, sampleRate)
    assert sampleRate >= 8000, "CreateBoomSfx: invalid sample rate"
    assert pitch > 0, "CreateBoomSfx: invalid pitch"
 
    freqs = [
            [v: 0, p: sampleRate/500, d: 0, t: 0, w: pitch],
            [v: 0, p: sampleRate/1000, d: 0, t: 0, w: pitch^2],
            [v: 0, p: sampleRate/2000, d: 0, t: 0, w: pitch^3],
            [v: 0, p: sampleRate/8000, d: 0, t: 0, w: pitch^4]]
   
    s = sizeof(freqs)
    data = []
    vol = 1
    fadeOut = fadeOut*duration*sampleRate
    fadeOutDelta = 1/(duration*sampleRate - fadeOut)
    for i = 0 to duration*sampleRate - 1
        v = 0
        w = 0
        for j = 0 to s - 1; f = freqs[j]
            f.t = f.t - 1
            if f.t <= 0
                f.t = f.p
                f.d = ((rnd()*2 - 1) - f.v)/f.p
            endif
            f.v = f.v + f.d
            v = v + f.v*f.w
            w = w + f.w
        next
        data[i] = vol*v/w
        if i > fadeOut  vol = vol - fadeOutDelta
    next
    return createsound(data, data, sampleRate)
endfunc

' ImageFromMonoData
' -----------------
' Create one color image from array.
function ImageFromMonoData(data, r, g, b, gridCols, gridRows)
    h = sizeof(data)
    w = sizeof(data[0])
    img = createimage(w, h)
    set image img
    for y = 0 to h - 1  for x = 0 to w - 1
        if data[y][x]  set color r, g, b, 255
        else  set color 0, 0, 0, 0
        set pixel x, y
    next
    set image primary
    set image grid img, gridCols, gridRows
    return img
endfunc
Cool... Much improved... Aww... You fixed the "bounce"... lol Great job!
(01-20-2024, 08:44 PM)Marcus Wrote: [ -> ]...all assets generated in game...

It would take months for me to understand these "assets generated in game" , no external sounds, no external image files ... all resources are packed into one single Naalaa file .... It's super amazing  Big Grin Big Grin Big Grin

It inspired me to recreate Mario's character pixel-art using ImageFromMonoData function from your game. Voila !

Code:
#win32
set window "Mario - Pixel Art", 1, 1, false, 10

MarioImage = ImageFromMonoData([
[0,0,0,0,0,0,0,1,1,1,0,0,0,0,0],
[0,0,0,0,0,1,1,0,0,0,1,0,0,0,0],
[0,0,0,0,1,0,0,0,0,0,1,0,0,0,0],
[0,0,0,1,0,0,0,0,1,1,1,1,0,0,0],
[0,0,1,0,0,1,1,1,1,1,1,1,1,1,0],
[0,0,1,0,1,1,0,0,0,0,0,1,1,0,0],
[0,1,1,1,1,0,0,0,1,0,1,1,0,0,0],
[0,1,0,0,1,0,0,0,1,0,1,1,1,0,0],
[0,1,0,0,0,1,0,0,0,0,0,0,0,1,0],
[0,1,0,0,0,0,0,1,0,0,0,0,0,1,0],
[0,0,1,0,0,0,1,1,1,1,0,0,1,1,1],
[0,0,0,1,0,0,0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,0,0,0,0,0,1,0,0,0,0],
[0,1,1,0,1,1,1,1,1,1,0,0,0,0,0],
[0,1,0,0,0,1,1,0,0,1,1,0,1,0,0],
[1,0,0,0,1,0,0,1,0,0,1,1,0,1,0],
[1,0,0,1,0,0,0,0,1,0,0,1,0,1,0],
[1,1,0,0,1,0,0,1,0,1,1,0,1,0,0],
[0,1,1,1,1,1,1,0,0,1,1,0,1,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[1,0,1,1,1,1,1,0,1,1,1,0,0,0,1],
[1,0,0,1,1,1,0,0,0,1,0,0,0,0,1],
[0,1,0,0,0,0,1,0,0,1,0,0,0,1,0],
[0,0,1,1,1,0,0,0,0,0,1,1,1,0,0]])

set color 0, 0, 0; cls; set color 255,255,255'clear screen
draw image MarioImage,0,0
do;wait 1;until keydown(KEY_ESCAPE)'pause

function ImageFromMonoData(data)
...
endfunc