Im nearly done with pixel perfect but its a little headache so in the meantime...here's a simple demo on how using reduced collision rectangles can give an approximated result...
Code: (Select All)
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Unseen's Game Dev Tutorials pt.1 - Collisions \\
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'///////////////////////////////////// System Initialisation ////////////////////////////////////////////////
CHDIR "Tutorials\" '// The root path for the files we will use
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Game objects - All sprites in GDK (and in general should be right orientated as 0 debrees rotation)
DIM Ship AS GDK_Game_Object, Bullet(19) AS GDK_Game_Object, Max_Bullets&, Enemy(39) AS GDK_Game_Object, EnemyAnim AS GDK_Animation
Max_Bullets& = 20
'// Now we load the game objects (sprites)
GDK_GameObject_New Ship, "Hunter_Red.png", 1, 1, 400, 530, 0, 2 * ATN(1)
GDK_Sprite_SetRotationAsCenter Ship.Sprite
Ship.Sprite.Scale = .8
'// This is NOT the way to do it, ideally you only use one instance of the bullet image and the enemy image but for now...this works
ex% = 60: ey% = 80
FOR i% = 0 TO 39
GDK_GameObject_New Enemy(i%), "Invader.png", 4, 1, ex%, ey%, 0, 0
GDK_Sprite_SetRotationAsCenter Enemy(i%).Sprite
IF (i% + 1) MOD 10 = 0 THEN
ey% = ey% + 90
ex% = 60
ELSE
ex% = ex% + 60
END IF
NEXT
'// The enemy is animated so we make a singular animation variable to handle it
GDK_ANIMATION_NEW EnemyAnim, 1, 1, 4, .1, 0
FOR i% = 0 TO Max_Bullets& - 1
GDK_GameObject_New Bullet(i%), "Bullet_Blue.png", 1, 1, 0, 0, 0, 0
Bullet(i%).Exists = GDK_FALSE '// Reset default auto existance
GDK_Sprite_SetRotationAsCenter Bullet(i%).Sprite
NEXT
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Control and movement limits
DIM Enemy_Left AS _BYTE, Enemy_Move AS _BYTE
DIM Shoot_Time AS DOUBLE, Shoot_Timer AS DOUBLE
DIM Player_MinX AS INTEGER, Player_MaxX AS INTEGER
Shoot_Time = .33 '// Fire 3 bullets every one second
Player_MinX = 40 '// X axis limits
Player_MaxX = 760
'// Intersection box for pixel perfect collisions
DIM ISecBox AS GDK_Box
'// Bullets movement and collisions checks - destroy bullets when they leave the screen
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN
GDK_Vector_Update Bullet(i%).Vector, Bullet(i%).Rotation, Bullet(i%).Speed
IF Bullet(i%).Vector.Y < 0 THEN
Bullet(i%).Exists = GDK_FALSE '// off screen so destroy
ELSE '// check for enemy collisions
FOR j% = 0 TO 39
IF Enemy(j%).Exists = GDK_TRUE THEN
Enemy(j%).Sprite.Scale = .8 '// This will reduce the collision box to 80%
IF GDK_CheckCollision(Bullet(i%), Enemy(j%)) THEN '// Bullet has hit an enemy
Bullet(i%).Exists = GDK_FALSE
Enemy(j%).Exists = GDK_FALSE
'IF GDK_GetIntersectionAABB(Bullet(i%).Rect, Enemy(j%).Rect, ISecBox) THEN
'// actual coordiantes for the collision are are now stored in isecbox? Steve to do his magic with that!
'// This will be for pixel perfect collisions that analyse exact positions of pixels in an image and also account for scaling
'END IF
EXIT FOR
END IF
Enemy(j%).Sprite.Scale = 1 '// Restore the scale for rendering
END IF
NEXT
END IF
END IF
NEXT
'// Enemy movement
IF Enemy_Left = GDK_TRUE THEN
IF Enemy_Move > 0 THEN Enemy_Move = Enemy_Move - 1 ELSE Enemy_Left = GDK_FALSE
ELSE
IF Enemy_Move < 120 THEN Enemy_Move = Enemy_Move + 1 ELSE Enemy_Left = GDK_TRUE
END IF
FOR i% = 0 TO 39
IF Enemy_Left = GDK_TRUE THEN Enemy(i%).Vector.X = Enemy(i%).Vector.X - 1 ELSE Enemy(i%).Vector.X = Enemy(i%).Vector.X + 1
NEXT
'// INPUT
'// Space bar shoots
IF _KEYDOWN(32) THEN
IF TIMER(.001) - Shoot_Timer >= Shoot_Time THEN
FOR i% = 0 TO Max_Bullets& - 1
IF NOT Bullet(i%).Exists THEN
Bullet(i%).Exists = GDK_TRUE
GDK_Vector_New Bullet(i%).Vector, Ship.Vector.X, Ship.Vector.Y
Bullet(i%).Speed = 5
Bullet(i%).Rotation = 6 * ATN(1)
EXIT FOR
END IF
NEXT
Shoot_Timer = TIMER(.001)
END IF
END IF
'// Ship left and right movement
IF _KEYDOWN(19200) THEN '// left
IF Ship.Vector.X > Player_MinX THEN Ship.Vector.X = Ship.Vector.X - 1
ELSEIF _KEYDOWN(19712) THEN '// right
IF Ship.Vector.X < Player_MaxX THEN Ship.Vector.X = Ship.Vector.X + 1
END IF
'// Render
'// Enemy
FOR i% = 0 TO 39
IF Enemy(i%).Exists = GDK_TRUE THEN GDK_GameObject_Draw Enemy(i%), EnemyAnim.Frame
NEXT
'// Bullets
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN GDK_GameObject_Draw Bullet(i%), 1
NEXT
'// Player
GDK_GameObject_Draw Ship, 1
_DISPLAY
LOOP UNTIL INKEY$ = CHR$(27)
SYSTEM
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// THIS IS ALL FROM A VERY OLD GDK as such is only for demostration purposes
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
TYPE GDK_Vector
X AS SINGLE
Y AS SINGLE
END TYPE
TYPE GDK_Box
Position AS GDK_Vector
WidthHeight AS GDK_Vector
Rotation AS SINGLE
RotationPointOffset AS GDK_Vector
END TYPE
TYPE GDK_Sprite
File AS LONG
Width AS INTEGER
Height AS INTEGER
Alpha AS _UNSIGNED LONG
RotationX AS SINGLE
RotationY AS SINGLE
Scale AS SINGLE
IsVisible AS _BYTE
XFrameCount AS _BYTE
YFrameCount AS _BYTE
TotalFrameCount AS INTEGER
FrameWidth AS INTEGER
FrameHeight AS INTEGER
END TYPE
TYPE GDK_Game_Object
Sprite AS GDK_Sprite
Vector AS GDK_Vector
Rotation AS SINGLE
Speed AS SINGLE
Rect AS GDK_Box
Exists AS _BYTE
END TYPE
TYPE GDK_Animation '// Animation data.
Frame AS INTEGER '// The current frame - When data is loaded/set...this defaults to StartFrame - Is the index in the Frame array
StartFrame AS INTEGER '// 1st frame in sequence
ResetFrame AS INTEGER '// Last frame in sequence
Time AS DOUBLE '// Time between frame changes
Timer AS DOUBLE '// The last animation update
END TYPE
TYPE GDK_Collision_Area
Num_Pixels AS LONG
Pixel_Data AS _MEM '//
END TYPE
SUB GDK_DrawBox (Box AS GDK_Box, bcolor AS LONG)
DIM Corners(3) AS GDK_Vector
CALL GDK_GetBoxCorners(Box, Corners())
LINE (Corners(0).X, Corners(0).Y)-(Corners(1).X, Corners(1).Y), bcolor
LINE (Corners(1).X, Corners(1).Y)-(Corners(2).X, Corners(2).Y), bcolor
LINE (Corners(2).X, Corners(2).Y)-(Corners(3).X, Corners(3).Y), bcolor
LINE (Corners(3).X, Corners(3).Y)-(Corners(0).X, Corners(0).Y), bcolor
END SUB
SUB GDK_RotatePoint (Result AS GDK_Vector, p AS GDK_Vector, pivot AS GDK_Vector, angle AS SINGLE)
DIM temp_x AS SINGLE, temp_y AS SINGLE
temp_x = p.X - pivot.X
temp_y = p.Y - pivot.Y
Result.X = temp_x * COS(angle) - temp_y * SIN(angle)
Result.Y = temp_x * SIN(angle) + temp_y * COS(angle)
Result.X = Result.X + pivot.X
Result.Y = Result.Y + pivot.Y
END SUB
SUB GDK_GetBoxCorners (Box AS GDK_Box, Corners() AS GDK_Vector)
DIM HalfW AS SINGLE, HalfH AS SINGLE
DIM UnrotatedCorner AS GDK_Vector
DIM Center AS GDK_Vector
FUNCTION GDK_Overlap_On_Axis (Corners1() AS GDK_Vector, Corners2() AS GDK_Vector, Axis AS GDK_Vector)
DIM Min1 AS SINGLE, Max1 AS SINGLE, Min2 AS SINGLE, Max2 AS SINGLE
DIM Projection AS SINGLE, I AS LONG
FOR I = 0 TO 3
Projection = Corners1(I).X * Axis.X + Corners1(I).Y * Axis.Y
IF Projection < Min1 THEN Min1 = Projection
IF Projection > Max1 THEN Max1 = Projection
NEXT I
FOR I = 0 TO 3
Projection = Corners2(I).X * Axis.X + Corners2(I).Y * Axis.Y
IF Projection < Min2 THEN Min2 = Projection
IF Projection > Max2 THEN Max2 = Projection
NEXT I
IF Max1 >= Min2 AND Max2 >= Min1 THEN GDK_Overlap_On_Axis = -1 ELSE GDK_Overlap_On_Axis = 0
END FUNCTION
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// SAT Function
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_Box_Intersect (Box1 AS GDK_Box, Box2 AS GDK_Box)
DIM Box1_Corners(3) AS GDK_Vector, Box2_Corners(3) AS GDK_Vector
DIM Axis(3) AS GDK_Vector, length AS SINGLE, I AS LONG
FOR I = 0 TO 1
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
FOR I = 2 TO 3
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
SUB GDK_Sprite_SetRotationAsCenter (Sprite AS GDK_Sprite)
GDK_Sprite_SetRotationPoint Sprite, Sprite.Width / (Sprite.XFrameCount * 2), Sprite.Height / (Sprite.YFrameCount * 2)
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Rotate_Scale_AroundPoint (X AS LONG, Y AS LONG, Image AS LONG, Rotation AS SINGLE, ScaleX AS SINGLE, ScaleY AS SINGLE, PointX AS LONG, PointY AS LONG) '
DEFSNG A-Z
DIM pX(3) AS SINGLE: DIM pY(3) AS SINGLE
DIM W AS LONG, H AS LONG, I AS LONG, X2 AS LONG, Y2 AS LONG
W = _WIDTH(Image): H = _HEIGHT(Image)
pX(0) = -PointX: pY(0) = -PointY: pX(1) = -PointX: pY(1) = -PointY + H - 1: pX(2) = -PointX + W - 1: pY(2) = -PointY + H - 1: pX(3) = -PointX + W - 1: pY(3) = -PointY 'Set dest screen points
Radians = -Rotation / 57.29578: SINr = SIN(Radians): COSr = COS(Radians) 'Precalculate SIN & COS of angle
FOR I = 0 TO 3
pX(I) = pX(I) * ScaleX: pY(I) = pY(I) * ScaleY 'Scale
X2 = pX(I) * COSr + SINr * pY(I): pY(I) = pY(I) * COSr - pX(I) * SINr: pX(I) = X2 'Rotate Dest Points
pX(I) = pX(I) + X: pY(I) = pY(I) + Y 'Translate Dest Points
NEXT
_MAPTRIANGLE (0, 0)-(0, H - 1)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(1), pY(1))-(pX(2), pY(2))
_MAPTRIANGLE (0, 0)-(W - 1, 0)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(3), pY(3))-(pX(2), pY(2))
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB GDK_Sprite_Draw (Sprite AS GDK_Sprite, DestVector AS GDK_Vector, Rotation AS SINGLE, Frame%)
IF Frame% = 0 THEN Frame% = 1
_CLEARCOLOR Sprite.Alpha, Sprite.File
IF Sprite.IsVisible THEN
FrameXStart% = ((INT((Frame% - 1) MOD Sprite.XFrameCount)) * Sprite.FrameWidth)
FrameYStart% = ((INT((Frame% - 1) / Sprite.XFrameCount)) * Sprite.FrameHeight)
TmpImage& = _NEWIMAGE(Sprite.FrameWidth - 1, Sprite.FrameHeight - 1, 32)
_PUTIMAGE (0, 0), Sprite.File, TmpImage&, (FrameXStart%, FrameYStart%)-(FrameXStart% + Sprite.FrameWidth, FrameYStart% + Sprite.FrameHeight)
Rotate_Scale_AroundPoint DestVector.X, DestVector.Y, TmpImage&, -GDK_RadianToDegree(Rotation), Sprite.Scale, Sprite.Scale, Sprite.RotationX, Sprite.RotationY
_FREEIMAGE TmpImage&
END IF
END SUB
SUB GDK_GameObject_Draw (GameObject AS GDK_Game_Object, AnimationFrame%)
GDK_Sprite_Draw GameObject.Sprite, GameObject.Vector, GameObject.Rotation, AnimationFrame%
END SUB
SUB GDK_ANIMATION_UPDATE (Anim AS GDK_Animation)
IF TIMER(.001) - Anim.Timer >= Anim.Time THEN
IF Anim.Frame = Anim.ResetFrame THEN
Anim.Frame = Anim.StartFrame
ELSE
Anim.Frame = Anim.Frame + 1
END IF
Anim.Timer = TIMER(.001)
END IF
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_CheckCollision (object1 AS GDK_Game_Object, object2 AS GDK_Game_Object)
DIM box1 AS GDK_Box
DIM box2 AS GDK_Box
' Create the bounding box data for object1 using its Rect and Sprite
box1.Position.X = object1.Vector.X
box1.Position.Y = object1.Vector.Y
box1.WidthHeight.X = object1.Sprite.FrameWidth * (object1.Sprite.Scale)
box1.WidthHeight.Y = object1.Sprite.FrameHeight * (object1.Sprite.Scale)
box1.Rotation = object1.Rotation
box1.RotationPointOffset.X = object1.Rect.RotationPointOffset.X * (object1.Sprite.Scale)
box1.RotationPointOffset.Y = object1.Rect.RotationPointOffset.Y * (object1.Sprite.Scale)
' Create the bounding box data for object2 using its Rect and Sprite
box2.Position.X = object2.Vector.X
box2.Position.Y = object2.Vector.Y
box2.WidthHeight.X = object2.Sprite.FrameWidth * (object2.Sprite.Scale)
box2.WidthHeight.Y = object2.Sprite.FrameHeight * (object2.Sprite.Scale)
box2.Rotation = object2.Rotation
box2.RotationPointOffset.X = object2.Rect.RotationPointOffset.X * (object2.Sprite.Scale)
box2.RotationPointOffset.Y = object2.Rect.RotationPointOffset.Y * (object2.Sprite.Scale)
' Check for collision using the GDK_Box_Intersect function
' NOTE: Your GDK_Box_Intersect must support rotated boxes for this to work correctly.
IF GDK_Box_Intersect(box1, box2) THEN GDK_CheckCollision = GDK_TRUE
END FUNCTION
FUNCTION GDK_GetIntersectionAABB (box1 AS GDK_Box, box2 AS GDK_Box, intersection AS GDK_Box)
DIM box1_corners(3) AS GDK_Vector, box2_corners(3) AS GDK_Vector
DIM minX AS SINGLE, maxX AS SINGLE, minY AS SINGLE, maxY AS SINGLE
DIM i AS INTEGER
' Find the AABB of the first box
minX = box1_corners(0).X: maxX = box1_corners(0).X
minY = box1_corners(0).Y: maxY = box1_corners(0).Y
FOR i = 1 TO 3
IF box1_corners(i).X < minX THEN minX = box1_corners(i).X
IF box1_corners(i).X > maxX THEN maxX = box1_corners(i).X
IF box1_corners(i).Y < minY THEN minY = box1_corners(i).Y
IF box1_corners(i).Y > maxY THEN maxY = box1_corners(i).Y
NEXT i
DIM box1_minX AS SINGLE, box1_maxX AS SINGLE, box1_minY AS SINGLE, box1_maxY AS SINGLE
box1_minX = minX: box1_maxX = maxX: box1_minY = minY: box1_maxY = maxY
' Find the AABB of the second box
minX = box2_corners(0).X: maxX = box2_corners(0).X
minY = box2_corners(0).Y: maxY = box2_corners(0).Y
FOR i = 1 TO 3
IF box2_corners(i).X < minX THEN minX = box2_corners(i).X
IF box2_corners(i).X > maxX THEN maxX = box2_corners(i).X
IF box2_corners(i).Y < minY THEN minY = box2_corners(i).Y
IF box2_corners(i).Y > maxY THEN maxY = box2_corners(i).Y
NEXT i
DIM box2_minX AS SINGLE, box2_maxX AS SINGLE, box2_minY AS SINGLE, box2_maxY AS SINGLE
box2_minX = minX: box2_maxX = maxX: box2_minY = minY: box2_maxY = maxY
IF intersection.WidthHeight.X > 0 AND intersection.WidthHeight.Y > 0 THEN
GDK_GetIntersectionAABB = -1 ' Returns true if there is an intersection
ELSE
GDK_GetIntersectionAABB = 0 ' Returns false otherwise
END IF
END FUNCTION
And just for that, now im gonna make them shoot back...smart ass comments coming from a man who NEVER gives us any code to rip on...quite ballsy! I love it!
09-08-2025, 11:39 PM (This post was last modified: 09-08-2025, 11:41 PM by bplus.)
(09-08-2025, 10:19 PM)Unseen Machine Wrote: And just for that, now im gonna make them shoot back...smart ass comments coming from a man who NEVER gives us any code to rip on...quite ballsy! I love it!
John
Check this out Space Invaders in a Halloween Theme AND you are only allowed to use one key!!!
@steve - Lol: To be fair, yourself, clippy, solitaire, pete, thebob, bob, cypherium(miss that guy) and more all can take responsibility for me being as capable as I am...it aint my fault, its you guys who are to blame! Now, i've nearly got it to where it can give me the EXACT area on the screen that is intersecting, this "box" along with your trick of cross comparing bit masked (or similar) images could then use JUST this area when checking for pixel differences...if i get it working (math wise) would you be happy to help merge your method with it...then we will have super fast pixel perfect collisions!
And as for petes One button approach....lazy git! Was it so difficult for you to add more? Im sure clippy could point you to some demos or give you help if you need it bro!
Now my enemies shoot back, shield is implemented and ill repost before bed....blame Pete!
Its my birthday so be nice! It's soooo not how i would have coded it if i had to do it again...but its been fun debuggin new and old GDK stuff and whilst its still very easy to play...when i get stage two working properly...(for some reason in the boss level it only shows one enemy for now) i hope Pete likes it!
Same files as attached in the first post of the thread...
Code: (Select All)
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Unseen's Game Dev Tutorials pt.1 - Collisions \\
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
RANDOMIZE TIMER
' Sine wave parameters
CONST SINE_AMPLITUDE = 120.0 ' Maximum horizontal offset from the center
CONST SINE_FREQUENCY = 7 ' Speed of the wave
CONST VERTICAL_SPEED = .75 ' Speed at which enemies move down the screen
' Spawning parameters
CONST SPAWN_OFFSET_Y = 50 ' Extra space to spawn off-screen
CONST SPAWN_DELAY = 10 ' Delay between enemy spawns (in frames)
'///////////////////////////////////// System Initialisation ////////////////////////////////////////////////
CHDIR "Tutorials\" '// The root path for the files we will use
DIM SHARED GameState AS INTEGER
DIM SHARED TransitionStarted AS _BYTE
DIM SHARED Ship AS GDK_Game_Object, Bullet(19) AS GDK_Game_Object, Max_Bullets&, EnemyAnim AS GDK_Animation
DIM SHARED WallPaper AS LONG, Enemy_Bullet AS GDK_Game_Object, Hit%
DIM SHARED PHealth AS INTEGER: PHealth = 10
DIM SHARED gt# '// Global timer
DIM SHARED Enemy_Left AS _BYTE, Enemy_Move AS _BYTE, eShoot_Time AS DOUBLE, eShoot_Timer AS DOUBLE
DIM SHARED Shoot_Time AS DOUBLE, Shoot_Timer AS DOUBLE
DIM SHARED Player_MinX AS INTEGER, Player_MaxX AS INTEGER, Player_MinY AS INTEGER, Player_MaxY AS INTEGER
DIM SHARED repelChargeTimer AS DOUBLE
DIM SHARED repelWaveReady AS _BYTE, RepulsorRadius
DIM SHARED transitionPhase AS INTEGER
Max_Bullets& = 20
RepulsorRadius = 20
DIM SHARED Enemy(MAX_ENEMIES) AS GDK_Game_Object
DIM SHARED BossEnemy(MAX_ENEMIES) AS GDK_Game_Object
DIM SHARED GameTime AS SINGLE, MainMode%
'// Now we load the game objects (sprites)
GDK_GameObject_New Ship, "Hunter_Red.png", 1, 1, 400, 530, 0, 6 * ATN(1)
GDK_Sprite_SetRotationAsCenter Ship.Sprite
GDK_ANIMATION_NEW Ship.Animation, 1, 1, 1, 0, 0
CALL PlayGameState
IF NumActiveEnemies(Enemy()) <= 10 AND TransitionStarted = GDK_FALSE THEN
TransitionStarted = GDK_TRUE
GameState = GDK_STATE_TRANSITION
END IF
CASE GDK_STATE_TRANSITION
TransitionState Enemy(), Ship
CASE GDK_STATE_BOSS_MODE
Populate_Enemies_2 BossEnemy()
Shoot_Time = .2 '// Fire 2 bullets every one second
Player_MinX = 40 '// X axis limits
Player_MinY = 40 '// X axis limits
Player_MaxX = 760
Player_MaxY = 560
eShoot_Time = 3
BossModeState Enemy(), Ship
END SELECT
'// Enemy
FOR i% = 0 TO MAX_ENEMIES - 1
IF Enemy(i%).Exists = GDK_TRUE THEN GDK_GameObject_Draw Enemy(i%), Enemy(i%).Animation.Frame
NEXT
'// Bullets
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN GDK_GameObject_Draw Bullet(i%), Bullet(i%).Animation.Frame
NEXT
IF Enemy_Bullet.Exists THEN GDK_GameObject_Draw Enemy_Bullet, Enemy_Bullet.Animation.Frame
'// Player
GDK_GameObject_Draw Ship, Ship.Animation.Frame
Draw_Shield Ship, Hit%
TYPE GDK_Animation '// Animation data.
Frame AS INTEGER '// The current frame - When data is loaded/set...this defaults to StartFrame - Is the index in the Frame array
StartFrame AS INTEGER '// 1st frame in sequence
ResetFrame AS INTEGER '// Last frame in sequence
Time AS DOUBLE '// Time between frame changes
Timer AS DOUBLE '// The last animation update
END TYPE
TYPE GDK_Box
Position AS GDK_Vector
WidthHeight AS GDK_Vector
Rotation AS SINGLE
RotationPointOffset AS GDK_Vector
END TYPE
TYPE GDK_Sprite
File AS LONG
Width AS INTEGER
Height AS INTEGER
Alpha AS _UNSIGNED LONG
RotationX AS SINGLE
RotationY AS SINGLE
Scale AS SINGLE
IsVisible AS _BYTE
XFrameCount AS _BYTE
YFrameCount AS _BYTE
TotalFrameCount AS INTEGER
FrameWidth AS INTEGER
FrameHeight AS INTEGER
END TYPE
TYPE GDK_Game_Object
Sprite AS GDK_Sprite
Vector AS GDK_Vector
Rotation AS SINGLE
Speed AS SINGLE
Rect AS GDK_Box
Exists AS _BYTE
Animation AS GDK_Animation
Last_Vector AS GDK_Vector
X_Initial AS SINGLE
END TYPE
TYPE GDK_BoundingCircle
X AS INTEGER
Y AS INTEGER
Radius AS INTEGER
END TYPE
SUB Populate_Enemies (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
DIM time_offset AS SINGLE
DIM group AS INTEGER
DIM group_radius AS SINGLE
' Loop through all 40 enemies to set their initial state
FOR i = 0 TO MAX_ENEMIES - 1
' Ensure the enemy exists.
Enemies(i).Exists = _TRUE
' Calculate initial position on the helix to stagger the start
time_offset = (i / MAX_ENEMIES) * (2 * 3.14159)
' Assign each of the 4 groups of 10 a slightly different radius
group = i / ENEMIES_PER_GROUP
group_radius = HELIX_RADIUS + (group * 20)
' Set the initial position and scale, starting off-screen
' The X position starts off-screen, and the Y position is based on the helix
Enemies(i).Vector.X = SWARM_START_X + (group_radius * COS(time_offset))
Enemies(i).Vector.Y = SWARM_CENTER_Y + (group_radius * SIN(time_offset))
Enemies(i).Sprite.Scale = 1.0 - (COS(time_offset * Z_DISTANCE_SCALE_MODIFIER)) * 0.5
NEXT i
END SUB
SUB Update_Enemies (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
DIM time_offset AS SINGLE
DIM group AS INTEGER
DIM group_radius AS SINGLE
DIM time_scale_factor AS SINGLE
DIM helix_x AS SINGLE, helix_y AS SINGLE
DIM dx AS SINGLE, dy AS SINGLE
' Advance the global time counter
GameTime = GameTime + 1
' Loop through all enemies that exist
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN
' Determine the group for this enemy to vary the helix path
group = i / ENEMIES_PER_GROUP
group_radius = HELIX_RADIUS + (group * 20)
' Add a variation to the time progression for each group
time_scale_factor = HELIX_SPEED + (group * 0.005)
' Calculate the position on the stationary helix path
time_offset = GameTime * time_scale_factor + (i / MAX_ENEMIES) * (2 * 3.14159)
helix_x = group_radius * COS(time_offset)
helix_y = group_radius * SIN(time_offset)
' Update X and Y position with the horizontal scroll
Enemies(i).Vector.X = SWARM_START_X + (GameTime * SWARM_TRAVEL_SPEED) + helix_x
Enemies(i).Vector.Y = SWARM_CENTER_Y + helix_y
' Update scale for the Z-distance effect based on the helix position
Enemies(i).Sprite.Scale = 1.0 - (COS(time_offset * Z_DISTANCE_SCALE_MODIFIER)) * 0.5
' Update rotation to face the direction of travel (horizontally)
Enemies(i).Rotation = _ATAN2(0, SWARM_TRAVEL_SPEED) - _PI
END IF
NEXT i
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Render_Enemies (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
' Loop through all enemies
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN
' Draw the sprite using its vector position, rotation, and scale
GDK_Sprite_Draw Enemies(i).Sprite, Enemies(i).Vector, Enemies(i).Rotation - (_PI / 2), Enemies(i%).Animation.Frame
END IF
NEXT i
END SUB
SUB GDK_DrawBox (Box AS GDK_Box, bcolor AS LONG)
DIM Corners(3) AS GDK_Vector
CALL GDK_GetBoxCorners(Box, Corners())
LINE (Corners(0).X, Corners(0).Y)-(Corners(1).X, Corners(1).Y), bcolor
LINE (Corners(1).X, Corners(1).Y)-(Corners(2).X, Corners(2).Y), bcolor
LINE (Corners(2).X, Corners(2).Y)-(Corners(3).X, Corners(3).Y), bcolor
LINE (Corners(3).X, Corners(3).Y)-(Corners(0).X, Corners(0).Y), bcolor
END SUB
SUB GDK_RotatePoint (Result AS GDK_Vector, p AS GDK_Vector, pivot AS GDK_Vector, angle AS SINGLE)
DIM temp_x AS SINGLE, temp_y AS SINGLE
temp_x = p.X - pivot.X
temp_y = p.Y - pivot.Y
Result.X = temp_x * COS(angle) - temp_y * SIN(angle)
Result.Y = temp_x * SIN(angle) + temp_y * COS(angle)
Result.X = Result.X + pivot.X
Result.Y = Result.Y + pivot.Y
END SUB
' Calculate the absolute position of the rotation pivot
Pivot.X = Box.Position.X + Box.RotationPointOffset.X
Pivot.Y = Box.Position.Y + Box.RotationPointOffset.Y
FUNCTION GDK_Overlap_On_Axis (Corners1() AS GDK_Vector, Corners2() AS GDK_Vector, Axis AS GDK_Vector)
DIM Min1 AS SINGLE, Max1 AS SINGLE, Min2 AS SINGLE, Max2 AS SINGLE
DIM Projection AS SINGLE, I AS LONG
FOR I = 0 TO 3
Projection = Corners1(I).X * Axis.X + Corners1(I).Y * Axis.Y
IF Projection < Min1 THEN Min1 = Projection
IF Projection > Max1 THEN Max1 = Projection
NEXT I
FOR I = 0 TO 3
Projection = Corners2(I).X * Axis.X + Corners2(I).Y * Axis.Y
IF Projection < Min2 THEN Min2 = Projection
IF Projection > Max2 THEN Max2 = Projection
NEXT I
IF Max1 >= Min2 AND Max2 >= Min1 THEN GDK_Overlap_On_Axis = -1 ELSE GDK_Overlap_On_Axis = 0
END FUNCTION
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// SAT Function
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_Box_Intersect (Box1 AS GDK_Box, Box2 AS GDK_Box)
DIM Box1_Corners(3) AS GDK_Vector, Box2_Corners(3) AS GDK_Vector
DIM Axis(3) AS GDK_Vector, length AS SINGLE, I AS LONG
FOR I = 0 TO 1
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
FOR I = 2 TO 3
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
SUB GDK_Sprite_SetRotationAsCenter (Sprite AS GDK_Sprite)
GDK_Sprite_SetRotationPoint Sprite, Sprite.Width / (Sprite.XFrameCount * 2), Sprite.Height / (Sprite.YFrameCount * 2)
END SUB
SUB Rotate_Scale_AroundPoint (X AS LONG, Y AS LONG, Image AS LONG, Rotation AS SINGLE, ScaleX AS SINGLE, ScaleY AS SINGLE, PointX AS LONG, PointY AS LONG) '
DEFSNG A-Z
DIM pX(3) AS SINGLE: DIM pY(3) AS SINGLE
DIM W AS LONG, H AS LONG, I AS LONG, X2 AS LONG, Y2 AS LONG
W = _WIDTH(Image): H = _HEIGHT(Image)
pX(0) = -PointX: pY(0) = -PointY: pX(1) = -PointX: pY(1) = -PointY + H - 1: pX(2) = -PointX + W - 1: pY(2) = -PointY + H - 1: pX(3) = -PointX + W - 1: pY(3) = -PointY 'Set dest screen points
Radians = -Rotation / 57.29578: SINr = SIN(-Radians): COSr = COS(-Radians) 'Precalculate SIN & COS of angle
FOR I = 0 TO 3
pX(I) = pX(I) * ScaleX: pY(I) = pY(I) * ScaleY 'Scale
X2 = pX(I) * COSr + SINr * pY(I): pY(I) = pY(I) * COSr - pX(I) * SINr: pX(I) = X2 'Rotate Dest Points
pX(I) = pX(I) + X: pY(I) = pY(I) + Y 'Translate Dest Points
NEXT
_MAPTRIANGLE (0, 0)-(0, H - 1)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(1), pY(1))-(pX(2), pY(2))
_MAPTRIANGLE (0, 0)-(W - 1, 0)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(3), pY(3))-(pX(2), pY(2))
END SUB
SUB GDK_Sprite_Draw (Sprite AS GDK_Sprite, DestVector AS GDK_Vector, Rotation AS SINGLE, Frame%)
IF Frame% = 0 THEN Frame% = 1
_CLEARCOLOR Sprite.Alpha, Sprite.File
IF Sprite.IsVisible THEN
FrameXStart% = ((INT((Frame% - 1) MOD Sprite.XFrameCount)) * Sprite.FrameWidth)
FrameYStart% = ((INT((Frame% - 1) / Sprite.XFrameCount)) * Sprite.FrameHeight)
TmpImage& = _NEWIMAGE(Sprite.FrameWidth - 1, Sprite.FrameHeight - 1, 32)
_PUTIMAGE (0, 0), Sprite.File, TmpImage&, (FrameXStart%, FrameYStart%)-(FrameXStart% + Sprite.FrameWidth, FrameYStart% + Sprite.FrameHeight)
Rotate_Scale_AroundPoint DestVector.X, DestVector.Y, TmpImage&, -GDK_RadianToDegree(Rotation), Sprite.Scale, Sprite.Scale, Sprite.RotationX, Sprite.RotationY
'Rotate_Scale_Source_Rect DestVector.X, DestVector.Y, Sprite.File, -GDK_RadianToDegree(Rotation), Sprite.Scale, FrameXStart%, FrameYStart%, Sprite.FrameWidth - 1, Sprite.FrameHeight - 1, Sprite.RotationX, Sprite.RotationY
_FREEIMAGE TmpImage&
END IF
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Rotate_Scale_Source_Rect (X AS SINGLE, Y AS SINGLE, Image AS LONG, Rotation AS DOUBLE, Scale AS SINGLE, _
FrameXStart AS INTEGER, FrameYStart AS INTEGER, _
FrameWidth AS INTEGER, FrameHeight AS INTEGER, _
RotX AS LONG, RotY AS LONG)
DEFSNG A-Z
DIM pX(3) AS SINGLE, pY(3) AS SINGLE
DIM I AS LONG, X2 AS SINGLE, Y2 AS SINGLE
' Use FrameWidth and FrameHeight directly
DIM W AS SINGLE, H AS SINGLE
W = FrameWidth: H = FrameHeight
' Define destination points relative to rotation point
pX(0) = -RotX: pY(0) = -RotY
pX(1) = -RotX: pY(1) = -RotY + H
pX(2) = -RotX + W: pY(2) = -RotY + H
pX(3) = -RotX + W: pY(3) = -RotY
DIM SINr AS SINGLE, COSr AS SINGLE
SINr = SIN(Rotation): COSr = COS(Rotation)
FOR I = 0 TO 3
' Scale
pX(I) = pX(I) * Scale: pY(I) = pY(I) * Scale
SUB GDK_GameObject_Draw (GameObject AS GDK_Game_Object, AnimationFrame%)
GDK_Sprite_Draw GameObject.Sprite, GameObject.Vector, GameObject.Rotation, AnimationFrame%
END SUB
SUB GDK_ANIMATION_UPDATE (Anim AS GDK_Animation)
IF gt# - Anim.Timer >= Anim.Time THEN
IF Anim.Frame = Anim.ResetFrame THEN
Anim.Frame = Anim.StartFrame
ELSE
Anim.Frame = Anim.Frame + 1
END IF
Anim.Timer = gt#
END IF
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_CheckCollision (object1 AS GDK_Game_Object, object2 AS GDK_Game_Object)
DIM box1 AS GDK_Box
DIM box2 AS GDK_Box
' Create the bounding box data for object1 using its Rect and Sprite
box1.Position.X = object1.Vector.X
box1.Position.Y = object1.Vector.Y
box1.WidthHeight.X = object1.Sprite.FrameWidth * (object1.Sprite.Scale)
box1.WidthHeight.Y = object1.Sprite.FrameHeight * (object1.Sprite.Scale)
box1.Rotation = object1.Rotation
box1.RotationPointOffset.X = object1.Rect.RotationPointOffset.X * (object1.Sprite.Scale)
box1.RotationPointOffset.Y = object1.Rect.RotationPointOffset.Y * (object1.Sprite.Scale)
' Create the bounding box data for object2 using its Rect and Sprite
box2.Position.X = object2.Vector.X
box2.Position.Y = object2.Vector.Y
box2.WidthHeight.X = object2.Sprite.FrameWidth * (object2.Sprite.Scale)
box2.WidthHeight.Y = object2.Sprite.FrameHeight * (object2.Sprite.Scale)
box2.Rotation = object2.Rotation
box2.RotationPointOffset.X = object2.Rect.RotationPointOffset.X * (object2.Sprite.Scale)
box2.RotationPointOffset.Y = object2.Rect.RotationPointOffset.Y * (object2.Sprite.Scale)
' Check for collision using the GDK_Box_Intersect function
' NOTE: Your GDK_Box_Intersect must support rotated boxes for this to work correctly.
IF GDK_Box_Intersect(box1, box2) THEN GDK_CheckCollision = GDK_TRUE
END FUNCTION
SUB GDK_GetIntersectionAABB (box1 AS GDK_Box, box2 AS GDK_Box, intersection AS GDK_Box)
DIM box1_corners(3) AS GDK_Vector, box2_corners(3) AS GDK_Vector
' Initialize min/max with the first corner of Box1
minX = box1_corners(0).X: maxX = box1_corners(0).X
minY = box1_corners(0).Y: maxY = box1_corners(0).Y
' Expand min/max to include all corners of Box1
FOR i = 1 TO 3
IF box1_corners(i).X < minX THEN minX = box1_corners(i).X
IF box1_corners(i).X > maxX THEN maxX = box1_corners(i).X
IF box1_corners(i).Y < minY THEN minY = box1_corners(i).Y
IF box1_corners(i).Y > maxY THEN maxY = box1_corners(i).Y
NEXT i
' Expand min/max to include all corners of Box2
FOR i = 0 TO 3
IF box2_corners(i).X < minX THEN minX = box2_corners(i).X
IF box2_corners(i).X > maxX THEN maxX = box2_corners(i).X
IF box2_corners(i).Y < minY THEN minY = box2_corners(i).Y
IF box2_corners(i).Y > maxY THEN maxY = box2_corners(i).Y
NEXT i
' The intersection AABB is the combined AABB of both boxes.
intersection.Position.X = minX
intersection.Position.Y = minY
intersection.WidthHeight.X = maxX - minX
intersection.WidthHeight.Y = maxY - minY
END SUB
FUNCTION GetPixelFromImage~& (object AS GDK_Game_Object, x AS INTEGER, y AS INTEGER)
DIM pColor AS _UNSIGNED LONG
DIM frameX AS INTEGER, frameY AS INTEGER
DIM frameRow AS INTEGER, frameCol AS INTEGER
DIM imageWidth AS INTEGER
DIM old_source AS LONG
imageWidth = _WIDTH(object.Sprite.File)
' Adjust the 1-based frame to a 0-based index for calculation
DIM frameIndex AS INTEGER
frameIndex = object.Animation.Frame - 1
' Calculate the offset for the current frame
frameRow = frameIndex \ (imageWidth / object.Sprite.FrameWidth)
frameCol = frameIndex MOD (imageWidth / object.Sprite.FrameWidth)
frameX = frameCol * object.Sprite.FrameWidth
frameY = frameRow * object.Sprite.FrameHeight
' Adjust the local coordinates with the frame offset
x = x + frameX
y = y + frameY
' Save the current source and set the new one
old_source = _SOURCE
_SOURCE object.Sprite.File
' Check if the final coordinates are within the image bounds and get the pixel color
IF x >= 0 AND x < _WIDTH(object.Sprite.File) AND y >= 0 AND y < _HEIGHT(object.Sprite.File) THEN
pColor = POINT(x, y)
ELSE
pColor = object.Sprite.Alpha ' Assuming alpha is the clear color
END IF
FUNCTION GDK_CheckPixelCollision% (object1 AS GDK_Game_Object, object2 AS GDK_Game_Object)
DIM box1 AS GDK_Box, box2 AS GDK_Box
DIM intersection_AABB AS GDK_Box
DIM pixelPosition AS GDK_Vector, local1 AS GDK_Vector, local2 AS GDK_Vector
DIM x AS SINGLE, y AS SINGLE
DIM color1 AS _UNSIGNED LONG, color2 AS _UNSIGNED LONG
' Find the intersecting AABB
GDK_GetIntersectionAABB box1, box2, intersection_AABB
' Iterate over the intersecting area
FOR y = intersection_AABB.Position.Y TO intersection_AABB.Position.Y + intersection_AABB.WidthHeight.Y
FOR x = intersection_AABB.Position.X TO intersection_AABB.Position.X + intersection_AABB.WidthHeight.X
pixelPosition.X = x
pixelPosition.Y = y
' Calculate inverse rotation to map world coords to local sprite coords
CALL GDK_RotatePoint(local1, pixelPosition, object1.Vector, -object1.Rotation)
CALL GDK_RotatePoint(local2, pixelPosition, object2.Vector, -object2.Rotation)
' Get the pixel color from each sprite
color1 = GetPixelFromImage(object1, local1.X, local1.Y)
color2 = GetPixelFromImage(object2, local2.X, local2.Y)
' Check for color-key transparency
IF color1 <> object1.Sprite.Alpha AND color2 <> object2.Sprite.Alpha THEN
' Collision detected at this pixel, return immediately
GDK_CheckPixelCollision = GDK_TRUE
EXIT FUNCTION
END IF
NEXT x
NEXT y
' No collision detected after checking all overlapping pixels
GDK_CheckPixelCollision = GDK_FALSE
END FUNCTION
'/// GAME STUFF ///
SUB GDK_GenerateNebula (imageHandle AS LONG)
DIM dest AS LONG, width AS LONG, height AS LONG
DIM x AS INTEGER, y AS INTEGER
DIM noise_scale AS SINGLE, noise_offset AS SINGLE
DIM noise_val AS SINGLE
DIM final_color AS _UNSIGNED LONG
DIM star_prob AS INTEGER
' Improved noise settings
noise_scale = 0.005 ' Adjust for larger or smaller cloud patterns
noise_offset = RND * 1000
' Get the screen dimensions
width = _WIDTH(0)
height = _HEIGHT(0)
' Set the destination to the new image
dest = _DEST
_DEST imageHandle
' Clear the image with a dark space color
PAINT (0, 0), _RGB32(0, 0, 20)
' Fill the image with multi-layered noise colors
FOR y = 0 TO height - 1
FOR x = 0 TO width - 1
' Start with a base noise layer
noise_val = SIN((x + noise_offset) * noise_scale) + COS((y + noise_offset) * noise_scale)
' Add an even higher frequency layer (finer detail)
noise_val = noise_val + SIN((x + noise_offset) * noise_scale * 4) + COS((y + noise_offset) * noise_scale * 4)
' Normalize noise_val to a 0-1 range
noise_val = (noise_val + 3) / 6 ' Range is approx -3 to 3
' Create color based on the normalized noise value
SELECT CASE noise_val
CASE IS < 0.3
final_color = _RGB32(10 + noise_val * 14, 10, 20 + noise_val * 16) ' Deep purple/blue
CASE IS < 0.6
final_color = _RGB32(25 + noise_val * 10, 40 + noise_val * 14, 10 + noise_val * 42) ' Brighter purple/pink
CASE ELSE
final_color = _RGB32(15 + noise_val * 10, 15 + noise_val * 15, 2 + noise_val * 31) ' Orange/red
END SELECT
PSET (x, y), final_color
NEXT x
NEXT y
' Add some stars
FOR x = 1 TO width * height / 500
star_prob = RND * 100
IF star_prob > 95 THEN
PSET (RND * width, RND * height), _RGB32(255, 255, 255)
ELSEIF star_prob > 90 THEN
PSET (RND * width, RND * height), _RGB32(200, 200, 255)
END IF
NEXT x
' Restore the original destination
_DEST dest
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Draw_Repulsor_Wave (Player AS GDK_Game_Object, Radius AS SINGLE)
DIM lColor AS _UNSIGNED LONG
DIM i AS INTEGER
DIM ringAlpha AS SINGLE
DIM maxRings AS INTEGER
DIM ringRadius AS SINGLE
maxRings = 8 ' Draw 8 concentric rings
' Loop to draw multiple rings
FOR i = 0 TO maxRings - 1
' Calculate the radius for the current ring
ringRadius = Radius - (i * 2) ' Space rings by 2 pixels
' Ensure radius is not negative
IF ringRadius <= 0 THEN EXIT FOR
' Calculate the alpha for the current ring, fading outwards
ringAlpha = 100 - (i * (100 / maxRings)) ' Start at 100 alpha and decrease
' Set the color for the ring
lColor = _RGBA32(255, 255, 255, ringAlpha)
CIRCLE (Player.Vector.X, Player.Vector.Y), ringRadius, lColor
NEXT i
END SUB
SUB Draw_Shield (Player AS GDK_Game_Object, Hit%)
DIM maxHealth AS INTEGER: maxHealth = 10 ' Maximum health value - when the shield fails
DIM alphaRatio AS SINGLE
DIM alphaValue AS INTEGER
' Calculate the ratio of current health to maximum health
' Use a minimum health of 1 to avoid division by zero
IF PHealth > 2 THEN
' Check if we are in the charging phase
IF transitionPhase = 4 AND _KEYDOWN(100304) OR transitionPhase = 6 AND _KEYDOWN(100304) THEN
' Calculate brightness based on charge time
Brightness = (gt# - repelChargeTimer) / REPEL_CHARGE_TIME * 255
IF Brightness > 255 THEN Brightness = 255
ELSE
Brightness = 0 ' No extra brightness when not charging
END IF
alphaRatio = PHealth / maxHealth
FOR j% = 0 TO 15 + (Brightness / 25)
' Modify alpha and color based on brightness
alphaValue = (j% * 10) * alphaRatio
IF NOT Hit% THEN
CLR~& = _RGBA32(j% * 5 + Brightness, j% * 4 + Brightness, (j% * 20) + Brightness, alphaValue)
ELSE
CLR~& = _RGBA32(215, 0, 0, (j% * 5))
END IF
Radius! = (Player.Sprite.FrameWidth / 2) + (1 * j%)
MinStep! = 1 / ((_PI * 2) * Radius!) ' Optimization from CodeGuy
FOR i = 0 TO (_PI * 2) STEP MinStep!
PSET (Player.Vector.X + (Radius! * COS(i)), Player.Vector.Y + (Radius! * SIN(i))), CLR~&
NEXT
NEXT
END IF
Hit% = GDK_FALSE
SUB BossModeState (Enemy() AS GDK_Game_Object, Ship AS GDK_Game_Object)
STATIC Repelling
'// Player movement based on rotation
IF _KEYDOWN(19200) THEN Ship.Rotation = Ship.Rotation - .15
IF _KEYDOWN(19712) THEN Ship.Rotation = Ship.Rotation + .15
IF _KEYDOWN(18432) AND Ship.Speed < 10 THEN Ship.Speed = Ship.Speed + .15
IF _KEYDOWN(20480) AND Ship.Speed > 0 THEN Ship.Speed = Ship.Speed - .15
'// Bullets movement and collisions checks - destroy bullets when they leave the screen
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN
GDK_Vector_Update Bullet(i%).Vector, Bullet(i%).Rotation, Bullet(i%).Speed
IF Bullet(i%).Vector.Y < 0 OR Bullet(i%).Vector.Y > 600 OR Bullet(i%).Vector.X < 0 OR Bullet(i%).Vector.X > 800 THEN
Bullet(i%).Exists = GDK_FALSE '// off screen so destroy
ELSE '// check for enemy collisions
FOR j% = 0 TO MAX_ENEMIES - 1
IF Enemy(j%).Exists = GDK_TRUE THEN
Enemy(j%).Sprite.Scale = .85 '//collision box scaling
IF GDK_CheckCollision(Bullet(i%), Enemy(j%)) THEN '// Bullet has hit an enemy bounding box
'IF GDK_CheckPixelCollision(Bullet(i%), Enemy(j%)) THEN '// Pixel perfect check - Faulty
Bullet(i%).Exists = GDK_FALSE
Enemy(j%).Exists = GDK_FALSE
EXIT FOR
'END IF
END IF
Enemy(j%).Sprite.Scale = 1 '// restore original scale
END IF
NEXT
END IF
END IF
NEXT
'// enemy shooting
IF gt# - eShoot_Timer >= eShoot_Time THEN
FOR i% = MAX_ENEMIES - 1 TO 0 STEP -1
IF NOT Enemy_Bullet.Exists THEN '// bulet is dstroyed
nme = RND * (MAX_ENEMIES - 1) '// Randomly choose a enemy to fire at us
IF Enemy(nme).Exists THEN
Enemy_Bullet.Exists = GDK_TRUE
GDK_Vector_New Enemy_Bullet.Vector, Enemy(nme).Vector.X, Enemy(nme).Vector.Y
Enemy_Bullet.Speed = 15
Enemy_Bullet.Rotation = _ATAN2(Ship.Vector.Y - Enemy(nme).Vector.Y, Ship.Vector.X - Enemy(nme).Vector.X)
EXIT FOR
END IF
END IF
NEXT
eShoot_Timer = gt#
END IF
IF Enemy_Bullet.Exists THEN
GDK_Vector_Update Enemy_Bullet.Vector, Enemy_Bullet.Rotation, Enemy_Bullet.Speed
IF Enemy_Bullet.Vector.Y < 0 OR Enemy_Bullet.Vector.Y > 600 OR Enemy_Bullet.Vector.X < 0 OR Enemy_Bullet.Vector.X > 800 THEN
Enemy_Bullet.Exists = GDK_FALSE
ELSE '// check player collision
IF GDK_CheckShieldCollision(Enemy_Bullet, Ship) THEN
IF PHealth <= 2 THEN '// no more shield so direct hit needed
IF GDK_CheckCollision(Enemy_Bullet, Ship) THEN '// player hit
Enemy_Bullet.Exists = GDK_FALSE
IF PHealth > 0 THEN
Hit% = GDK_TRUE
PHealth = PHealth - 1
ELSE '// GAme over
CLS
PRINT "You got blown up! But thanks for playing"
_DISPLAY
_DELAY 5
SYSTEM
END IF
END IF
ELSE
Hit% = GDK_TRUE
PHealth = PHealth - 1
Enemy_Bullet.Exists = GDK_FALSE
END IF
ELSE
Hit% = GDK_FALSE
END IF
END IF
END IF
'// INPUT
'// Space bar shoots
IF _KEYDOWN(32) THEN
IF gt# - Shoot_Timer >= Shoot_Time THEN
FOR i% = 0 TO Max_Bullets& - 1
IF NOT Bullet(i%).Exists THEN
Bullet(i%).Exists = GDK_TRUE
GDK_Vector_New Bullet(i%).Vector, Ship.Vector.X, Ship.Vector.Y
Bullet(i%).Speed = 15
Bullet(i%).Rotation = Ship.Rotation
EXIT FOR
END IF
NEXT
Shoot_Timer = gt#
END IF
END IF
IF NOT Repelling THEN
' Check for shift key input to start charging
IF _KEYDOWN(100304) AND repelChargeTimer = 0 THEN repelChargeTimer = gt# ' Start timer
' Check for full charge
IF repelChargeTimer > 0 AND gt# - repelChargeTimer >= REPEL_CHARGE_TIME THEN
Repelling = GDK_TRUE
repelChargeTimer = 0 ' Reset timer
END IF
ELSE
Draw_Repulsor_Wave Ship, RepulsorRadius
RepulsorRadius = RepulsorRadius + 4 ' Expand the wave
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
' Check if enemy is within the repulsor radius
dx = Ship.Vector.X - Enemy(i).Vector.X
dy = Ship.Vector.Y - Enemy(i).Vector.Y
distance = SQR(dx * dx + dy * dy)
IF distance <= RepulsorRadius THEN Enemy(i).Exists = GDK_FALSE ' Destroy enemy
END IF
NEXT i
' Check if wave has expanded far enough
IF RepulsorRadius > _WIDTH / 4 * 1.5 THEN
RepulsorRadius = 0 ' Reset for next time
Repelling = GDK_FALSE
END IF
END IF
'// Bullets
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN GDK_GameObject_Draw Bullet(i%), Bullet(i%).Animation.Frame
NEXT
IF Enemy_Bullet.Exists THEN GDK_GameObject_Draw Enemy_Bullet, Enemy_Bullet.Animation.Frame
FUNCTION NumActiveEnemies (Enemy() AS GDK_Game_Object)
DIM count AS INTEGER, i AS INTEGER
count = 0
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
count = count + 1
END IF
NEXT i
NumActiveEnemies = count
END FUNCTION
SUB TransitionState (Enemy() AS GDK_Game_Object, Ship AS GDK_Game_Object)
STATIC playerTargetX AS SINGLE, playerTargetY AS SINGLE
STATIC enemyTargetX(39) AS SINGLE, enemyTargetY(39) AS SINGLE
DIM i AS INTEGER, j AS INTEGER
DIM angle AS SINGLE, screenCenterX AS SINGLE, screenCenterY AS SINGLE
DIM active_enemy_count AS INTEGER
DIM move_speed AS SINGLE
DIM enemies_in_line AS _BYTE
DIM player_at_center AS _BYTE
DIM enemies_in_formation AS _BYTE
DIM enemy_line_x AS SINGLE
screenCenterX = _WIDTH / 2
screenCenterY = _HEIGHT / 2
move_speed = 5 ' Speed for player and enemy movement
SELECT CASE transitionPhase
CASE 0 ' Begin transition - pause, clear bullets, set target positions
playerTargetX = screenCenterX
playerTargetY = screenCenterY
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN Enemy(i).Speed = 0 ' Pause enemies
NEXT i
FOR i = 0 TO Max_Bullets - 1
Bullet(i).Exists = GDK_FALSE
NEXT i
Enemy_Bullet.Exists = GDK_FALSE
transitionPhase = 1
CASE 1 ' Move player and enemies towards line simultaneously
player_at_center = move_to_position(Ship, playerTargetX, playerTargetY, move_speed)
enemies_in_line = GDK_TRUE
enemy_line_x = screenCenterX - (NumActiveEnemies(Enemy()) / 2 * 60) ' Start position for line
j = 0
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
enemies_in_line = enemies_in_line AND move_to_position(Enemy(i), enemy_line_x + j * 60, 50, move_speed)
j = j + 1
END IF
NEXT i
IF enemies_in_line = GDK_TRUE AND player_at_center = GDK_TRUE THEN
transitionPhase = 2 ' Proceed to store circular positions
END IF
CASE 2 ' Calculate and store final circular positions
active_enemy_count = NumActiveEnemies(Enemy())
j = 0
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
angle = (j * 2 * 3.14159 / active_enemy_count)
enemyTargetX(i) = screenCenterX + COS(angle) * 220
enemyTargetY(i) = screenCenterY + SIN(angle) * 220
Enemy(i).Rotation = _ATAN2(screenCenterY - Enemy(i).Vector.Y, screenCenterX - Enemy(i).Vector.X)
j = j + 1
END IF
NEXT i
transitionPhase = 3 ' Proceed to move enemies to final positions
CASE 3 ' Move enemies to final circular positions
enemies_in_formation = GDK_TRUE
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
enemies_in_formation = enemies_in_formation AND move_to_position(Enemy(i), enemyTargetX(i), enemyTargetY(i), move_speed)
END IF
NEXT i
IF enemies_in_formation = GDK_TRUE THEN
transitionPhase = 4 ' All enemies in position, finalize transition
END IF
CASE 4 ' Tractor beam phase: grab player, charge shield
' Draw instructions on screen
COLOR _RGB32(100, 100, 255), _RGBA32(0, 0, 0, 0): _PRINTSTRING (280, 120), "HOLD L Shift TO CHARGE REPULSOR WAVE!"
' Draw tractor beams from all active enemies to the player
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN DrawTractorBeam Enemy(i), Ship
NEXT i
' Check for shift key input to start charging
IF _KEYDOWN(100304) AND repelChargeTimer = 0 THEN repelChargeTimer = gt# ' Start timer
' Check for full charge
IF repelChargeTimer > 0 AND gt# - repelChargeTimer >= REPEL_CHARGE_TIME THEN
repelChargeTimer = 0 ' Reset timer
transitionPhase = 5 ' Proceed to repulsor wave
END IF
CASE 5 'Repulsor wave phase
Draw_Repulsor_Wave Ship, RepulsorRadius
RepulsorRadius = RepulsorRadius + 4 ' Expand the wave
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
' Check if enemy is within the repulsor radius
dx = Ship.Vector.X - Enemy(i).Vector.X
dy = Ship.Vector.Y - Enemy(i).Vector.Y
distance = SQR(dx * dx + dy * dy)
IF distance <= RepulsorRadius THEN
Enemy(i).Exists = GDK_FALSE ' Destroy enemy
END IF
END IF
NEXT i
' Check if wave has expanded far enough (e.g., off-screen)
IF RepulsorRadius > _WIDTH / 2 * 1.5 THEN
transitionPhase = 6 ' Proceed to finalize
RepulsorRadius = 0 ' Reset for next time
END IF
FUNCTION move_to_position%% (object AS GDK_Game_Object, targetX AS SINGLE, targetY AS SINGLE, speed AS SINGLE)
DIM at_target AS _BYTE
DIM x_moved AS _BYTE, y_moved AS _BYTE
at_target = GDK_TRUE
' Calculate the vector to the target
dx = targetX - object.Vector.X
dy = targetY - object.Vector.Y
' Check if we are close enough to the target
IF ABS(dx) < speed AND ABS(dy) < speed THEN
object.Vector.X = targetX ' Snap to position
object.Vector.Y = targetY
move_to_position = GDK_TRUE
EXIT FUNCTION
END IF
' Move towards the target without overshooting
move_x = dx / SQR(dx * dx + dy * dy) * speed
move_y = dy / SQR(dx * dx + dy * dy) * speed
'// Bullets movement and collisions checks - destroy bullets when they leave the screen
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN
GDK_Vector_Update Bullet(i%).Vector, Bullet(i%).Rotation, Bullet(i%).Speed
IF Bullet(i%).Vector.Y < 0 THEN
Bullet(i%).Exists = GDK_FALSE '// off screen so destroy
ELSE '// check for enemy collisions
FOR j% = 0 TO MAX_ENEMIES - 1
IF Enemy(j%).Exists = GDK_TRUE THEN
Enemy(j%).Sprite.Scale = .75 '//collision box scaling
IF GDK_CheckCollision(Bullet(i%), Enemy(j%)) THEN '// Bullet has hit an enemy bounding box
'IF GDK_CheckPixelCollision(Bullet(i%), Enemy(j%)) THEN '// Pixel perfect check - Faulty
Bullet(i%).Exists = GDK_FALSE
Enemy(j%).Exists = GDK_FALSE
EXIT FOR
'END IF
END IF
Enemy(j%).Sprite.Scale = 1 '// restore original scale
END IF
NEXT
END IF
END IF
NEXT
'// Enemy movement
IF Enemy_Left = GDK_TRUE THEN
IF Enemy_Move > 0 THEN Enemy_Move = Enemy_Move - 1 ELSE Enemy_Left = GDK_FALSE
ELSE
IF Enemy_Move < 120 THEN Enemy_Move = Enemy_Move + 1 ELSE Enemy_Left = GDK_TRUE
END IF
FOR i% = 0 TO MAX_ENEMIES - 1
GDK_ANIMATION_UPDATE Enemy(i%).Animation '// Enemy animation update
IF Enemy_Left = GDK_TRUE THEN Enemy(i%).Vector.X = Enemy(i%).Vector.X - 1 ELSE Enemy(i%).Vector.X = Enemy(i%).Vector.X + 1
NEXT
'// enemy shooting
IF gt# - eShoot_Timer >= eShoot_Time THEN
FOR i% = MAX_ENEMIES - 1 TO 0 STEP -1
IF NOT Enemy_Bullet.Exists THEN '// bulet is dstroyed
nme = RND * 39 '// Randomly choose a enemy to fire at us
IF Enemy(nme).Exists THEN
Enemy_Bullet.Exists = GDK_TRUE
GDK_Vector_New Enemy_Bullet.Vector, Enemy(nme).Vector.X, Enemy(nme).Vector.Y
Enemy_Bullet.Speed = 10
Enemy_Bullet.Rotation = _ATAN2(Ship.Vector.Y - Enemy(nme).Vector.Y, Ship.Vector.X - Enemy(nme).Vector.X)
EXIT FOR
END IF
END IF
NEXT
eShoot_Timer = gt#
END IF
IF Enemy_Bullet.Exists THEN
GDK_Vector_Update Enemy_Bullet.Vector, Enemy_Bullet.Rotation, Enemy_Bullet.Speed
IF Enemy_Bullet.Vector.Y > 600 THEN
Enemy_Bullet.Exists = GDK_FALSE
ELSE '// check player collision
IF GDK_CheckCollision(Enemy_Bullet, Ship) THEN '// player hit
Enemy_Bullet.Exists = GDK_FALSE
IF PHealth > 0 THEN
Hit% = GDK_TRUE
PHealth = PHealth - 1
ELSE '// GAme over
CLS
PRINT "You got blown the F up! But thanks for playing"
_DISPLAY
_DELAY 5
SYSTEM
END IF
ELSE
Hit% = GDK_FALSE
END IF
END IF
END IF
'// INPUT
'// Space bar shoots
IF _KEYDOWN(32) THEN
IF gt# - Shoot_Timer >= Shoot_Time THEN
FOR i% = 0 TO Max_Bullets& - 1
IF NOT Bullet(i%).Exists THEN
Bullet(i%).Exists = GDK_TRUE
GDK_Vector_New Bullet(i%).Vector, Ship.Vector.X, Ship.Vector.Y
Bullet(i%).Speed = 5
Bullet(i%).Rotation = 6 * ATN(1)
EXIT FOR
END IF
NEXT
Shoot_Timer = gt#
END IF
END IF
'// Ship left and right movement
IF _KEYDOWN(19200) THEN '// left
IF Ship.Vector.X > Player_MinX THEN Ship.Vector.X = Ship.Vector.X - 3
ELSEIF _KEYDOWN(19712) THEN '// right
IF Ship.Vector.X < Player_MaxX THEN Ship.Vector.X = Ship.Vector.X + 3
END IF
FUNCTION GDK_CheckShieldCollision (Player AS GDK_Game_Object, EnemyBullet AS GDK_Game_Object)
DIM playerShield AS GDK_BoundingCircle
DIM enemyBulletCircle AS GDK_BoundingCircle
DIM dx AS SINGLE, dy AS SINGLE
DIM distanceSquared AS SINGLE
DIM radiiSum AS INTEGER
' Create the bounding circle for the player's shield
playerShield.X = Player.Vector.X
playerShield.Y = Player.Vector.Y
playerShield.Radius = (Player.Sprite.FrameWidth / 2.4) + 35 ' Using a larger radius for the shield
' Create the bounding circle for the enemy bullet
enemyBulletCircle.X = EnemyBullet.Vector.X
enemyBulletCircle.Y = EnemyBullet.Vector.Y
enemyBulletCircle.Radius = EnemyBullet.Sprite.FrameWidth / 2 ' Assuming bullet is a circle
' Calculate the squared distance between the centers
dx = playerShield.X - enemyBulletCircle.X
dy = playerShield.Y - enemyBulletCircle.Y
distanceSquared = (dx * dx) + (dy * dy)
' Calculate the sum of the radii
radiiSum = playerShield.Radius + enemyBulletCircle.Radius
' Compare the squared distance with the squared sum of the radii
' Using squared values avoids the slower SQR function
IF distanceSquared <= (radiiSum * radiiSum) THEN
GDK_CheckShieldCollision = GDK_TRUE
ELSE
GDK_CheckShieldCollision = GDK_FALSE
END IF
END FUNCTION
' Draw multiple lines to create a "scanning" effect
FOR i = 0 TO 5
' Add a small random offset to the line's endpoint
randomOffset = (RND * -10) + 20 ' Adjust 20 for more or less random wobble
lineX = Player.Vector.X + randomOffset
lineY = Player.Vector.Y + randomOffset
' Draw the line from the enemy to the player's position
LINE (Enemy.Vector.X, Enemy.Vector.Y)-(lineX, lineY), lColor
NEXT i
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Populate_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
DIM start_x AS SINGLE
DIM start_y AS SINGLE
DIM y_spacing AS SINGLE
DIM row_index AS INTEGER
' Loop through all enemies
FOR i = 0 TO MAX_ENEMIES - 1
Enemies(i).Exists = GDK_TRUE
' Set initial position off-screen at the top
Enemies(i).Vector.Y = -SPAWN_OFFSET_Y
' Arrange enemies evenly across the X-axis in rows
row_index = i / ENEMIES_PER_ROW
start_x = 100 + (row_index * y_spacing)
Enemies(i).Vector.X = start_x
'Enemies(i).Rotation = -(_PI)
' Store initial x position to maintain horizontal spacing
Enemies(i).X_Initial = Enemies(i).Vector.X
' Stagger the spawn timing slightly
Enemies(i).Speed = (i / ENEMIES_PER_ROW) * SPAWN_DELAY
NEXT i
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
' -----------------------------------------------------------------------------
' Updates the enemies' positions for the sine wave and respawns them.
' -----------------------------------------------------------------------------
SUB Update_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
' Advance the global time counter
GameTime = GameTime + 1
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN
' Store the current position as the last position for the next frame
Enemies(i).Last_Vector.X = Enemies(i).Vector.X
Enemies(i).Last_Vector.Y = Enemies(i).Vector.Y
GDK_ANIMATION_UPDATE Enemies(i).Animation
IF GameTime > Enemies(i).Speed THEN
' Move the enemy down the screen
Enemies(i).Vector.Y = Enemies(i).Vector.Y + VERTICAL_SPEED
' Apply sine wave to the X position
sine_y_offset = (Enemies(i).Vector.Y / SCREEN_HEIGHT) * (2 * _PI)
Enemies(i).Vector.X = Enemies(i).X_Initial + SINE_AMPLITUDE * SIN(sine_y_offset * SINE_FREQUENCY)
' Calculate the change in position (velocity vector)
dx = Enemies(i).Vector.X - Enemies(i).Last_Vector.X
dy = Enemies(i).Vector.Y - Enemies(i).Last_Vector.Y
' Use ATAN2 to set the rotation
' Note: ATAN2 returns radians, GDK uses degrees. Multiply by 180 / PI to convert
Enemies(i).Rotation = _ATAN2(dy, dx) - (_PI / 2)
' Check if enemy is off-screen at the bottom and respawn
IF Enemies(i).Vector.Y > SCREEN_HEIGHT + SPAWN_OFFSET_Y THEN
Enemies(i).Vector.Y = -SPAWN_OFFSET_Y
Enemies(i).Speed = GameTime + SPAWN_DELAY
' Reset last position for the new spawn
Enemies(i).Last_Vector.X = Enemies(i).Vector.X
Enemies(i).Last_Vector.Y = Enemies(i).Vector.Y
END IF
END IF
END IF
NEXT i
END SUB
SUB Render_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN GDK_GameObject_Draw Enemies(i), Enemies(i).Animation.Frame
NEXT
END SUB
09-09-2025, 03:01 PM (This post was last modified: 09-09-2025, 03:02 PM by Pete.)
Inefficient. Uses too many buttons. Mine only uses one!
To be fair, Steve one-upped me, as usual. His version is controlled solely by his mind, but after countless hours of coding, all he can get is one blip out of it.
Oh well...
Looking good John! It did throw an error code 9 at 1209. I'm sure it's Steve's fault.
Maybe Clippy can help you with it. I don't have his exact address, but I know it's somewhere on the corner of Hell and Douche Street.
09-09-2025, 04:17 PM (This post was last modified: 09-09-2025, 04:18 PM by Unseen Machine.)
Ha! Does that mean you didnt win!? (Lol, sorry its all a bit complicated) it went way off tangent when you said i get no points! Like damn bro! Emotional DAMAGE!
I think ive fixed most of the errors now and it should also take you onto the next level, (it sucks, im trying to get them to like swarm in groups but no gravy! Just generic sine wave movement for now! Thanks for trying it though! I spent at least 3 hours on a 1 that should have been a ! and an Enemy that should have been a BossEnemy...debugging is hell sometimes!
Code: (Select All)
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Unseen's GDK 3 DEV - Space Invaders Clone - \\
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
RANDOMIZE TIMER
' Sine wave parameters
CONST SINE_AMPLITUDE = 200.0 ' Maximum horizontal offset from the center
CONST SINE_FREQUENCY = 7 ' Speed of the wave
CONST VERTICAL_SPEED = .75 ' Speed at which enemies move down the screen
' Spawning parameters
CONST SPAWN_OFFSET_Y = 50 ' Extra space to spawn off-screen
CONST SPAWN_DELAY = 200 ' Delay between enemy spawns (in frames)
'///////////////////////////////////// System Initialisation ////////////////////////////////////////////////
CHDIR "Tutorials\" '// The root path for the files we will use
DIM SHARED GameState AS INTEGER
DIM SHARED TransitionStarted AS _BYTE
DIM SHARED Ship AS GDK_Game_Object, Bullet(19) AS GDK_Game_Object, Max_Bullets&, EnemyAnim AS GDK_Animation
DIM SHARED WallPaper AS LONG, Enemy_Bullet AS GDK_Game_Object, Hit%
DIM SHARED PHealth AS INTEGER: PHealth = 10
DIM SHARED gt# '// Global timer
DIM SHARED Enemy_Left AS _BYTE, Enemy_Move AS _BYTE, eShoot_Time AS DOUBLE, eShoot_Timer AS DOUBLE
DIM SHARED Shoot_Time AS DOUBLE, Shoot_Timer AS DOUBLE
DIM SHARED Player_MinX AS INTEGER, Player_MaxX AS INTEGER, Player_MinY AS INTEGER, Player_MaxY AS INTEGER
DIM SHARED repelChargeTimer AS DOUBLE
DIM SHARED repelWaveReady AS _BYTE, RepulsorRadius
DIM SHARED transitionPhase AS INTEGER
Max_Bullets& = 20
RepulsorRadius = 20
DIM SHARED Enemy(MAX_ENEMIES - 1) AS GDK_Game_Object
DIM SHARED BossEnemy(MAX_ENEMIES - 1) AS GDK_Game_Object
DIM SHARED GameTime AS SINGLE, MainMode%
'// Now we load the game objects (sprites)
GDK_GameObject_New Ship, "Hunter_Red.png", 1, 1, 400, 530, 0, 6 * ATN(1)
GDK_Sprite_SetRotationAsCenter Ship.Sprite
GDK_ANIMATION_NEW Ship.Animation, 1, 1, 1, 0, 0
CALL PlayGameState
IF NumActiveEnemies(Enemy()) <= 10 AND TransitionStarted = GDK_FALSE THEN
TransitionStarted = GDK_TRUE
GameState = GDK_STATE_TRANSITION
END IF
CASE GDK_STATE_TRANSITION
TransitionState Enemy(), Ship
Populate_Enemies_2 BossEnemy()
Shoot_Time = .2 '// Fire 2 bullets every one second
Player_MinX = 40 '// X axis limits
Player_MinY = 40 '// X axis limits
Player_MaxX = 760
Player_MaxY = 560
eShoot_Time = 3
CASE GDK_STATE_BOSS_MODE
BossModeState BossEnemy(), Ship
END SELECT
'// Enemy
FOR i% = 0 TO MAX_ENEMIES - 1
IF Enemy(i%).Exists = GDK_TRUE THEN GDK_GameObject_Draw Enemy(i%), Enemy(i%).Animation.Frame
NEXT
'// Bullets
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN GDK_GameObject_Draw Bullet(i%), Bullet(i%).Animation.Frame
NEXT
IF Enemy_Bullet.Exists THEN GDK_GameObject_Draw Enemy_Bullet, Enemy_Bullet.Animation.Frame
'// Player
GDK_GameObject_Draw Ship, Ship.Animation.Frame
Draw_Shield Ship, Hit%
TYPE GDK_Animation '// Animation data.
Frame AS INTEGER '// The current frame - When data is loaded/set...this defaults to StartFrame - Is the index in the Frame array
StartFrame AS INTEGER '// 1st frame in sequence
ResetFrame AS INTEGER '// Last frame in sequence
Time AS DOUBLE '// Time between frame changes
Timer AS DOUBLE '// The last animation update
END TYPE
TYPE GDK_Box
Position AS GDK_Vector
WidthHeight AS GDK_Vector
Rotation AS SINGLE
RotationPointOffset AS GDK_Vector
END TYPE
TYPE GDK_Sprite
File AS LONG
Width AS INTEGER
Height AS INTEGER
Alpha AS _UNSIGNED LONG
RotationX AS SINGLE
RotationY AS SINGLE
Scale AS SINGLE
IsVisible AS _BYTE
XFrameCount AS _BYTE
YFrameCount AS _BYTE
TotalFrameCount AS INTEGER
FrameWidth AS INTEGER
FrameHeight AS INTEGER
END TYPE
TYPE GDK_Game_Object
Sprite AS GDK_Sprite
Vector AS GDK_Vector
Rotation AS SINGLE
Speed AS SINGLE
Rect AS GDK_Box
Exists AS _BYTE
Animation AS GDK_Animation
Last_Vector AS GDK_Vector
X_Initial AS SINGLE
END TYPE
TYPE GDK_BoundingCircle
X AS INTEGER
Y AS INTEGER
Radius AS INTEGER
END TYPE
SUB GDK_DrawBox (Box AS GDK_Box, bcolor AS LONG)
DIM Corners(3) AS GDK_Vector
CALL GDK_GetBoxCorners(Box, Corners())
LINE (Corners(0).X, Corners(0).Y)-(Corners(1).X, Corners(1).Y), bcolor
LINE (Corners(1).X, Corners(1).Y)-(Corners(2).X, Corners(2).Y), bcolor
LINE (Corners(2).X, Corners(2).Y)-(Corners(3).X, Corners(3).Y), bcolor
LINE (Corners(3).X, Corners(3).Y)-(Corners(0).X, Corners(0).Y), bcolor
END SUB
SUB GDK_RotatePoint (Result AS GDK_Vector, p AS GDK_Vector, pivot AS GDK_Vector, angle AS SINGLE)
DIM temp_x AS SINGLE, temp_y AS SINGLE
temp_x = p.X - pivot.X
temp_y = p.Y - pivot.Y
Result.X = temp_x * COS(angle) - temp_y * SIN(angle)
Result.Y = temp_x * SIN(angle) + temp_y * COS(angle)
Result.X = Result.X + pivot.X
Result.Y = Result.Y + pivot.Y
END SUB
' Calculate the absolute position of the rotation pivot
Pivot.X = Box.Position.X + Box.RotationPointOffset.X
Pivot.Y = Box.Position.Y + Box.RotationPointOffset.Y
FUNCTION GDK_Overlap_On_Axis (Corners1() AS GDK_Vector, Corners2() AS GDK_Vector, Axis AS GDK_Vector)
DIM Min1 AS SINGLE, Max1 AS SINGLE, Min2 AS SINGLE, Max2 AS SINGLE
DIM Projection AS SINGLE, I AS LONG
FOR I = 0 TO 3
Projection = Corners1(I).X * Axis.X + Corners1(I).Y * Axis.Y
IF Projection < Min1 THEN Min1 = Projection
IF Projection > Max1 THEN Max1 = Projection
NEXT I
FOR I = 0 TO 3
Projection = Corners2(I).X * Axis.X + Corners2(I).Y * Axis.Y
IF Projection < Min2 THEN Min2 = Projection
IF Projection > Max2 THEN Max2 = Projection
NEXT I
IF Max1 >= Min2 AND Max2 >= Min1 THEN GDK_Overlap_On_Axis = -1 ELSE GDK_Overlap_On_Axis = 0
END FUNCTION
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// SAT Function
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_Box_Intersect (Box1 AS GDK_Box, Box2 AS GDK_Box)
DIM Box1_Corners(3) AS GDK_Vector, Box2_Corners(3) AS GDK_Vector
DIM Axis(3) AS GDK_Vector, length AS SINGLE, I AS LONG
FOR I = 0 TO 1
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
FOR I = 2 TO 3
length = SQR(Axis(I).X * Axis(I).X + Axis(I).Y * Axis(I).Y)
IF length > 0 THEN Axis(I).X = Axis(I).X / length: Axis(I).Y = Axis(I).Y / length
IF NOT GDK_Overlap_On_Axis(Box1_Corners(), Box2_Corners(), Axis(I)) THEN GDK_Box_Intersect = 0: EXIT FUNCTION
NEXT I
SUB GDK_Sprite_SetRotationAsCenter (Sprite AS GDK_Sprite)
GDK_Sprite_SetRotationPoint Sprite, Sprite.Width / (Sprite.XFrameCount * 2), Sprite.Height / (Sprite.YFrameCount * 2)
END SUB
SUB Rotate_Scale_AroundPoint (X AS LONG, Y AS LONG, Image AS LONG, Rotation AS SINGLE, ScaleX AS SINGLE, ScaleY AS SINGLE, PointX AS LONG, PointY AS LONG) '
DEFSNG A-Z
DIM pX(3) AS SINGLE: DIM pY(3) AS SINGLE
DIM W AS LONG, H AS LONG, I AS LONG, X2 AS LONG, Y2 AS LONG
W = _WIDTH(Image): H = _HEIGHT(Image)
pX(0) = -PointX: pY(0) = -PointY: pX(1) = -PointX: pY(1) = -PointY + H - 1: pX(2) = -PointX + W - 1: pY(2) = -PointY + H - 1: pX(3) = -PointX + W - 1: pY(3) = -PointY 'Set dest screen points
Radians = -Rotation / 57.29578: SINr = SIN(-Radians): COSr = COS(-Radians) 'Precalculate SIN & COS of angle
FOR I = 0 TO 3
pX(I) = pX(I) * ScaleX: pY(I) = pY(I) * ScaleY 'Scale
X2 = pX(I) * COSr + SINr * pY(I): pY(I) = pY(I) * COSr - pX(I) * SINr: pX(I) = X2 'Rotate Dest Points
pX(I) = pX(I) + X: pY(I) = pY(I) + Y 'Translate Dest Points
NEXT
_MAPTRIANGLE (0, 0)-(0, H - 1)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(1), pY(1))-(pX(2), pY(2))
_MAPTRIANGLE (0, 0)-(W - 1, 0)-(W - 1, H - 1), Image TO(pX(0), pY(0))-(pX(3), pY(3))-(pX(2), pY(2))
END SUB
SUB GDK_Sprite_Draw (Sprite AS GDK_Sprite, DestVector AS GDK_Vector, Rotation AS SINGLE, Frame%)
IF Frame% = 0 THEN Frame% = 1
_CLEARCOLOR Sprite.Alpha, Sprite.File
IF Sprite.IsVisible THEN
FrameXStart% = ((INT((Frame% - 1) MOD Sprite.XFrameCount)) * Sprite.FrameWidth)
FrameYStart% = ((INT((Frame% - 1) / Sprite.XFrameCount)) * Sprite.FrameHeight)
TmpImage& = _NEWIMAGE(Sprite.FrameWidth - 1, Sprite.FrameHeight - 1, 32)
_PUTIMAGE (0, 0), Sprite.File, TmpImage&, (FrameXStart%, FrameYStart%)-(FrameXStart% + Sprite.FrameWidth, FrameYStart% + Sprite.FrameHeight)
Rotate_Scale_AroundPoint DestVector.X, DestVector.Y, TmpImage&, -GDK_RadianToDegree(Rotation), Sprite.Scale, Sprite.Scale, Sprite.RotationX, Sprite.RotationY
'Rotate_Scale_Source_Rect DestVector.X, DestVector.Y, Sprite.File, -GDK_RadianToDegree(Rotation), Sprite.Scale, FrameXStart%, FrameYStart%, Sprite.FrameWidth - 1, Sprite.FrameHeight - 1, Sprite.RotationX, Sprite.RotationY
_FREEIMAGE TmpImage&
END IF
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Rotate_Scale_Source_Rect (X AS SINGLE, Y AS SINGLE, Image AS LONG, Rotation AS DOUBLE, Scale AS SINGLE, _
FrameXStart AS INTEGER, FrameYStart AS INTEGER, _
FrameWidth AS INTEGER, FrameHeight AS INTEGER, _
RotX AS LONG, RotY AS LONG)
DEFSNG A-Z
DIM pX(3) AS SINGLE, pY(3) AS SINGLE
DIM I AS LONG, X2 AS SINGLE, Y2 AS SINGLE
' Use FrameWidth and FrameHeight directly
DIM W AS SINGLE, H AS SINGLE
W = FrameWidth: H = FrameHeight
' Define destination points relative to rotation point
pX(0) = -RotX: pY(0) = -RotY
pX(1) = -RotX: pY(1) = -RotY + H
pX(2) = -RotX + W: pY(2) = -RotY + H
pX(3) = -RotX + W: pY(3) = -RotY
DIM SINr AS SINGLE, COSr AS SINGLE
SINr = SIN(Rotation): COSr = COS(Rotation)
FOR I = 0 TO 3
' Scale
pX(I) = pX(I) * Scale: pY(I) = pY(I) * Scale
SUB GDK_GameObject_Draw (GameObject AS GDK_Game_Object, AnimationFrame%)
GDK_Sprite_Draw GameObject.Sprite, GameObject.Vector, GameObject.Rotation, AnimationFrame%
END SUB
SUB GDK_ANIMATION_UPDATE (Anim AS GDK_Animation)
IF gt# - Anim.Timer >= Anim.Time THEN
IF Anim.Frame = Anim.ResetFrame THEN
Anim.Frame = Anim.StartFrame
ELSE
Anim.Frame = Anim.Frame + 1
END IF
Anim.Timer = gt#
END IF
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
FUNCTION GDK_CheckCollision (object1 AS GDK_Game_Object, object2 AS GDK_Game_Object)
DIM box1 AS GDK_Box
DIM box2 AS GDK_Box
' Create the bounding box data for object1 using its Rect and Sprite
box1.Position.X = object1.Vector.X
box1.Position.Y = object1.Vector.Y
box1.WidthHeight.X = object1.Sprite.FrameWidth * (object1.Sprite.Scale)
box1.WidthHeight.Y = object1.Sprite.FrameHeight * (object1.Sprite.Scale)
box1.Rotation = object1.Rotation
box1.RotationPointOffset.X = object1.Rect.RotationPointOffset.X * (object1.Sprite.Scale)
box1.RotationPointOffset.Y = object1.Rect.RotationPointOffset.Y * (object1.Sprite.Scale)
' Create the bounding box data for object2 using its Rect and Sprite
box2.Position.X = object2.Vector.X
box2.Position.Y = object2.Vector.Y
box2.WidthHeight.X = object2.Sprite.FrameWidth * (object2.Sprite.Scale)
box2.WidthHeight.Y = object2.Sprite.FrameHeight * (object2.Sprite.Scale)
box2.Rotation = object2.Rotation
box2.RotationPointOffset.X = object2.Rect.RotationPointOffset.X * (object2.Sprite.Scale)
box2.RotationPointOffset.Y = object2.Rect.RotationPointOffset.Y * (object2.Sprite.Scale)
IF GDK_Box_Intersect(box1, box2) THEN GDK_CheckCollision = GDK_TRUE
END FUNCTION
SUB GDK_GetIntersectionAABB (box1 AS GDK_Box, box2 AS GDK_Box, intersection AS GDK_Box)
DIM box1_corners(3) AS GDK_Vector, box2_corners(3) AS GDK_Vector
' Initialize min/max with the first corner of Box1
minX = box1_corners(0).X: maxX = box1_corners(0).X
minY = box1_corners(0).Y: maxY = box1_corners(0).Y
' Expand min/max to include all corners of Box1
FOR i = 1 TO 3
IF box1_corners(i).X < minX THEN minX = box1_corners(i).X
IF box1_corners(i).X > maxX THEN maxX = box1_corners(i).X
IF box1_corners(i).Y < minY THEN minY = box1_corners(i).Y
IF box1_corners(i).Y > maxY THEN maxY = box1_corners(i).Y
NEXT i
' Expand min/max to include all corners of Box2
FOR i = 0 TO 3
IF box2_corners(i).X < minX THEN minX = box2_corners(i).X
IF box2_corners(i).X > maxX THEN maxX = box2_corners(i).X
IF box2_corners(i).Y < minY THEN minY = box2_corners(i).Y
IF box2_corners(i).Y > maxY THEN maxY = box2_corners(i).Y
NEXT i
' The intersection AABB is the combined AABB of both boxes.
intersection.Position.X = minX
intersection.Position.Y = minY
intersection.WidthHeight.X = maxX - minX
intersection.WidthHeight.Y = maxY - minY
END SUB
FUNCTION GetPixelFromImage~& (object AS GDK_Game_Object, x AS INTEGER, y AS INTEGER)
DIM pColor AS _UNSIGNED LONG
DIM frameX AS INTEGER, frameY AS INTEGER
DIM frameRow AS INTEGER, frameCol AS INTEGER
DIM imageWidth AS INTEGER
DIM old_source AS LONG
imageWidth = _WIDTH(object.Sprite.File)
' Adjust the 1-based frame to a 0-based index for calculation
DIM frameIndex AS INTEGER
frameIndex = object.Animation.Frame - 1
' Calculate the offset for the current frame
frameRow = frameIndex \ (imageWidth / object.Sprite.FrameWidth)
frameCol = frameIndex MOD (imageWidth / object.Sprite.FrameWidth)
frameX = frameCol * object.Sprite.FrameWidth
frameY = frameRow * object.Sprite.FrameHeight
' Adjust the local coordinates with the frame offset
x = x + frameX
y = y + frameY
' Save the current source and set the new one
old_source = _SOURCE
_SOURCE object.Sprite.File
' Check if the final coordinates are within the image bounds and get the pixel color
IF x >= 0 AND x < _WIDTH(object.Sprite.File) AND y >= 0 AND y < _HEIGHT(object.Sprite.File) THEN
pColor = POINT(x, y)
ELSE
pColor = object.Sprite.Alpha ' Assuming alpha is the clear color
END IF
FUNCTION GDK_CheckPixelCollision% (object1 AS GDK_Game_Object, object2 AS GDK_Game_Object)
DIM box1 AS GDK_Box, box2 AS GDK_Box
DIM intersection_AABB AS GDK_Box
DIM pixelPosition AS GDK_Vector, local1 AS GDK_Vector, local2 AS GDK_Vector
DIM x AS SINGLE, y AS SINGLE
DIM color1 AS _UNSIGNED LONG, color2 AS _UNSIGNED LONG
' Find the intersecting AABB
GDK_GetIntersectionAABB box1, box2, intersection_AABB
' Iterate over the intersecting area
FOR y = intersection_AABB.Position.Y TO intersection_AABB.Position.Y + intersection_AABB.WidthHeight.Y
FOR x = intersection_AABB.Position.X TO intersection_AABB.Position.X + intersection_AABB.WidthHeight.X
pixelPosition.X = x
pixelPosition.Y = y
' Calculate inverse rotation to map world coords to local sprite coords
CALL GDK_RotatePoint(local1, pixelPosition, object1.Vector, -object1.Rotation)
CALL GDK_RotatePoint(local2, pixelPosition, object2.Vector, -object2.Rotation)
' Get the pixel color from each sprite
color1 = GetPixelFromImage(object1, local1.X, local1.Y)
color2 = GetPixelFromImage(object2, local2.X, local2.Y)
' Check for color-key transparency
IF color1 <> object1.Sprite.Alpha AND color2 <> object2.Sprite.Alpha THEN
' Collision detected at this pixel, return immediately
GDK_CheckPixelCollision = GDK_TRUE
EXIT FUNCTION
END IF
NEXT x
NEXT y
' No collision detected after checking all overlapping pixels
GDK_CheckPixelCollision = GDK_FALSE
END FUNCTION
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'////////////// ANYTHING BELOW HERE IS LIKELY FOR THE GAME IM MAKING TO ASSIST DEV ////////////////////////
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'/// GAME STUFF ///
SUB GDK_GenerateNebula (imageHandle AS LONG)
DIM dest AS LONG, width AS LONG, height AS LONG
DIM x AS INTEGER, y AS INTEGER
DIM noise_scale AS SINGLE, noise_offset AS SINGLE
DIM noise_val AS SINGLE
DIM final_color AS _UNSIGNED LONG
DIM star_prob AS INTEGER
' Improved noise settings
noise_scale = 0.005 ' Adjust for larger or smaller cloud patterns
noise_offset = RND * 1000
' Get the screen dimensions
width = _WIDTH(0)
height = _HEIGHT(0)
' Set the destination to the new image
dest = _DEST
_DEST imageHandle
' Clear the image with a dark space color
PAINT (0, 0), _RGB32(0, 0, 20)
' Fill the image with multi-layered noise colors
FOR y = 0 TO height - 1
FOR x = 0 TO width - 1
' Start with a base noise layer
noise_val = SIN((x + noise_offset) * noise_scale) + COS((y + noise_offset) * noise_scale)
' Add an even higher frequency layer (finer detail)
noise_val = noise_val + SIN((x + noise_offset) * noise_scale * 4) + COS((y + noise_offset) * noise_scale * 4)
' Normalize noise_val to a 0-1 range
noise_val = (noise_val + 3) / 6 ' Range is approx -3 to 3
' Create color based on the normalized noise value
SELECT CASE noise_val
CASE IS < 0.3
final_color = _RGB32(10 + noise_val * 14, 10, 20 + noise_val * 16) ' Deep purple/blue
CASE IS < 0.6
final_color = _RGB32(25 + noise_val * 10, 40 + noise_val * 14, 9 + noise_val * 42) ' Brighter purple/pink
CASE ELSE
final_color = _RGB32(15 + noise_val * 10, 15 + noise_val * 15, 5 + noise_val * 31) ' Orange/red
END SELECT
PSET (x, y), final_color
NEXT x
NEXT y
' Add some stars
FOR x = 1 TO width * height / 500
star_prob = RND * 100
IF star_prob > 95 THEN
PSET (RND * width, RND * height), _RGB32(255, 255, 255)
ELSEIF star_prob > 90 THEN
PSET (RND * width, RND * height), _RGB32(200, 200, 255)
END IF
NEXT x
' Restore the original destination
_DEST dest
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Draw_Repulsor_Wave (Player AS GDK_Game_Object, Radius AS SINGLE)
DIM lColor AS _UNSIGNED LONG
DIM i AS INTEGER
DIM ringAlpha AS SINGLE
DIM maxRings AS INTEGER
DIM ringRadius AS SINGLE
IF Radius < 20 THEN Radius = 20
maxRings = 18 ' Draw 8 concentric rings
FOR i = 0 TO maxRings - 1
ringRadius = Radius - i
ringAlpha = 250 - (i * 15)
lColor = _RGBA32(255, 95, 95, ringAlpha)
CIRCLE (Player.Vector.X, Player.Vector.Y), ringRadius, lColor
NEXT
END SUB
SUB Draw_Shield (Player AS GDK_Game_Object, Hit%)
DIM maxHealth AS INTEGER: maxHealth = 10 ' Maximum health value - when the shield fails
DIM alphaRatio AS SINGLE
DIM alphaValue AS INTEGER
IF PHealth > 2 THEN '// shield and repulsor not available if hit 8+ times
' Check if we are in the charging phase
IF (transitionPhase = 4 AND _KEYDOWN(100304)) OR (transitionPhase = 6 AND _KEYDOWN(100304)) THEN
' Calculate brightness based on charge time
Brightness = (gt# - repelChargeTimer) / REPEL_CHARGE_TIME * 255
IF Brightness > 255 THEN Brightness = 255
ELSE
Brightness = 0 ' No extra brightness when not charging
END IF
alphaRatio = PHealth / maxHealth
FOR j% = 0 TO 15 + (Brightness / 20)
alphaValue = (j% * 5) * alphaRatio ' Modify alpha and color based on brightness
IF NOT Hit% THEN CLR~& = _RGBA32(Brightness / 10, Brightness / 10, 255, alphaValue) ELSE CLR~& = _RGBA32(215, 0, 0, (j% * 15))
Radius! = (Player.Sprite.FrameWidth / 2.2) + (1 * j%)
MinStep! = 1 / ((_PI * 2) * Radius!) ' Optimization from CodeGuy
FOR i = 0 TO (_PI * 2) STEP MinStep!
PSET (Player.Vector.X + (Radius! * COS(i)), Player.Vector.Y + (Radius! * SIN(i))), CLR~&
NEXT
NEXT
END IF
Hit% = GDK_FALSE
END SUB
SUB BossModeState (Enemy() AS GDK_Game_Object, Ship AS GDK_Game_Object)
STATIC Repelling
'// Player movement based on rotation
IF _KEYDOWN(19200) THEN Ship.Rotation = Ship.Rotation - .15
IF _KEYDOWN(19712) THEN Ship.Rotation = Ship.Rotation + .15
IF _KEYDOWN(18432) AND Ship.Speed < 10 THEN Ship.Speed = Ship.Speed + .15
IF _KEYDOWN(20480) AND Ship.Speed > 0 THEN Ship.Speed = Ship.Speed - .15
'// Bullets movement and collisions checks - destroy bullets when they leave the screen
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN
GDK_Vector_Update Bullet(i%).Vector, Bullet(i%).Rotation, Bullet(i%).Speed
IF Bullet(i%).Vector.Y < 0 OR Bullet(i%).Vector.Y > 600 OR Bullet(i%).Vector.X < 0 OR Bullet(i%).Vector.X > 800 THEN
Bullet(i%).Exists = GDK_FALSE '// off screen so destroy
ELSE '// check for enemy collisions
FOR j% = 0 TO MAX_ENEMIES - 1
IF Enemy(j%).Exists = GDK_TRUE THEN
Enemy(j%).Sprite.Scale = .85 '//collision box scaling
IF GDK_CheckCollision(Bullet(i%), Enemy(j%)) THEN '// Bullet has hit an enemy bounding box
'IF GDK_CheckPixelCollision(Bullet(i%), Enemy(j%)) THEN '// Pixel perfect check - Faulty
Bullet(i%).Exists = GDK_FALSE
Enemy(j%).Exists = GDK_FALSE
EXIT FOR
'END IF
END IF
Enemy(j%).Sprite.Scale = 1 '// restore original scale
END IF
NEXT
END IF
END IF
NEXT
'// enemy shooting
IF gt# - eShoot_Timer >= eShoot_Time THEN
FOR i% = MAX_ENEMIES - 1 TO 0 STEP -1
IF NOT Enemy_Bullet.Exists THEN '// bulet is dstroyed
nme = RND * (MAX_ENEMIES - 1) '// Randomly choose a enemy to fire at us
IF Enemy(nme).Exists THEN
Enemy_Bullet.Exists = GDK_TRUE
GDK_Vector_New Enemy_Bullet.Vector, Enemy(nme).Vector.X, Enemy(nme).Vector.Y
Enemy_Bullet.Speed = 15
Enemy_Bullet.Rotation = _ATAN2(Ship.Vector.Y - Enemy(nme).Vector.Y, Ship.Vector.X - Enemy(nme).Vector.X)
EXIT FOR
END IF
END IF
NEXT
eShoot_Timer = gt#
END IF
IF Enemy_Bullet.Exists THEN
GDK_Vector_Update Enemy_Bullet.Vector, Enemy_Bullet.Rotation, Enemy_Bullet.Speed
IF Enemy_Bullet.Vector.Y < 0 OR Enemy_Bullet.Vector.Y > 600 OR Enemy_Bullet.Vector.X < 0 OR Enemy_Bullet.Vector.X > 800 THEN
Enemy_Bullet.Exists = GDK_FALSE
ELSE '// check player collision
IF GDK_CheckShieldCollision(Enemy_Bullet, Ship) THEN
IF PHealth <= 2 THEN '// no more shield so direct hit needed
IF GDK_CheckCollision(Enemy_Bullet, Ship) THEN '// player hit
Enemy_Bullet.Exists = GDK_FALSE
IF PHealth > 0 THEN
Hit% = GDK_TRUE
PHealth = PHealth - 1
ELSE '// GAme over
CLS
PRINT "You got blown up! But thanks for playing"
_DISPLAY
_DELAY 5
SYSTEM
END IF
END IF
ELSE
Hit% = GDK_TRUE
PHealth = PHealth - 1
Enemy_Bullet.Exists = GDK_FALSE
END IF
ELSE
Hit% = GDK_FALSE
END IF
END IF
END IF
'// INPUT
'// Space bar shoots
IF _KEYDOWN(32) THEN
IF gt# - Shoot_Timer >= Shoot_Time THEN
FOR i% = 0 TO Max_Bullets& - 1
IF NOT Bullet(i%).Exists THEN
Bullet(i%).Exists = GDK_TRUE
GDK_Vector_New Bullet(i%).Vector, Ship.Vector.X, Ship.Vector.Y
Bullet(i%).Speed = 15
Bullet(i%).Rotation = Ship.Rotation
EXIT FOR
END IF
NEXT
Shoot_Timer = gt#
END IF
END IF
IF NOT Repelling THEN
IF PHealth > 2 THEN
IF _KEYDOWN(100304) AND repelChargeTimer = 0 THEN repelChargeTimer = gt# ' Start timer ' Check for shift key input to start charging
IF repelChargeTimer > 0 AND gt# - repelChargeTimer >= REPEL_CHARGE_TIME THEN Repelling = GDK_TRUE ' Check for full charge
END IF
ELSE
Draw_Repulsor_Wave Ship, RepulsorRadius
RepulsorRadius = RepulsorRadius + 4 ' Expand the wave
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
' Check if enemy is within the repulsor radius
dx = Ship.Vector.X - Enemy(i).Vector.X
dy = Ship.Vector.Y - Enemy(i).Vector.Y
distance = SQR(dx * dx + dy * dy)
IF distance <= RepulsorRadius THEN Enemy(i).Exists = GDK_FALSE ' Destroy enemy
END IF
NEXT i
' Check if wave has expanded far enough
IF RepulsorRadius > _WIDTH / 4 * 1.5 THEN
RepulsorRadius = 0 ' Reset for next time
Repelling = GDK_FALSE
repelChargeTimer = 0 ' Reset timer
END IF
END IF
'// Bullets
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN GDK_GameObject_Draw Bullet(i%), Bullet(i%).Animation.Frame
NEXT
IF Enemy_Bullet.Exists THEN GDK_GameObject_Draw Enemy_Bullet, Enemy_Bullet.Animation.Frame
FUNCTION NumActiveEnemies (Enemy() AS GDK_Game_Object)
DIM count AS INTEGER, i AS INTEGER
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN count = count + 1
NEXT i
NumActiveEnemies = count
END FUNCTION
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// This is basiclly a list of scripted actions that the player and enemies undertake
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB TransitionState (Enemy() AS GDK_Game_Object, Ship AS GDK_Game_Object)
STATIC playerTargetX AS SINGLE, playerTargetY AS SINGLE
STATIC enemyTargetX(MAX_ENEMIES - 1) AS SINGLE, enemyTargetY(MAX_ENEMIES - 1) AS SINGLE
DIM i AS INTEGER, j AS INTEGER
DIM angle AS SINGLE, screenCenterX AS SINGLE, screenCenterY AS SINGLE
DIM active_enemy_count AS INTEGER
DIM move_speed AS SINGLE
DIM enemies_in_line AS _BYTE
DIM player_at_center AS _BYTE
DIM enemies_in_formation AS _BYTE
DIM enemy_line_x AS SINGLE
screenCenterX = _WIDTH / 2
screenCenterY = _HEIGHT / 2
move_speed = 5 ' Speed for player and enemy movement
SELECT CASE transitionPhase '///////////////////////////////////////////////////////////////////////////
CASE 0 ' Begin transition - pause, clear bullets, set target positions '///////////////////////////////////////////////////////////////////////////
playerTargetX = screenCenterX
playerTargetY = screenCenterY
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN Enemy(i).Speed = 0 ' Pause enemies
NEXT i
FOR i = 0 TO Max_Bullets - 1
Bullet(i).Exists = GDK_FALSE
NEXT i
Enemy_Bullet.Exists = GDK_FALSE
transitionPhase = 1
CASE 1 ' Move player and enemies towards line simultaneously '///////////////////////////////////////////////////////////////////////////
player_at_center = move_to_position(Ship, playerTargetX, playerTargetY, move_speed)
enemies_in_line = GDK_TRUE
enemy_line_x = screenCenterX - (NumActiveEnemies(Enemy()) / 2 * 60) ' Start position for line
j = 0
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
enemies_in_line = enemies_in_line AND move_to_position(Enemy(i), enemy_line_x + j * 60, 50, move_speed)
j = j + 1
END IF
NEXT i
IF enemies_in_line = GDK_TRUE AND player_at_center = GDK_TRUE THEN
transitionPhase = 2 ' Proceed to store circular positions
END IF
CASE 2 ' Calculate and store final circular positions '///////////////////////////////////////////////////////////////////////////
active_enemy_count = NumActiveEnemies(Enemy())
j = 0
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
angle = (j * 2 * 3.14159 / active_enemy_count)
enemyTargetX(i) = screenCenterX + COS(angle) * 220
enemyTargetY(i) = screenCenterY + SIN(angle) * 220
Enemy(i).Rotation = _ATAN2(screenCenterY - Enemy(i).Vector.Y, screenCenterX - Enemy(i).Vector.X) - _PI
j = j + 1
END IF
NEXT i
transitionPhase = 3 ' Proceed to move enemies to final positions
CASE 3 ' Move enemies to final circular positions '///////////////////////////////////////////////////////////////////////////
enemies_in_formation = GDK_TRUE
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
enemies_in_formation = enemies_in_formation AND move_to_position(Enemy(i), enemyTargetX(i), enemyTargetY(i), move_speed)
END IF
NEXT i
IF enemies_in_formation = GDK_TRUE THEN
transitionPhase = 4 ' All enemies in position, finalize transition
END IF
' Draw instructions on screen
COLOR _RGB32(100, 100, 255), _RGBA32(0, 0, 0, 0): _PRINTSTRING (280, 120), "HOLD L Shift TO CHARGE REPULSOR WAVE!"
' Draw tractor beams from all active enemies to the player
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN DrawTractorBeam Enemy(i), Ship
NEXT i
' Check for shift key input to start charging
IF _KEYDOWN(100304) AND repelChargeTimer = 0 THEN repelChargeTimer = gt# ' Start timer
' Check for full charge
IF repelChargeTimer > 0 AND gt# - repelChargeTimer >= REPEL_CHARGE_TIME THEN
repelChargeTimer = 0 ' Reset timer
transitionPhase = 5 ' Proceed to repulsor wave
END IF
CASE 5 'Repulsor wave phase '///////////////////////////////////////////////////////////////////////////
Draw_Repulsor_Wave Ship, RepulsorRadius
RepulsorRadius = RepulsorRadius + 4 ' Expand the wave
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemy(i).Exists THEN
' Check if enemy is within the repulsor radius
dx = Ship.Vector.X - Enemy(i).Vector.X
dy = Ship.Vector.Y - Enemy(i).Vector.Y
distance = SQR(dx * dx + dy * dy)
IF distance <= RepulsorRadius THEN
Enemy(i).Exists = GDK_FALSE ' Destroy enemy
END IF
END IF
NEXT i
' Check if wave has expanded far enough (e.g., off-screen)
IF RepulsorRadius > _WIDTH / 4 * 1.5 THEN
transitionPhase = 6 ' Proceed to finalize
RepulsorRadius = 0 ' Reset for next time
END IF
FUNCTION move_to_position%% (object AS GDK_Game_Object, targetX AS SINGLE, targetY AS SINGLE, speed AS SINGLE)
DIM at_target AS _BYTE
DIM x_moved AS _BYTE, y_moved AS _BYTE
at_target = GDK_TRUE
' Calculate the vector to the target
dx = targetX - object.Vector.X
dy = targetY - object.Vector.Y
' Check if we are close enough to the target
IF ABS(dx) < speed AND ABS(dy) < speed THEN
object.Vector.X = targetX ' Snap to position
object.Vector.Y = targetY
move_to_position = GDK_TRUE
EXIT FUNCTION
END IF
' Move towards the target without overshooting
move_x = dx / SQR(dx * dx + dy * dy) * speed
move_y = dy / SQR(dx * dx + dy * dy) * speed
'// Bullets movement and collisions checks - destroy bullets when they leave the screen
FOR i% = 0 TO Max_Bullets& - 1
IF Bullet(i%).Exists THEN
GDK_Vector_Update Bullet(i%).Vector, Bullet(i%).Rotation, Bullet(i%).Speed
IF Bullet(i%).Vector.Y < 0 THEN
Bullet(i%).Exists = GDK_FALSE '// off screen so destroy
ELSE '// check for enemy collisions
FOR j% = 0 TO MAX_ENEMIES - 1
IF Enemy(j%).Exists = GDK_TRUE THEN
Enemy(j%).Sprite.Scale = .75 '//collision box scaling
IF GDK_CheckCollision(Bullet(i%), Enemy(j%)) THEN '// Bullet has hit an enemy bounding box
'IF GDK_CheckPixelCollision(Bullet(i%), Enemy(j%)) THEN '// Pixel perfect check - Faulty
Bullet(i%).Exists = GDK_FALSE
Enemy(j%).Exists = GDK_FALSE
EXIT FOR
'END IF
END IF
Enemy(j%).Sprite.Scale = 1 '// restore original scale
END IF
NEXT
END IF
END IF
NEXT
'// Enemy movement
IF Enemy_Left = GDK_TRUE THEN
IF Enemy_Move > 0 THEN Enemy_Move = Enemy_Move - 1 ELSE Enemy_Left = GDK_FALSE
ELSE
IF Enemy_Move < 120 THEN Enemy_Move = Enemy_Move + 1 ELSE Enemy_Left = GDK_TRUE
END IF
FOR i% = 0 TO MAX_ENEMIES - 1
GDK_ANIMATION_UPDATE Enemy(i%).Animation '// Enemy animation update
IF Enemy_Left = GDK_TRUE THEN Enemy(i%).Vector.X = Enemy(i%).Vector.X - 1 ELSE Enemy(i%).Vector.X = Enemy(i%).Vector.X + 1
NEXT
'// enemy shooting
IF gt# - eShoot_Timer >= eShoot_Time THEN
FOR i% = MAX_ENEMIES - 1 TO 0 STEP -1
IF NOT Enemy_Bullet.Exists THEN '// bulet is dstroyed
nme = RND * 39 '// Randomly choose a enemy to fire at us
IF Enemy(nme).Exists THEN
Enemy_Bullet.Exists = GDK_TRUE
GDK_Vector_New Enemy_Bullet.Vector, Enemy(nme).Vector.X, Enemy(nme).Vector.Y
Enemy_Bullet.Speed = 10
Enemy_Bullet.Rotation = _ATAN2(Ship.Vector.Y - Enemy(nme).Vector.Y, Ship.Vector.X - Enemy(nme).Vector.X)
EXIT FOR
END IF
END IF
NEXT
eShoot_Timer = gt#
END IF
IF Enemy_Bullet.Exists THEN
GDK_Vector_Update Enemy_Bullet.Vector, Enemy_Bullet.Rotation, Enemy_Bullet.Speed
IF Enemy_Bullet.Vector.Y > 600 THEN
Enemy_Bullet.Exists = GDK_FALSE
ELSE '// check player collision
IF GDK_CheckCollision(Enemy_Bullet, Ship) THEN '// player hit
Enemy_Bullet.Exists = GDK_FALSE
IF PHealth > 0 THEN
Hit% = GDK_TRUE
PHealth = PHealth - 1
ELSE '// GAme over
CLS
PRINT "You got blown the F up! But thanks for playing"
_DISPLAY
_DELAY 5
SYSTEM
END IF
ELSE
Hit% = GDK_FALSE
END IF
END IF
END IF
'// INPUT
'// Space bar shoots
IF _KEYDOWN(32) THEN
IF gt# - Shoot_Timer >= Shoot_Time THEN
FOR i% = 0 TO Max_Bullets& - 1
IF NOT Bullet(i%).Exists THEN
Bullet(i%).Exists = GDK_TRUE
GDK_Vector_New Bullet(i%).Vector, Ship.Vector.X, Ship.Vector.Y
Bullet(i%).Speed = 5
Bullet(i%).Rotation = 6 * ATN(1)
EXIT FOR
END IF
NEXT
Shoot_Timer = gt#
END IF
END IF
'// Ship left and right movement
IF _KEYDOWN(19200) THEN '// left
IF Ship.Vector.X > Player_MinX THEN Ship.Vector.X = Ship.Vector.X - 3
ELSEIF _KEYDOWN(19712) THEN '// right
IF Ship.Vector.X < Player_MaxX THEN Ship.Vector.X = Ship.Vector.X + 3
END IF
FUNCTION GDK_CheckShieldCollision (Player AS GDK_Game_Object, EnemyBullet AS GDK_Game_Object)
DIM playerShield AS GDK_BoundingCircle
DIM enemyBulletCircle AS GDK_BoundingCircle
DIM dx AS SINGLE, dy AS SINGLE
DIM distanceSquared AS SINGLE
DIM radiiSum AS INTEGER
' Create the bounding circle for the player's shield
playerShield.X = Player.Vector.X
playerShield.Y = Player.Vector.Y
playerShield.Radius = (Player.Sprite.FrameWidth / 2.4) + 35 ' Using a larger radius for the shield
' Create the bounding circle for the enemy bullet
enemyBulletCircle.X = EnemyBullet.Vector.X
enemyBulletCircle.Y = EnemyBullet.Vector.Y
enemyBulletCircle.Radius = EnemyBullet.Sprite.FrameWidth / 2 ' Assuming bullet is a circle
' Calculate the squared distance between the centers
dx = playerShield.X - enemyBulletCircle.X
dy = playerShield.Y - enemyBulletCircle.Y
distanceSquared = (dx * dx) + (dy * dy)
' Calculate the sum of the radii
radiiSum = playerShield.Radius + enemyBulletCircle.Radius
' Compare the squared distance with the squared sum of the radii
' Using squared values avoids the slower SQR function
IF distanceSquared <= (radiiSum * radiiSum) THEN
GDK_CheckShieldCollision = GDK_TRUE
ELSE
GDK_CheckShieldCollision = GDK_FALSE
END IF
END FUNCTION
SUB DrawTractorBeam (Enemy AS GDK_Game_Object, Player AS GDK_Game_Object)
DIM lColor AS _UNSIGNED LONG
' Draw multiple lines to create a "scanning" effect
FOR i = 0 TO 5
' Add a small random offset to the line's endpoint
randomOffset = (RND * -10) + 20 ' Adjust 20 for more or less random wobble
lineX = Player.Vector.X + randomOffset
lineY = Player.Vector.Y + randomOffset
lColor = _RGBA32(RND * 100, RND * 100, 255, (RND * 200) + 55) ' Semi-transparent blue/white beam
' Draw the line from the enemy to the player's position
LINE (Enemy.Vector.X, Enemy.Vector.Y)-(lineX, lineY), lColor
NEXT i
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
SUB Populate_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER, row_index AS INTEGER
y_spacing = (SCREEN_WIDTH) / ENEMIES_PER_ROW
FOR i = 0 TO MAX_ENEMIES - 1
Enemies(i).Exists = GDK_TRUE
Enemies(i).Vector.Y = -SPAWN_OFFSET_Y
row_index = i \ ENEMIES_PER_ROW
start_x = 100 + (row_index * y_spacing)
Enemies(i).Vector.X = start_x
Enemies(i).X_Initial = Enemies(i).Vector.X
Enemies(i).Speed = (i / ENEMIES_PER_ROW) * SPAWN_DELAY
NEXT i
END SUB
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
' -----------------------------------------------------------------------------
' Updates the enemies' positions for the sine wave and respawns them.
' -----------------------------------------------------------------------------
SUB Update_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
' Advance the global time counter
GameTime = GameTime + 1
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN
' Store the current position as the last position for the next frame
Enemies(i).Last_Vector.X = Enemies(i).Vector.X
Enemies(i).Last_Vector.Y = Enemies(i).Vector.Y
GDK_ANIMATION_UPDATE Enemies(i).Animation
IF GameTime > Enemies(i).Speed THEN
' Move the enemy down the screen
Enemies(i).Vector.Y = Enemies(i).Vector.Y + VERTICAL_SPEED
' Apply sine wave to the X position
sine_y_offset = (Enemies(i).Vector.Y / SCREEN_HEIGHT) * (2 * _PI)
Enemies(i).Vector.X = Enemies(i).X_Initial + SINE_AMPLITUDE * SIN(sine_y_offset * SINE_FREQUENCY)
' Calculate the change in position (velocity vector)
dx = Enemies(i).Vector.X - Enemies(i).Last_Vector.X
dy = Enemies(i).Vector.Y - Enemies(i).Last_Vector.Y
' Use ATAN2 to set the rotation
' Note: ATAN2 returns radians, GDK uses degrees. Multiply by 180 / PI to convert
Enemies(i).Rotation = _ATAN2(dy, dx) - (_PI / 2)
' Check if enemy is off-screen at the bottom and respawn
IF Enemies(i).Vector.Y > SCREEN_HEIGHT + SPAWN_OFFSET_Y THEN
Enemies(i).Vector.Y = -SPAWN_OFFSET_Y
Enemies(i).Speed = GameTime + SPAWN_DELAY
' Reset last position for the new spawn
Enemies(i).Last_Vector.X = Enemies(i).Vector.X
Enemies(i).Last_Vector.Y = Enemies(i).Vector.Y
END IF
END IF
END IF
NEXT i
END SUB
SUB Render_Enemies_2 (Enemies() AS GDK_Game_Object)
DIM i AS INTEGER
FOR i = 0 TO MAX_ENEMIES - 1
IF Enemies(i).Exists THEN GDK_GameObject_Draw Enemies(i), Enemies(i).Animation.Frame
NEXT
END SUB