Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Space Invaders knock off to show simple collisions
#1
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 \\
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

CONST GDK_Vector_Size = 8, GDK_TRUE = -1, GDK_FALSE = 0

'///////////////////////////////////// System Initialisation ////////////////////////////////////////////////
CHDIR "Tutorials\" '// The root path for the files we will use

SCREEN _NEWIMAGE(800, 600, 32)
_SCREENMOVE 0, 0
_DELAY 1


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// 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

PRINT "Unseen machines little collisions demo..."
PRINT "Space = Shoot, Left/Right arrows move ship."
_DISPLAY
_DELAY 2

'//////////////////////////////////////// Main Loop ////////////////////////////////////////////////////////////

DO

  CLS

  _LIMIT 60

  '// Logic
  GDK_ANIMATION_UPDATE EnemyAnim '// Enemy animation update


  '// 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

  HalfW = Box.WidthHeight.X / 2
  HalfH = Box.WidthHeight.Y / 2
  Center = Box.Position

  UnrotatedCorner.X = Center.X - HalfW
  UnrotatedCorner.Y = Center.Y - HalfH
  CALL GDK_RotatePoint(Corners(0), UnrotatedCorner, Center, Box.Rotation)

  UnrotatedCorner.X = Center.X + HalfW
  UnrotatedCorner.Y = Center.Y - HalfH
  CALL GDK_RotatePoint(Corners(1), UnrotatedCorner, Center, Box.Rotation)

  UnrotatedCorner.X = Center.X + HalfW
  UnrotatedCorner.Y = Center.Y + HalfH
  CALL GDK_RotatePoint(Corners(2), UnrotatedCorner, Center, Box.Rotation)

  UnrotatedCorner.X = Center.X - HalfW
  UnrotatedCorner.Y = Center.Y + HalfH
  CALL GDK_RotatePoint(Corners(3), UnrotatedCorner, Center, Box.Rotation)
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  Min1 = 9999999!: Max1 = -9999999!
  Min2 = 9999999!: Max2 = -9999999!

  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

  CALL GDK_GetBoxCorners(Box1, Box1_Corners())
  CALL GDK_GetBoxCorners(Box2, Box2_Corners())

  Axis(0).X = -(Box1_Corners(1).Y - Box1_Corners(0).Y): Axis(0).Y = Box1_Corners(1).X - Box1_Corners(0).X
  Axis(1).X = -(Box1_Corners(0).Y - Box1_Corners(3).Y): Axis(1).Y = Box1_Corners(0).X - Box1_Corners(3).X

  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

  Axis(2).X = -(Box2_Corners(1).Y - Box2_Corners(0).Y): Axis(2).Y = Box2_Corners(1).X - Box2_Corners(0).X
  Axis(3).X = -(Box2_Corners(0).Y - Box2_Corners(3).Y): Axis(3).Y = Box2_Corners(0).X - Box2_Corners(3).X

  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

  '// Temp to draw collision boxes
  GDK_DrawBox Box1, _RGB32(255, 0, 0)
  GDK_DrawBox Box2, _RGB32(255, 0, 0)


  GDK_Box_Intersect = -1
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple sprite stuff - For universal spaced sprites sheets
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_New (Sprite AS GDK_Sprite, FileSource$, XFrameCount%, YFrameCount%, TotalFrameCount%, Alpha&)
  Sprite.File = _LOADIMAGE(FileSource$, 32)
  Sprite.Width = _WIDTH(Sprite.File)
  Sprite.Height = _HEIGHT(Sprite.File)
  Sprite.XFrameCount = XFrameCount%
  Sprite.YFrameCount = YFrameCount%
  Sprite.TotalFrameCount = TotalFrameCount%
  Sprite.Scale = 1
  Sprite.IsVisible = -1
  Sprite.Alpha = Alpha&
  Sprite.FrameWidth = Sprite.Width \ Sprite.XFrameCount
  Sprite.FrameHeight = Sprite.Height \ Sprite.YFrameCount
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetRotationPoint (Sprite AS GDK_Sprite, X!, Y!)
  Sprite.RotationX = X!
  Sprite.RotationY = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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_Sprite_SetVisibility (Sprite AS GDK_Sprite, OnOff AS _BYTE)
  Sprite.IsVisible = OnOff
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetAlpha (Sprite AS GDK_Sprite, Alpha&)
  Sprite.Alpha = Alpha&
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_GameObject_New (Handle AS GDK_Game_Object, File$, XframeCount%, YFrameCount%, X%, Y%, Speed!, Rotation!)
  GDK_Sprite_New Handle.Sprite, File$, XframeCount%, YFrameCount%, XframeCount% * YFrameCount%, _RGB32(255, 0, 255)
  GDK_Vector_New Handle.Vector, X%, Y%
  Handle.Rotation = Rotation!
  Handle.Speed = Speed
  GDK_Sprite_SetVisibility Handle.Sprite, -1
  GDK_Sprite_SetAlpha Handle.Sprite, _RGB32(255, 0, 255)
  Handle.Exists = GDK_TRUE
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_GameObject_Update (Handle AS GDK_Game_Object)
  GDK_Vector_Update Handle.Vector, Handle.Rotation, Handle.Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_New (Vector AS GDK_Vector, X!, Y!)
  Vector.X = X!
  Vector.Y = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_Update (Vector AS GDK_Vector, Rot, Speed)
  Vector.X = Vector.X + COS(Rot) * Speed
  Vector.Y = Vector.Y + SIN(Rot) * Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple math functions

FUNCTION GDK_Distance! (X%, Y%, XX%, YY%)
  xd% = XX% - X%
  yd% = YY% - Y%
  GDK_Distance! = SQR((xd% * xd%) + (yd% * yd%))
END FUNCTION

FUNCTION GDK_RadianToDegree! (Radians!)
  GDK_RadianToDegree! = Radians! * (180 / (4 * ATN(1)))
END FUNCTION

FUNCTION GDK_DegreeToRadian! (Degrees!)
  GDK_DegreeToRadian! = Degrees! * ((4 * ATN(1)) / 180)
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_ANIMATION_NEW (Anim AS GDK_Animation, Frame%, StartFrame%, ResetFrame%, AnimTime#, AnimTimer#)
  Anim.Frame = Frame%
  Anim.StartFrame = StartFrame%
  Anim.ResetFrame = ResetFrame%
  Anim.Time = AnimTime#
  Anim.Timer = AnimTimer#
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

  CALL GDK_GetBoxCorners(box1, box1_corners())
  CALL GDK_GetBoxCorners(box2, box2_corners())

  ' 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

  ' Calculate the intersection AABB
  intersection.Position.X = _MAX(box1_minX, box2_minX)
  intersection.Position.Y = _MAX(box1_minY, box2_minY)
  intersection.WidthHeight.X = _MIN(box1_maxX, box2_maxX) - intersection.Position.X
  intersection.WidthHeight.Y = _MIN(box1_maxY, box2_maxY) - intersection.Position.Y

  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



'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

Unseen


Attached Files
.7z   Tutorials.7z (Size: 2.98 KB / Downloads: 44)
Reply
#2
It's really not much of an accomplishment, so nothing for that... but then again I LOVE GAMES I CAN'T LOSE! So +2!

Pete Big Grin
Reply
#3
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
Reply
#4
If Pete gives you code, give it back -- quickly!!  You don't want it!
Reply
#5
(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!!!

AND Pete wrote it! Smile It's really quite good!

Never say never!


Attached Files
.zip   Pete One Key Halloween.zip (Size: 2.79 MB / Downloads: 43)
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#6
@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! Smile 

Now my enemies shoot back, shield is implemented and ill repost before bed....blame Pete!

John
Reply
#7
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

CONST GDK_Vector_Size = 8, GDK_TRUE = -1, GDK_FALSE = 0

'///////////////////////////////////// Game States ////////////////////////////////////////////////
CONST GDK_STATE_PLAYING = 0
CONST GDK_STATE_TRANSITION = 1
CONST GDK_STATE_BOSS_MODE = 2
CONST REPEL_CHARGE_TIME = 5.0
' Global constants for the swarm behavior
CONST MAX_ENEMIES = 50
CONST NUM_GROUPS = 4
CONST ENEMIES_PER_GROUP = 10
CONST HELIX_RADIUS = 20.0
CONST HELIX_SPEED = 0.05
CONST Z_DISTANCE_SCALE_MODIFIER = 0.05
CONST SWARM_CENTER_X = 400
CONST SWARM_CENTER_Y = 300
CONST SWARM_TRAVEL_SPEED = 2.0 ' Pixels per frame to move across the screen

CONST SWARM_START_X = -50
CONST ENEMIES_PER_ROW = 5
CONST SCREEN_WIDTH = 800
CONST SCREEN_HEIGHT = 600

' 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

SCREEN _NEWIMAGE(800, 600, 32)
_SCREENMOVE 0, 0
_DELAY 1

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

ex% = 60: ey% = 40
GDK_GameObject_New Enemy(0), "Invader.png", 4, 1, ex%, ey%, 0, 0
GDK_Sprite_SetRotationAsCenter Enemy(0).Sprite
GDK_ANIMATION_NEW Enemy(0).Animation, 1, 1, 4, .15, 0
BossEnemy(0) = Enemy(0)
ex% = 120
FOR i% = 1 TO MAX_ENEMIES - 1
  Enemy(i%) = Enemy(0)
  BossEnemy(i%) = Enemy(0)

  Enemy(i%).Vector.X = ex%
  Enemy(i%).Vector.Y = ey%
  IF (i% + 1) MOD 10 = 0 THEN
    ey% = ey% + 90
    ex% = 60
  ELSE
    ex% = ex% + 60
  END IF
NEXT

GDK_GameObject_New Bullet(0), "Bullet_Blue.png", 1, 1, 0, 0, 0, 0
GDK_Sprite_SetRotationAsCenter Bullet(0).Sprite
Bullet(0).Exists = GDK_FALSE '// Reset default auto existance
Enemy_Bullet = Bullet(0)
FOR i% = 1 TO Max_Bullets& - 1
  Bullet(i%) = Bullet(0)
NEXT


WallPaper = _NEWIMAGE(800, 600, 32): GDK_GenerateNebula WallPaper '// generates a random background stylised to look like a nebula (its half crap)



_PRINTSTRING (325, 260), "Space Quest"
_PRINTSTRING (170, 350), "Space = Shoot, Left/Right arrows move, Hold LShift for Repulsor."
_DISPLAY
_DELAY 3

GameState = GDK_STATE_PLAYING
TransitionStarted = GDK_FALSE
LastGT# = TIMER(.001)

'//////////////////////////////////////// Main Loop ////////////////////////////////////////////////////////////

DO

  gt# = TIMER(.001) '// global timer
  DT# = gt# - LastGT# '// Delta time

  CLS
  _LIMIT 30

  _PUTIMAGE , WallPaper


  SELECT CASE MainMode%
    CASE 0 '// Space invaders

      SELECT CASE GameState
        CASE GDK_STATE_PLAYING
          Shoot_Time = .6 '//
          Player_MinX = 40 '//  axis limits
          Player_MaxX = 760
          eShoot_Time = 3

          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%

      _DISPLAY

    CASE 1
      BossModeState BossEnemy(), Ship
      Update_Enemies_2 BossEnemy()
      Render_Enemies_2 BossEnemy()
      GDK_GameObject_Draw Ship, Ship.Animation.Frame
      Draw_Shield Ship, Hit%

      _DISPLAY


  END SELECT

  LastGT# = gt#

LOOP UNTIL INKEY$ = CHR$(27)

SYSTEM


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

TYPE GDK_Vector
  X AS SINGLE
  Y AS SINGLE
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_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

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_GetBoxCorners (Box AS GDK_Box, Corners() AS GDK_Vector)
  DIM HalfW AS SINGLE, HalfH AS SINGLE
  DIM Pivot AS GDK_Vector
  DIM corner AS GDK_Vector

  HalfW = Box.WidthHeight.X / 2
  HalfH = Box.WidthHeight.Y / 2

  ' Calculate the absolute position of the rotation pivot
  Pivot.X = Box.Position.X + Box.RotationPointOffset.X
  Pivot.Y = Box.Position.Y + Box.RotationPointOffset.Y

  ' Corner 0: top-left relative to the pivot
  corner.X = Pivot.X - HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y - HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(0), corner, Pivot, Box.Rotation)

  ' Corner 1: top-right relative to the pivot
  corner.X = Pivot.X + HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y - HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(1), corner, Pivot, Box.Rotation)

  ' Corner 2: bottom-right relative to the pivot
  corner.X = Pivot.X + HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y + HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(2), corner, Pivot, Box.Rotation)

  ' Corner 3: bottom-left relative to the pivot
  corner.X = Pivot.X - HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y + HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(3), corner, Pivot, Box.Rotation)
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  Min1 = 9999999!: Max1 = -9999999!
  Min2 = 9999999!: Max2 = -9999999!

  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

  CALL GDK_GetBoxCorners(Box1, Box1_Corners())
  CALL GDK_GetBoxCorners(Box2, Box2_Corners())

  GDK_DrawBox Box1, _RGB32(255, 0, 0) '// render collision boxes - temp
  GDK_DrawBox Box2, _RGB32(255, 0, 0) '// render collision boxes - temp

  Axis(0).X = -(Box1_Corners(1).Y - Box1_Corners(0).Y): Axis(0).Y = Box1_Corners(1).X - Box1_Corners(0).X
  Axis(1).X = -(Box1_Corners(0).Y - Box1_Corners(3).Y): Axis(1).Y = Box1_Corners(0).X - Box1_Corners(3).X

  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

  Axis(2).X = -(Box2_Corners(1).Y - Box2_Corners(0).Y): Axis(2).Y = Box2_Corners(1).X - Box2_Corners(0).X
  Axis(3).X = -(Box2_Corners(0).Y - Box2_Corners(3).Y): Axis(3).Y = Box2_Corners(0).X - Box2_Corners(3).X

  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


  GDK_Box_Intersect = -1
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple sprite stuff - For universal spaced sprites sheets
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_New (Sprite AS GDK_Sprite, FileSource$, XFrameCount%, YFrameCount%, TotalFrameCount%, Alpha&)
  Sprite.File = _LOADIMAGE(FileSource$, 32)
  Sprite.Width = _WIDTH(Sprite.File)
  Sprite.Height = _HEIGHT(Sprite.File)
  Sprite.XFrameCount = XFrameCount%
  Sprite.YFrameCount = YFrameCount%
  Sprite.TotalFrameCount = TotalFrameCount%
  Sprite.Scale = 1
  Sprite.IsVisible = -1
  Sprite.Alpha = Alpha&
  Sprite.FrameWidth = Sprite.Width \ Sprite.XFrameCount
  Sprite.FrameHeight = Sprite.Height \ Sprite.YFrameCount
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetRotationPoint (Sprite AS GDK_Sprite, X!, Y!)
  Sprite.RotationX = X!
  Sprite.RotationY = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

    ' Rotate
    X2 = pX(I) * COSr - pY(I) * SINr
    Y2 = pY(I) * COSr + pX(I) * SINr
    pX(I) = X2
    pY(I) = Y2

    ' Translate to screen position
    pX(I) = pX(I) + X: pY(I) = pY(I) + Y
  NEXT

  ' Map the source rectangle to the rotated and scaled destination
  _MAPTRIANGLE (FrameXStart, FrameYStart)-(FrameXStart, FrameYStart + H)-(FrameXStart + W, FrameYStart + H), Image TO(pX(0), pY(0))-(pX(1), pY(1))-(pX(2), pY(2))
  _MAPTRIANGLE (FrameXStart, FrameYStart)-(FrameXStart + W, FrameYStart)-(FrameXStart + W, FrameYStart + H), Image TO(pX(0), pY(0))-(pX(3), pY(3))-(pX(2), pY(2))
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetVisibility (Sprite AS GDK_Sprite, OnOff AS _BYTE)
  Sprite.IsVisible = OnOff
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetAlpha (Sprite AS GDK_Sprite, Alpha&)
  Sprite.Alpha = Alpha&
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_GameObject_New (Handle AS GDK_Game_Object, File$, XframeCount%, YFrameCount%, X%, Y%, Speed!, Rotation!)
  GDK_Sprite_New Handle.Sprite, File$, XframeCount%, YFrameCount%, XframeCount% * YFrameCount%, _RGB32(255, 0, 255)
  GDK_Vector_New Handle.Vector, X%, Y%
  Handle.Rotation = Rotation!
  Handle.Speed = Speed
  GDK_Sprite_SetVisibility Handle.Sprite, -1
  GDK_Sprite_SetAlpha Handle.Sprite, _RGB32(255, 0, 255)
  Handle.Exists = GDK_TRUE
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_GameObject_Update (Handle AS GDK_Game_Object)
  GDK_Vector_Update Handle.Vector, Handle.Rotation, Handle.Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_New (Vector AS GDK_Vector, X!, Y!)
  Vector.X = X!
  Vector.Y = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_Update (Vector AS GDK_Vector, Rot, Speed)
  Vector.X = Vector.X + COS(Rot) * Speed
  Vector.Y = Vector.Y + SIN(Rot) * Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple math functions

FUNCTION GDK_Distance! (X%, Y%, XX%, YY%)
  xd% = XX% - X%
  yd% = YY% - Y%
  GDK_Distance! = SQR((xd% * xd%) + (yd% * yd%))
END FUNCTION

FUNCTION GDK_RadianToDegree! (Radians!)
  GDK_RadianToDegree! = Radians! * (180 / (4 * ATN(1)))
END FUNCTION

FUNCTION GDK_DegreeToRadian! (Degrees!)
  GDK_DegreeToRadian! = Degrees! * ((4 * ATN(1)) / 180)
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_ANIMATION_NEW (Anim AS GDK_Animation, Frame%, StartFrame%, ResetFrame%, AnimTime#, AnimTimer#)
  Anim.Frame = Frame%
  Anim.StartFrame = StartFrame%
  Anim.ResetFrame = ResetFrame%
  Anim.Time = AnimTime#
  Anim.Timer = AnimTimer#
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

  CALL GDK_GetBoxCorners(box1, box1_corners())
  CALL GDK_GetBoxCorners(box2, box2_corners())

  ' 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

  ' Restore the previous source
  _SOURCE old_source

  GetPixelFromImage = pColor
END FUNCTION


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  ' Create the bounding boxes for the intersection check
  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

  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


  ' 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)

      ' Scale the local coordinates
      local1.X = local1.X / object1.Sprite.Scale
      local1.Y = local1.Y / object1.Sprite.Scale
      local2.X = local2.X / object2.Sprite.Scale
      local2.Y = local2.Y / object2.Sprite.Scale

      ' Translate local coordinates to sprite's frame coordinates
      local1.X = local1.X + object1.Rect.RotationPointOffset.X
      local1.Y = local1.Y + object1.Rect.RotationPointOffset.Y
      local2.X = local2.X + object2.Rect.RotationPointOffset.X
      local2.Y = local2.Y + object2.Rect.RotationPointOffset.Y

      ' 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 a higher frequency layer (more detail)
      noise_val = noise_val + SIN((x + noise_offset) * noise_scale * 2) + COS((y + noise_offset) * noise_scale * 2)

      ' 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

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

  GDK_Vector_Update Ship.Vector, Ship.Rotation, Ship.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 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

END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

    CASE 6 'final phase/next level mayve
      TransitionStarted = GDK_FALSE
      Ship.Vector.X = 750
      Ship.Vector.Y = 300
      Ship.Rotation = 4 * ATN(1)
      Ship.Speed = 0
      GameState = GDK_STATE_BOSS_MODE
      MainMode% = 1

  END SELECT

END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  object.Vector.X = object.Vector.X + move_x
  object.Vector.Y = object.Vector.Y + move_y

  move_to_position = GDK_FALSE
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB PlayGameState

  '// 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


END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  lColor = _RGBA32(100, 100, 255, 100) ' Semi-transparent blue/white beam

  ' 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

  ' Calculate vertical spacing for initial spawn positions
  y_spacing = (SCREEN_WIDTH) / ENEMIES_PER_ROW

  ' 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

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

John
Reply
#8
Inefficient. Uses too many buttons. Mine only uses one! Big Grin

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. Cry 

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.

Pete Big Grin
Reply
#9
[Image: Repulsed.png]

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! Wink  

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

CONST GDK_Vector_Size = 8, GDK_TRUE = -1, GDK_FALSE = 0

'///////////////////////////////////// Game States ////////////////////////////////////////////////
CONST GDK_STATE_PLAYING = 0
CONST GDK_STATE_TRANSITION = 1
CONST GDK_STATE_BOSS_MODE = 2
CONST REPEL_CHARGE_TIME = 5.0

' Global constants for the swarm behavior
CONST MAX_ENEMIES = 40
CONST NUM_GROUPS = 4
CONST ENEMIES_PER_GROUP = 50
CONST HELIX_RADIUS = 20.0
CONST HELIX_SPEED = 0.05
CONST Z_DISTANCE_SCALE_MODIFIER = 0.05
CONST SWARM_CENTER_X = 400
CONST SWARM_CENTER_Y = 300
CONST SWARM_TRAVEL_SPEED = 1.0 ' Pixels per frame to move across the screen

CONST SWARM_START_X = -50
CONST ENEMIES_PER_ROW = 5
CONST SCREEN_WIDTH = 800
CONST SCREEN_HEIGHT = 600

' 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

SCREEN _NEWIMAGE(800, 600, 32)
_SCREENMOVE 0, 0
_DELAY 1

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

ex% = 60: ey% = 40
GDK_GameObject_New Enemy(0), "Invader.png", 4, 1, ex%, ey%, 0, 0
GDK_Sprite_SetRotationAsCenter Enemy(0).Sprite
GDK_ANIMATION_NEW Enemy(0).Animation, 1, 1, 4, .15, 0
BossEnemy(0) = Enemy(0)
ex% = 120
FOR i% = 1 TO MAX_ENEMIES - 1
  Enemy(i%) = Enemy(0)
  BossEnemy(i%) = Enemy(0)

  Enemy(i%).Vector.X = ex%
  Enemy(i%).Vector.Y = ey%
  IF (i% + 1) MOD 10 = 0 THEN
    ey% = ey% + 90
    ex% = 60
  ELSE
    ex% = ex% + 60
  END IF
NEXT

GDK_GameObject_New Bullet(0), "Bullet_Blue.png", 1, 1, 0, 0, 0, 0
GDK_Sprite_SetRotationAsCenter Bullet(0).Sprite
Bullet(0).Exists = GDK_FALSE '// Reset default auto existance
Enemy_Bullet = Bullet(0)
FOR i% = 1 TO Max_Bullets& - 1
  Bullet(i%) = Bullet(0)
NEXT


WallPaper = _NEWIMAGE(800, 600, 32): GDK_GenerateNebula WallPaper '// generates a random background stylised to look like a nebula (its half crap)

_PUTIMAGE , WallPaper



_PRINTSTRING (325, 260), "Space Quest"
_PRINTSTRING (150, 350), "Space = Shoot, Left/Right arrows move, Hold LShift for Repulsor."
_DISPLAY
_DELAY 3

GameState = GDK_STATE_PLAYING
TransitionStarted = GDK_FALSE
LastGT# = TIMER(.001)

'//////////////////////////////////////// Main Loop ////////////////////////////////////////////////////////////

DO

  gt# = TIMER(.001) '// global timer
  DT# = gt# - LastGT# '// Delta time

  CLS
  _LIMIT 30

  _PUTIMAGE , WallPaper


  SELECT CASE MainMode%
    CASE 0 '// Space invaders

      SELECT CASE GameState
        CASE GDK_STATE_PLAYING
          Shoot_Time = .6 '//
          Player_MinX = 40 '//  axis limits
          Player_MaxX = 760
          eShoot_Time = 3

          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%

      _DISPLAY

    CASE 1
      BossModeState BossEnemy(), Ship
      Update_Enemies_2 BossEnemy()
      Render_Enemies_2 BossEnemy()
      GDK_GameObject_Draw Ship, Ship.Animation.Frame
      Draw_Shield Ship, Hit%

      _DISPLAY


  END SELECT

  LastGT# = gt#

LOOP UNTIL INKEY$ = CHR$(27)

SYSTEM


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

TYPE GDK_Vector
  X AS SINGLE
  Y AS SINGLE
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_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

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_GetBoxCorners (Box AS GDK_Box, Corners() AS GDK_Vector)
  DIM HalfW AS SINGLE, HalfH AS SINGLE
  DIM Pivot AS GDK_Vector
  DIM corner AS GDK_Vector

  HalfW = Box.WidthHeight.X / 2
  HalfH = Box.WidthHeight.Y / 2

  ' Calculate the absolute position of the rotation pivot
  Pivot.X = Box.Position.X + Box.RotationPointOffset.X
  Pivot.Y = Box.Position.Y + Box.RotationPointOffset.Y

  ' Corner 0: top-left relative to the pivot
  corner.X = Pivot.X - HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y - HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(0), corner, Pivot, Box.Rotation)

  ' Corner 1: top-right relative to the pivot
  corner.X = Pivot.X + HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y - HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(1), corner, Pivot, Box.Rotation)

  ' Corner 2: bottom-right relative to the pivot
  corner.X = Pivot.X + HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y + HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(2), corner, Pivot, Box.Rotation)

  ' Corner 3: bottom-left relative to the pivot
  corner.X = Pivot.X - HalfW - Box.RotationPointOffset.X
  corner.Y = Pivot.Y + HalfH - Box.RotationPointOffset.Y
  CALL GDK_RotatePoint(Corners(3), corner, Pivot, Box.Rotation)
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  Min1 = 9999999!: Max1 = -9999999!
  Min2 = 9999999!: Max2 = -9999999!

  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

  CALL GDK_GetBoxCorners(Box1, Box1_Corners())
  CALL GDK_GetBoxCorners(Box2, Box2_Corners())

  GDK_DrawBox Box1, _RGB32(255, 0, 0) '// render collision boxes - temp
  GDK_DrawBox Box2, _RGB32(255, 0, 0) '// render collision boxes - temp

  Axis(0).X = -(Box1_Corners(1).Y - Box1_Corners(0).Y): Axis(0).Y = Box1_Corners(1).X - Box1_Corners(0).X
  Axis(1).X = -(Box1_Corners(0).Y - Box1_Corners(3).Y): Axis(1).Y = Box1_Corners(0).X - Box1_Corners(3).X

  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

  Axis(2).X = -(Box2_Corners(1).Y - Box2_Corners(0).Y): Axis(2).Y = Box2_Corners(1).X - Box2_Corners(0).X
  Axis(3).X = -(Box2_Corners(0).Y - Box2_Corners(3).Y): Axis(3).Y = Box2_Corners(0).X - Box2_Corners(3).X

  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

  GDK_Box_Intersect = -1
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple sprite stuff - For universal spaced sprites sheets
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_New (Sprite AS GDK_Sprite, FileSource$, XFrameCount%, YFrameCount%, TotalFrameCount%, Alpha&)
  Sprite.File = _LOADIMAGE(FileSource$, 32)
  Sprite.Width = _WIDTH(Sprite.File)
  Sprite.Height = _HEIGHT(Sprite.File)
  Sprite.XFrameCount = XFrameCount%
  Sprite.YFrameCount = YFrameCount%
  Sprite.TotalFrameCount = TotalFrameCount%
  Sprite.Scale = 1
  Sprite.IsVisible = -1
  Sprite.Alpha = Alpha&
  Sprite.FrameWidth = Sprite.Width \ Sprite.XFrameCount
  Sprite.FrameHeight = Sprite.Height \ Sprite.YFrameCount
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetRotationPoint (Sprite AS GDK_Sprite, X!, Y!)
  Sprite.RotationX = X!
  Sprite.RotationY = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

    ' Rotate
    X2 = pX(I) * COSr - pY(I) * SINr
    Y2 = pY(I) * COSr + pX(I) * SINr
    pX(I) = X2
    pY(I) = Y2

    ' Translate to screen position
    pX(I) = pX(I) + X: pY(I) = pY(I) + Y
  NEXT

  ' Map the source rectangle to the rotated and scaled destination
  _MAPTRIANGLE (FrameXStart, FrameYStart)-(FrameXStart, FrameYStart + H)-(FrameXStart + W, FrameYStart + H), Image TO(pX(0), pY(0))-(pX(1), pY(1))-(pX(2), pY(2))
  _MAPTRIANGLE (FrameXStart, FrameYStart)-(FrameXStart + W, FrameYStart)-(FrameXStart + W, FrameYStart + H), Image TO(pX(0), pY(0))-(pX(3), pY(3))-(pX(2), pY(2))
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetVisibility (Sprite AS GDK_Sprite, OnOff AS _BYTE)
  Sprite.IsVisible = OnOff
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Sprite_SetAlpha (Sprite AS GDK_Sprite, Alpha&)
  Sprite.Alpha = Alpha&
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_GameObject_New (Handle AS GDK_Game_Object, File$, XframeCount%, YFrameCount%, X%, Y%, Speed!, Rotation!)
  GDK_Sprite_New Handle.Sprite, File$, XframeCount%, YFrameCount%, XframeCount% * YFrameCount%, _RGB32(255, 0, 255)
  GDK_Vector_New Handle.Vector, X%, Y%
  Handle.Rotation = Rotation!
  Handle.Speed = Speed
  GDK_Sprite_SetVisibility Handle.Sprite, -1
  GDK_Sprite_SetAlpha Handle.Sprite, _RGB32(255, 0, 255)
  Handle.Exists = GDK_TRUE
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_GameObject_Update (Handle AS GDK_Game_Object)
  GDK_Vector_Update Handle.Vector, Handle.Rotation, Handle.Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_New (Vector AS GDK_Vector, X!, Y!)
  Vector.X = X!
  Vector.Y = Y!
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_Vector_Update (Vector AS GDK_Vector, Rot, Speed)
  Vector.X = Vector.X + COS(Rot) * Speed
  Vector.Y = Vector.Y + SIN(Rot) * Speed
END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// Simple math functions

FUNCTION GDK_Distance! (X%, Y%, XX%, YY%)
  xd% = XX% - X%
  yd% = YY% - Y%
  GDK_Distance! = SQR((xd% * xd%) + (yd% * yd%))
END FUNCTION

FUNCTION GDK_RadianToDegree! (Radians!)
  GDK_RadianToDegree! = Radians! * (180 / _PI)
END FUNCTION

FUNCTION GDK_DegreeToRadian! (Degrees!)
  GDK_DegreeToRadian! = Degrees! * (_PI / 180)
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB GDK_ANIMATION_NEW (Anim AS GDK_Animation, Frame%, StartFrame%, ResetFrame%, AnimTime#, AnimTimer#)
  Anim.Frame = Frame%
  Anim.StartFrame = StartFrame%
  Anim.ResetFrame = ResetFrame%
  Anim.Time = AnimTime#
  Anim.Timer = AnimTimer#
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

  CALL GDK_GetBoxCorners(box1, box1_corners())
  CALL GDK_GetBoxCorners(box2, box2_corners())

  ' 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

  ' Restore the previous source
  _SOURCE old_source

  GetPixelFromImage = pColor
END FUNCTION


'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  ' Create the bounding boxes for the intersection check
  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

  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


  ' 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)

      ' Scale the local coordinates
      local1.X = local1.X / object1.Sprite.Scale
      local1.Y = local1.Y / object1.Sprite.Scale
      local2.X = local2.X / object2.Sprite.Scale
      local2.Y = local2.Y / object2.Sprite.Scale

      ' Translate local coordinates to sprite's frame coordinates
      local1.X = local1.X + object1.Rect.RotationPointOffset.X
      local1.Y = local1.Y + object1.Rect.RotationPointOffset.Y
      local2.X = local2.X + object2.Rect.RotationPointOffset.X
      local2.Y = local2.Y + object2.Rect.RotationPointOffset.Y

      ' 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 a higher frequency layer (more detail)
      noise_val = noise_val + SIN((x + noise_offset) * noise_scale * 2) + COS((y + noise_offset) * noise_scale * 2)

      ' 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

  GDK_Vector_Update Ship.Vector, Ship.Rotation, Ship.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 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

END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

    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 / 4 * 1.5 THEN
        transitionPhase = 6 ' Proceed to finalize
        RepulsorRadius = 0 ' Reset for next time
      END IF

    CASE 6 'final phase/next level mayve '///////////////////////////////////////////////////////////////////////////
      TransitionStarted = GDK_FALSE
      Ship.Vector.X = 750
      Ship.Vector.Y = 300
      Ship.Rotation = 4 * ATN(1)
      Ship.Speed = 0
      GameState = GDK_STATE_BOSS_MODE
      MainMode% = 1
      transitionPhase = 0

  END SELECT '///////////////////////////////////////////////////////////////////////////

END SUB



'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

  object.Vector.X = object.Vector.X + move_x
  object.Vector.Y = object.Vector.Y + move_y

  move_to_position = GDK_FALSE
END FUNCTION

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

SUB PlayGameState

  '// 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


END SUB

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////


Oh and where it crashed...thats the best bit! Repulsor time baby! 

Unseen
Reply
#10
Happy Birthday, Unseen!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Tiny Space Invaders bplus 15 1,582 09-11-2025, 04:39 PM
Last Post: Pete
  Rectangle Collisions Demo using SAT Unseen Machine 10 1,148 09-08-2025, 07:12 PM
Last Post: Pete
  Simple finance tracker program Delsus 0 515 06-15-2025, 08:02 AM
Last Post: Delsus
  Simple Numbers Magic Trick With MessageBox SierraKen 0 484 05-12-2025, 09:45 PM
Last Post: SierraKen
  Space Pongy bplus 3 818 11-29-2024, 11:18 PM
Last Post: SierraKen

Forum Jump:


Users browsing this thread: