Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- program LapeGame123;
- {$I SRL/osr.simba}
- const
- WIDTH = 720;
- HEIGHT = 480;
- // Colors
- C_SKY = $FF9933;
- C_WALL = $33AA55;
- C_LAVA = $0000FF;
- C_WATER = $FF9900;
- C_CRATE = $003366;
- C_PIPE = $C0C0C0;
- C_CLOUD = $FFFFFF;
- C_PROJ = $0000AA;
- C_OBSIDIAN = $000000;
- // Physics
- GRAVITY_AIR = 0.9;
- GRAVITY_WATER = 0.15;
- JUMP_FORCE = -16.5;
- JUMP_WATER = -5.0;
- MOVE_SPEED = 5.0;
- DASH_SPEED = 8.5;
- WATER_DRAG = 0.6;
- MAX_BREATH = 1800;
- MAX_DASH = 120;
- RESPAWN_TIME = 300;
- type
- TEntityType = (etPlayer, etWalker, etThrower);
- TProjectile = record
- Active: Boolean;
- Pos, Vel: Vector2;
- Size: Integer;
- end;
- // Forward declaration so TEntity can reference TGame in methods
- TGame = record
- Buffer: TMufasaBitmap;
- Camera: TPoint;
- end;
- TEntity = record
- Active: Boolean;
- EType: TEntityType;
- // Physics
- Pos: Vector2;
- HomePos: Vector2;
- Vel: Vector2;
- Size: TBox;
- Color: Int32;
- // State
- Grounded: Boolean;
- FaceRight: Boolean;
- RespawnTimer: Integer;
- // Water Logic
- InWater: Boolean;
- WaterSurfaceY: Integer;
- // Player Specific
- Breath: Integer;
- DashTimer: Integer;
- LastTapTime: UInt64;
- LastKey: Byte;
- // NPC Specific
- AttackCooldown: Integer;
- SpriteL, SpriteR: TMufasaBitmap;
- end;
- // Actual TGame Definition
- TGameFull = record
- Buffer: TMufasaBitmap;
- Camera: TPoint;
- Player: TEntity;
- Enemies: array of TEntity;
- Projectiles: array of TProjectile;
- // World Layers
- Walls, Crates, Pipes, Lava, Water, Clouds, Obsidian: TBoxArray;
- end;
- var
- Game: TGameFull;
- // -----------------------------------------------------------------------------
- // Helpers
- // -----------------------------------------------------------------------------
- function MakeBox(x, y, w, h: Int32): TBox;
- begin
- Result := [x, y, x + w, y + h];
- end;
- function CheckCollision(A, B: TBox): Boolean;
- begin
- Result := (A.X1 < B.X2) and (A.X2 > B.X1) and (A.Y1 < B.Y2) and (A.Y2 > B.Y1);
- end;
- // Pass G as TGameFull
- function CheckSolidCollision(Bounds: TBox; var G: TGameFull; out HitBox: TBox): Boolean;
- var i: Integer;
- begin
- Result := True;
- for i := 0 to High(G.Walls) do if CheckCollision(Bounds, G.Walls[i]) then begin HitBox := G.Walls[i]; Exit; end;
- for i := 0 to High(G.Crates) do if CheckCollision(Bounds, G.Crates[i]) then begin HitBox := G.Crates[i]; Exit; end;
- for i := 0 to High(G.Pipes) do if CheckCollision(Bounds, G.Pipes[i]) then begin HitBox := G.Pipes[i]; Exit; end;
- for i := 0 to High(G.Obsidian) do if CheckCollision(Bounds, G.Obsidian[i]) then begin HitBox := G.Obsidian[i]; Exit; end;
- Result := False;
- end;
- function PointInBoxes(Pt: TPoint; Boxes: TBoxArray): Boolean;
- var i: Integer;
- begin
- Result := False;
- for i := 0 to High(Boxes) do if Boxes[i].Contains(Pt) then Exit(True);
- end;
- // -----------------------------------------------------------------------------
- // Entity Logic
- // -----------------------------------------------------------------------------
- procedure TEntity.Init(StartX, StartY: Double; EType: TEntityType);
- begin
- Self.Active := True;
- Self.EType := EType;
- Self.Pos := [StartX, StartY];
- Self.HomePos:= [StartX, StartY];
- Self.Vel := [0, 0];
- Self.FaceRight := True;
- Self.Size := [0, 0, 40, 40];
- Self.RespawnTimer := 0;
- case EType of
- etPlayer:
- begin
- Self.Color := $0000FF;
- Self.Breath := MAX_BREATH;
- end;
- etWalker:
- begin
- Self.Color := $00FFFF;
- Self.Vel.x := 2.0;
- end;
- etThrower:
- begin
- Self.Color := $800080;
- Self.Vel.x := 1.5;
- end;
- end;
- end;
- function TEntity.GetBounds(): TBox;
- begin
- Result.X1 := Round(Self.Pos.X);
- Result.Y1 := Round(Self.Pos.Y);
- Result.X2 := Result.X1 + Self.Size.X2;
- Result.Y2 := Result.Y1 + Self.Size.Y2;
- end;
- function TEntity.ShouldTurnAround(var G: TGameFull): Boolean;
- var
- Feeler: TPoint;
- LookAhead: Integer;
- begin
- if not Self.Grounded then Exit(False);
- LookAhead := Round(Abs(Self.Vel.X) * 10) + 10;
- if Self.Vel.X > 0 then Feeler.X := Round(Self.Pos.X) + Self.Size.X2 + LookAhead
- else Feeler.X := Round(Self.Pos.X) - LookAhead;
- // Cliff Check
- Feeler.Y := Round(Self.Pos.Y) + Self.Size.Y2 + 5;
- if (not PointInBoxes(Feeler, G.Walls)) and
- (not PointInBoxes(Feeler, G.Obsidian)) and
- (not PointInBoxes(Feeler, G.Crates)) and
- (not PointInBoxes(Feeler, G.Pipes)) then Exit(True);
- // Hazard Check
- Feeler.Y := Round(Self.Pos.Y) + Self.Size.Y2 - 10;
- if PointInBoxes(Feeler, G.Lava) or PointInBoxes(Feeler, G.Water) then Exit(True);
- Result := False;
- end;
- procedure SpawnProjectile(StartPos, TargetPos: Vector2; var G: TGameFull);
- var
- Idx: Integer;
- begin
- Idx := Length(G.Projectiles);
- SetLength(G.Projectiles, Idx + 1);
- G.Projectiles[Idx].Active := True;
- G.Projectiles[Idx].Size := 10;
- G.Projectiles[Idx].Pos := StartPos;
- G.Projectiles[Idx].Vel.Y := -6.0;
- if TargetPos.X > StartPos.X then G.Projectiles[Idx].Vel.X := 5.0
- else G.Projectiles[Idx].Vel.X := -5.0;
- end;
- procedure TEntity.UpdatePhysics(var G: TGameFull);
- var
- Predicted, HitBox: TBox;
- i: Integer;
- DistToPlayer: Double;
- begin
- if not Self.Active then
- begin
- if Self.EType = etPlayer then Exit;
- if Self.RespawnTimer > 0 then
- begin
- Dec(Self.RespawnTimer);
- if Self.RespawnTimer <= 0 then Self.Init(Self.HomePos.X, Self.HomePos.Y, Self.EType);
- end;
- Exit;
- end;
- Self.Grounded := False;
- Self.InWater := False;
- // --- Attack Logic ---
- if (Self.EType = etThrower) and (Self.AttackCooldown > 0) then Dec(Self.AttackCooldown);
- if (Self.EType = etThrower) and (Self.AttackCooldown <= 0) then
- begin
- DistToPlayer := Hypot(Self.Pos.X - G.Player.Pos.X, Self.Pos.Y - G.Player.Pos.Y);
- if DistToPlayer < 350 then
- begin
- SpawnProjectile(Self.Pos, G.Player.Pos, G);
- Self.AttackCooldown := 120;
- end;
- end;
- if (Self.EType = etPlayer) and (Self.DashTimer > 0) then Dec(Self.DashTimer);
- // --- Water Check ---
- Predicted := Self.GetBounds();
- for i := 0 to High(G.Water) do
- if CheckCollision(Predicted, G.Water[i]) then
- begin
- Self.InWater := True;
- Self.WaterSurfaceY := G.Water[i].Y1;
- Break;
- end;
- // --- Lava Check ---
- for i := 0 to High(G.Lava) do
- if CheckCollision(Predicted, G.Lava[i]) then
- begin
- if Self.EType = etPlayer then
- begin
- WriteLn('Burned!');
- Self.Init(100, 300, etPlayer);
- G.Camera := [0,0];
- Exit;
- end
- else
- begin
- Self.Active := False;
- Self.RespawnTimer := RESPAWN_TIME;
- end;
- Exit;
- end;
- // --- Breath Logic ---
- if Self.EType = etPlayer then
- begin
- if Self.InWater then
- begin
- Dec(Self.Breath);
- if Self.Breath <= 0 then
- begin
- Self.Init(100, 300, etPlayer);
- G.Camera := [0,0];
- Exit;
- end;
- end
- else
- Self.Breath := MAX_BREATH;
- end;
- // --- X Movement ---
- if Self.InWater then Self.Vel.X := Self.Vel.X * WATER_DRAG;
- Self.Pos.X := Self.Pos.X + Self.Vel.X;
- Predicted := Self.GetBounds();
- if CheckSolidCollision(Predicted, G, HitBox) then
- begin
- if Self.Vel.X > 0 then Self.Pos.X := HitBox.X1 - Self.Size.X2
- else if Self.Vel.X < 0 then Self.Pos.X := HitBox.X2;
- if Self.EType = etPlayer then Self.Vel.X := 0
- else Self.Vel.X := -Self.Vel.X;
- end;
- // --- Turn Logic ---
- if (Self.EType <> etPlayer) and Self.Grounded and (Self.Vel.X <> 0) then
- begin
- if Self.ShouldTurnAround(G) then Self.Vel.X := -Self.Vel.X;
- end;
- // --- Y Movement ---
- Self.Pos.Y := Self.Pos.Y + Self.Vel.Y;
- Predicted := Self.GetBounds();
- if CheckSolidCollision(Predicted, G, HitBox) then
- begin
- if Self.Vel.Y > 0 then
- begin
- Self.Pos.Y := HitBox.Y1 - Self.Size.Y2;
- Self.Grounded := True;
- end
- else if Self.Vel.Y < 0 then
- Self.Pos.Y := HitBox.Y2;
- Self.Vel.Y := 0;
- end;
- // --- Gravity ---
- if Self.InWater then
- begin
- Self.Vel.Y := Self.Vel.Y + GRAVITY_WATER;
- if Self.Vel.Y > 3 then Self.Vel.Y := 3;
- end
- else
- begin
- Self.Vel.Y := Self.Vel.Y + GRAVITY_AIR;
- if Self.Vel.Y > 15 then Self.Vel.Y := 15;
- end;
- if Self.Pos.Y > 1200 then
- begin
- if Self.EType = etPlayer then
- begin
- Self.Init(100, 300, etPlayer);
- G.Camera := [0,0];
- end else
- begin
- Self.Active := False;
- Self.RespawnTimer := RESPAWN_TIME;
- end;
- end;
- end;
- procedure TEntity.Draw(Buffer: TMufasaBitmap; Cam: TPoint);
- var
- DrawBox: TBox;
- Pct: Double;
- BarW: Int32;
- begin
- if not Self.Active then Exit;
- DrawBox := Self.GetBounds();
- DrawBox := DrawBox.Offset(Point(-Cam.X, -Cam.Y));
- Buffer.DrawBoxFilled(DrawBox, False, Self.Color);
- if Self.EType = etPlayer then
- begin
- if Self.Breath < MAX_BREATH then
- begin
- Pct := Self.Breath / MAX_BREATH;
- BarW := Round(40 * Pct);
- Buffer.DrawBoxFilled([DrawBox.X1, DrawBox.Y1 - 10, DrawBox.X1 + 40, DrawBox.Y1 - 5], False, $000000);
- Buffer.DrawBoxFilled([DrawBox.X1, DrawBox.Y1 - 10, DrawBox.X1 + BarW, DrawBox.Y1 - 5], False, $00FFFF);
- end;
- if Self.DashTimer > 0 then
- begin
- Pct := Self.DashTimer / MAX_DASH;
- BarW := Round(40 * Pct);
- Buffer.DrawBoxFilled([DrawBox.X1, DrawBox.Y1 - 16, DrawBox.X1 + 40, DrawBox.Y1 - 11], False, $000000);
- Buffer.DrawBoxFilled([DrawBox.X1, DrawBox.Y1 - 16, DrawBox.X1 + BarW, DrawBox.Y1 - 11], False, $FFFF00);
- end;
- end;
- end;
- // -----------------------------------------------------------------------------
- // Projectiles & Main
- // -----------------------------------------------------------------------------
- procedure UpdateProjectiles(var G: TGameFull);
- var
- i: Integer;
- ProjBox, Hit, PlayerBox: TBox;
- begin
- PlayerBox := G.Player.GetBounds();
- for i := 0 to High(G.Projectiles) do
- begin
- if not G.Projectiles[i].Active then Continue;
- G.Projectiles[i].Pos.X := G.Projectiles[i].Pos.X + G.Projectiles[i].Vel.X;
- G.Projectiles[i].Pos.Y := G.Projectiles[i].Pos.Y + G.Projectiles[i].Vel.Y;
- G.Projectiles[i].Vel.Y := G.Projectiles[i].Vel.Y + GRAVITY_AIR;
- ProjBox := [Round(G.Projectiles[i].Pos.X), Round(G.Projectiles[i].Pos.Y),
- Round(G.Projectiles[i].Pos.X) + G.Projectiles[i].Size,
- Round(G.Projectiles[i].Pos.Y) + G.Projectiles[i].Size];
- if CheckSolidCollision(ProjBox, G, Hit) or (ProjBox.Y1 > 1200) then
- begin
- G.Projectiles[i].Active := False;
- Continue;
- end;
- if CheckCollision(ProjBox, PlayerBox) then
- begin
- G.Player.Init(100, 300, etPlayer);
- G.Camera := [0,0];
- G.Projectiles[i].Active := False;
- end;
- end;
- end;
- procedure TGameFull.Setup();
- var
- i, x, GapType, PropRoll: Integer;
- LastGapType: Integer;
- begin
- Self.Buffer.Init();
- Self.Buffer.SetSize(WIDTH, HEIGHT);
- Self.Player.Init(100, 250, etPlayer);
- SetLength(Self.Enemies, 30);
- for i := 0 to High(Self.Enemies) do
- begin
- if Random(3) = 0 then Self.Enemies[i].Init(500 + (i * 600), 100, etThrower)
- else Self.Enemies[i].Init(500 + (i * 600), 100, etWalker);
- end;
- Self.Walls += MakeBox(-100, 350, 600, 200);
- x := 500;
- LastGapType := 0;
- while x < 40000 do
- begin
- if Random(2) = 0 then
- Self.Clouds += MakeBox(x + Random(200), Random(50, 200), Random(80, 150), Random(30, 50));
- GapType := Random(0, 5);
- // Obsidian Gen (Prevent Water touching Lava)
- if ((LastGapType = 4) and (GapType = 5)) or ((LastGapType = 5) and (GapType = 4)) then
- begin
- Self.Obsidian += MakeBox(x, 350, 50, 200);
- x := x + 50;
- end;
- if GapType <= 3 then // Land
- begin
- Self.Walls += MakeBox(x, 350, Random(300, 700), 200);
- PropRoll := Random(10);
- if PropRoll <= 2 then Self.Pipes += MakeBox(x + 50, 300, 60, 50)
- else if PropRoll = 3 then
- begin
- Self.Pipes += MakeBox(x + 100, 250, 60, 100);
- Self.Crates += MakeBox(x + 40, 310, 40, 40);
- end;
- if PropRoll = 4 then
- begin
- Self.Crates += MakeBox(x + 150, 310, 40, 40);
- Self.Crates += MakeBox(x + 190, 310, 40, 40);
- Self.Crates += MakeBox(x + 170, 270, 40, 40);
- end
- else if PropRoll >= 8 then
- begin
- Self.Crates += MakeBox(x + 100, 200, 40, 40);
- Self.Crates += MakeBox(x + 140, 200, 40, 40);
- end;
- x := Self.Walls[High(Self.Walls)].X2;
- LastGapType := 0;
- end
- else if GapType = 4 then // Water
- begin
- Self.Water += MakeBox(x, 370, 300, 180);
- x += 300;
- LastGapType := 4;
- end
- else // Lava
- begin
- Self.Lava += MakeBox(x, 380, 200, 50);
- Self.Crates += MakeBox(x + 80, 280, 40, 40);
- x += 200;
- LastGapType := 5;
- end;
- end;
- DisplayDebugImgWindow(WIDTH, HEIGHT);
- end;
- procedure TGameFull.UpdateInput();
- var
- TargetSpeed: Double;
- CurrTime: UInt64;
- begin
- CurrTime := GetTickCount();
- TargetSpeed := MOVE_SPEED;
- if Self.Player.DashTimer > 0 then TargetSpeed := DASH_SPEED;
- Self.Player.Vel.X := Self.Player.Vel.X * 0.80;
- if Abs(Self.Player.Vel.X) < 0.1 then Self.Player.Vel.X := 0;
- if IsKeyDown(VK_RIGHT) then
- begin
- if (Self.Player.LastKey <> VK_RIGHT) then
- begin
- if (CurrTime - Self.Player.LastTapTime < 250) and (Self.Player.LastKey = 0) then
- begin
- Self.Player.DashTimer := MAX_DASH;
- TargetSpeed := DASH_SPEED;
- end;
- Self.Player.LastTapTime := CurrTime;
- end;
- Self.Player.LastKey := VK_RIGHT;
- Self.Player.Vel.X := TargetSpeed;
- Self.Player.FaceRight := True;
- end
- else if IsKeyDown(VK_LEFT) then
- begin
- if (Self.Player.LastKey <> VK_LEFT) then
- begin
- if (CurrTime - Self.Player.LastTapTime < 250) and (Self.Player.LastKey = 0) then
- begin
- Self.Player.DashTimer := MAX_DASH;
- TargetSpeed := DASH_SPEED;
- end;
- Self.Player.LastTapTime := CurrTime;
- end;
- Self.Player.LastKey := VK_LEFT;
- Self.Player.Vel.X := -TargetSpeed;
- Self.Player.FaceRight := False;
- end
- else
- Self.Player.LastKey := 0;
- if IsKeyDown(VK_SPACE) then
- begin
- if Self.Player.InWater then
- begin
- Self.Player.Vel.Y := Self.Player.Vel.Y - 1.5;
- if Self.Player.Vel.Y < JUMP_WATER then Self.Player.Vel.Y := JUMP_WATER;
- // Water Exit Force
- if Self.Player.Pos.Y <= (Self.Player.WaterSurfaceY + 15) then
- Self.Player.Vel.Y := JUMP_FORCE * 0.85;
- end
- else if Self.Player.Grounded then
- Self.Player.Vel.Y := JUMP_FORCE;
- end;
- end;
- procedure TGameFull.CheckCombat();
- var
- i: Integer;
- PBox, EBox: TBox;
- begin
- PBox := Self.Player.GetBounds();
- for i := 0 to High(Self.Enemies) do
- begin
- if not Self.Enemies[i].Active then Continue;
- EBox := Self.Enemies[i].GetBounds();
- if CheckCollision(PBox, EBox) then
- begin
- if (Self.Player.Vel.Y > 0) and (PBox.Y2 < EBox.Y2 - 10) then
- begin
- Self.Enemies[i].Active := False;
- Self.Enemies[i].RespawnTimer := RESPAWN_TIME;
- Self.Player.Vel.Y := -10.0;
- end
- else
- begin
- Self.Player.Init(100, 300, etPlayer);
- Self.Camera := [0,0];
- end;
- end;
- end;
- end;
- procedure TGameFull.Update();
- var
- i, PlayerScreenX, PlayerScreenY: Integer;
- begin
- Self.UpdateInput();
- Self.Player.UpdatePhysics(Self);
- for i := 0 to High(Self.Enemies) do Self.Enemies[i].UpdatePhysics(Self);
- UpdateProjectiles(Self);
- Self.CheckCombat();
- PlayerScreenX := Round(Self.Player.Pos.X) - Self.Camera.X;
- PlayerScreenY := Round(Self.Player.Pos.Y) - Self.Camera.Y;
- if PlayerScreenX > 450 then Self.Camera.X := Round(Self.Player.Pos.X) - 450;
- if PlayerScreenX < 200 then Self.Camera.X := Round(Self.Player.Pos.X) - 200;
- if Self.Camera.X < 0 then Self.Camera.X := 0;
- if PlayerScreenY < 150 then Self.Camera.Y := Round(Self.Player.Pos.Y) - 150;
- if PlayerScreenY > 330 then Self.Camera.Y := Round(Self.Player.Pos.Y) - 330;
- if Self.Camera.Y > 50 then Self.Camera.Y := 50;
- end;
- procedure TGameFull.Render();
- procedure DrawLayer(Boxes: TBoxArray; Col: Int32);
- var b, dw: TBox;
- begin
- for b in Boxes do
- begin
- dw := b.Offset(Point(-Self.Camera.X, -Self.Camera.Y));
- if (dw.X2 > 0) and (dw.X1 < WIDTH) and (dw.Y2 > 0) and (dw.Y1 < HEIGHT) then
- Self.Buffer.DrawBoxFilled(dw, False, Col);
- end;
- end;
- var i: Integer;
- begin
- Self.Buffer.DrawClear(C_SKY);
- DrawLayer(Self.Clouds, C_CLOUD);
- DrawLayer(Self.Walls, C_WALL);
- DrawLayer(Self.Obsidian, C_OBSIDIAN);
- DrawLayer(Self.Crates, C_CRATE);
- DrawLayer(Self.Pipes, C_PIPE);
- DrawLayer(Self.Lava, C_LAVA);
- DrawLayer(Self.Water, C_WATER);
- for i := 0 to High(Self.Projectiles) do
- if Self.Projectiles[i].Active then
- begin
- Self.Buffer.DrawBoxFilled([Round(Self.Projectiles[i].Pos.X)-Self.Camera.X, Round(Self.Projectiles[i].Pos.Y)-Self.Camera.Y,
- Round(Self.Projectiles[i].Pos.X)+Self.Projectiles[i].Size-Self.Camera.X,
- Round(Self.Projectiles[i].Pos.Y)+Self.Projectiles[i].Size-Self.Camera.Y], False, C_PROJ);
- end;
- for i := 0 to High(Self.Enemies) do Self.Enemies[i].Draw(Self.Buffer, Self.Camera);
- Self.Player.Draw(Self.Buffer, Self.Camera);
- DrawBitmapDebugImg(Self.Buffer);
- end;
- procedure TGameFull.Loop();
- begin
- while True do
- begin
- Self.Update();
- Self.Render();
- Wait(16);
- end;
- end;
- begin
- ClearDebug();
- Game.Setup();
- Game.Loop();
- end.
Advertisement
Add Comment
Please, Sign In to add comment