01-18-2024, 06:35 PM (This post was last modified: 01-20-2024, 09:37 PM by Marcus.)
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
Prevent the green aliens from obducting the dudes. The red spaceships just fly around randomly and shoot at you.
Edit Attached the full game (denaalaafender.n7) from a post further down (denaalaafender.zip is the old version).
(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.
01-20-2024, 08:44 PM (This post was last modified: 01-20-2024, 10:49 PM by Marcus.)
Here's a more complete game, now with all assets generated in game. It requires atleast n7 version 24.01.19
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
' 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
' 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 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
' 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
' 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
' 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
' 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
' 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
' 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
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
(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
It inspired me to recreate Mario's character pixel-art using ImageFromMonoData function from your game. Voila !