Skip to content

Commit a06f835

Browse files
committed
(#26) GameEngine: support collision of player with walls
1 parent acdb288 commit a06f835

File tree

7 files changed

+79
-26
lines changed

7 files changed

+79
-26
lines changed

O21.Game/Engine/Entities.fs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,23 @@ type Player = {
2323
match this.Direction with
2424
| HorizontalDirection.Left -> this.TopLeft
2525
| HorizontalDirection.Right -> this.TopRight
26+
27+
member this.Box: Box = { TopLeft = this.TopLeft; Size = GameRules.PlayerSize }
28+
29+
member this.Update(level: Level, timeDelta: int): PlayerEffect =
30+
let newPlayer =
31+
{ this with
32+
TopLeft = this.TopLeft + this.Velocity * timeDelta
33+
ShotCooldown = max (this.ShotCooldown - timeDelta) 0
34+
}
35+
match CheckCollision level newPlayer.Box with
36+
| Collision.OutOfBounds -> PlayerEffect.Update newPlayer // TODO[#28]: Level progression
37+
| Collision.CollidesBrick -> PlayerEffect.Die
38+
| Collision.None -> PlayerEffect.Update newPlayer
2639

27-
member this.Update(timeDelta: int): Player =
28-
{ this with
29-
TopLeft = this.TopLeft + this.Velocity * timeDelta
30-
ShotCooldown = max (this.ShotCooldown - timeDelta) 0
31-
}
40+
and [<RequireQualifiedAccess>] PlayerEffect =
41+
| Update of Player
42+
| Die
3243

3344
type Bullet = {
3445
TopLeft: Point
@@ -54,7 +65,7 @@ type Bullet = {
5465
else
5566
match CheckCollision level newBullet.Box with
5667
| Collision.OutOfBounds -> None
57-
| Collision.TouchesBrick -> None
68+
| Collision.CollidesBrick -> None
5869
| Collision.None -> Some newBullet
5970
else
6071
this.Update(level, maxTimeToProcessInOneStep)
@@ -72,5 +83,5 @@ type Particle = {
7283
let newParticle = { this with TopLeft = newPosition }
7384
match CheckCollision level newParticle.Box with
7485
| Collision.OutOfBounds -> None
75-
| Collision.TouchesBrick -> None
86+
| Collision.CollidesBrick -> None
7687
| Collision.None -> Some newParticle

O21.Game/Engine/GameEngine.fs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ type GameEngine = {
3636
Period = 0
3737
}
3838
}
39-
40-
member this.Update(time: Time): GameEngine =
39+
40+
member this.Update(time: Time): GameEngine * ExternalEffect[] =
4141
let newTick = int <| (time.Total - this.StartTime.Total) * GameRules.TicksPerSecond
4242
let timeDelta = newTick - this.Tick
43+
let playerEffect = this.Player.Update(this.CurrentLevel, timeDelta)
4344
// TODO[#26]: Bullet collisions
4445
{ this with
4546
Tick = newTick
46-
Player = this.Player.Update(timeDelta)
4747
Bullets = this.Bullets |> Array.choose(_.Update(this.CurrentLevel, timeDelta))
4848
ParticlesSource = this.ParticlesSource.Update(this.CurrentLevel, this.Player, timeDelta)
49-
}
49+
} |> GameEngine.ProcessPlayerEffect playerEffect
5050

5151
member this.ApplyCommand(command: PlayerCommand): GameEngine * ExternalEffect[] =
5252
match command with
@@ -71,3 +71,13 @@ type GameEngine = {
7171
Bullets = Array.append this.Bullets [| newBullet |] // TODO[#130]: Make more efficient (persistent vector?)
7272
}, [| PlaySound SoundType.Shot |]
7373
else this, Array.empty
74+
75+
static member private ProcessPlayerEffect effect (engine: GameEngine) =
76+
match effect with
77+
| PlayerEffect.Update player -> { engine with Player = player }, Array.empty
78+
| PlayerEffect.Die ->
79+
{ engine with GameEngine.Player.Velocity = Vector.Zero }, [| PlaySound SoundType.LifeLost |]
80+
// TODO[#26]: Lose a life: keep lives in the player object and add updated player to PlayerEffect.Die
81+
// TODO[#26]: Player sprite should be replaced with explosion for a while
82+
// TODO[#26]: Investigate how shot cooldown and direction should behave on resurrect: are they reset or not?
83+
// TODO[#27]: Investigate if enemy collision should stop the player from moving

O21.Game/Engine/GameField.fs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type Point =
99
| Point of int * int
1010

1111
static member (+) (Point(x1, y1), Vector(x2, y2)): Point = Point(x1 + x2, y1 + y2)
12+
static member (-) (Point(x1, y1), Vector(x2, y2)): Point = Point(x1 - x2, y1 - y2)
1213

1314
member this.X: int = let (Point(x, _)) = this in x
1415
member this.Y: int = let (Point(_, y)) = this in y
@@ -21,14 +22,16 @@ and [<Struct>] Vector =
2122

2223
member this.X: int = let (Vector(x, _)) = this in x
2324
member this.Y: int = let (Vector(_, y)) = this in y
25+
26+
static member Zero: Vector = Vector(0, 0)
2427

2528
[<Struct>]
2629
type Box =
2730
{ TopLeft: Point; Size: Vector }
2831

29-
member this.TopRight: Point = this.TopLeft + Vector(this.Size.X, 0)
30-
member this.BottomLeft: Point = this.TopLeft + Vector(0, this.Size.Y)
31-
member this.BottomRight: Point = this.TopLeft + this.Size
32+
member this.TopRight: Point = this.TopLeft + Vector(this.Size.X - 1, 0)
33+
member this.BottomLeft: Point = this.TopLeft + Vector(0, this.Size.Y - 1)
34+
member this.BottomRight: Point = this.TopLeft + this.Size - Vector(1, 1)
3235

3336
[<Struct>]
3437
type HorizontalDirection =
@@ -61,5 +64,7 @@ type ObliqueDirection = {
6164
[<Struct; RequireQualifiedAccess>]
6265
type Collision =
6366
| None
67+
/// Means the object is completely out of bounds, i.e. not visible on the game field.
6468
| OutOfBounds
65-
| TouchesBrick
69+
/// At least one pixel of the object intersects with a brick.
70+
| CollidesBrick

O21.Game/Engine/Geometry.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ let CheckCollision (level: Level) (box: Box): Collision =
2828
|| isBrickCell box.TopRight
2929
|| isBrickCell box.BottomLeft
3030
|| isBrickCell box.BottomRight
31-
then Collision.TouchesBrick
31+
then Collision.CollidesBrick
3232
else Collision.None

O21.Game/Scenes/PlayScene.fs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,14 @@ type PlayScene = {
8484
this.Camera.zoom <- (GetScreenHeight() |> float32) / (GameRules.GameScreenHeight |> float32)
8585
let cameraTargetX = ((GetScreenWidth() |> float32) - (GameRules.GameScreenWidth |> float32) * this.Camera.zoom) / -2f / this.Camera.zoom
8686
this.Camera.target <- System.Numerics.Vector2(cameraTargetX, 0f)
87+
// TODO[#26]: Merge ApplyCommands and Update into single function on GameEngine
8788
let game, effects = state.Game |> InputProcessor.ProcessKeys input this.HUD
88-
89-
let state = { state with Game = game.Update time }
89+
let game', effects' = game.Update time
90+
let state = { state with Game = game' }
91+
let allEffects = Seq.concat [| effects; effects' |]
9092
let sounds =
9193
state.SoundsToStartPlaying +
92-
(effects |> Seq.map(fun (PlaySound s) -> s) |> Set.ofSeq)
94+
(allEffects |> Seq.map(fun (PlaySound s) -> s) |> Set.ofSeq)
9395
let navigationEvent =
9496
if this.HUD.Lives < 0 then
9597
// TODO[#32]: Should be handled by the game engine

O21.Game/U95/Sound.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ type SoundType =
1717
| GameStarted = 0
1818
| GameOver = 1
1919
| NewRecord = 2
20-
| LifeTaken = 3
21-
| LifebuoyTaken = 4
22-
| TreasureTaken = 5
20+
| LifePickedUp = 3
21+
| LifebuoyPickedUp = 4
22+
| TreasurePickedUp = 5
2323
| ItemDestroyed = 6
2424
| Shot = 7
2525
| StationaryEnemyDestroyed = 8

O21.Tests/GameEngineTests.fs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ let private frameUp time =
1414
let mutable currentTime = time
1515
fun (gameEngine: GameEngine) ->
1616
let newTime = { Total = currentTime.Total + 0.1; Delta = 0.1f }
17-
gameEngine.Update newTime
17+
let gameEngine, _ = gameEngine.Update newTime
18+
gameEngine
1819

1920
let private frameUpN time n gameEngine =
2021
let mutable newTime = time
@@ -50,10 +51,10 @@ module Movement =
5051
let ``GameEngine reacts to the speed change``(): unit =
5152
let gameEngine = newEngine
5253
let frameUp = frameUp timeZero
53-
Assert.Equal(Point(0, 0), gameEngine.Player.TopLeft)
54+
Assert.Equal(GameRules.PlayerStartingPosition, gameEngine.Player.TopLeft)
5455
let gameEngine, _ = gameEngine.ApplyCommand <| VelocityDelta(Vector(1, 0))
5556
let gameEngine = frameUp gameEngine
56-
Assert.Equal(Point(1, 0), gameEngine.Player.TopLeft)
57+
Assert.Equal(GameRules.PlayerStartingPosition + Vector(1, 0), gameEngine.Player.TopLeft)
5758

5859
module Shooting =
5960

@@ -85,6 +86,30 @@ module Shooting =
8586
let gameEngine, _ = gameEngine.ApplyCommand Shoot
8687
Assert.Equal(bulletCount + 1, gameEngine.Bullets.Length)
8788

89+
module Player =
90+
[<Fact>]
91+
let ``Moving towards a brick kills the player``(): unit =
92+
let level = {
93+
LevelMap = [|
94+
[| Empty; Brick 0 |]
95+
|]
96+
}
97+
let player = {
98+
TopLeft = Point(GameRules.BrickSize.X - GameRules.PlayerSize.X, 0)
99+
Velocity = Vector(0, 0)
100+
Direction = HorizontalDirection.Right
101+
ShotCooldown = 0
102+
}
103+
let player' = player.Update(level, 1)
104+
Assert.Equal(PlayerEffect.Update player, player')
105+
106+
let player = {
107+
player with
108+
Velocity = Vector(1, 0)
109+
}
110+
let player' = player.Update(level, 1)
111+
Assert.Equal(PlayerEffect.Die, player')
112+
88113
module ParticleSystem =
89114

90115
[<Fact>]
@@ -168,4 +193,4 @@ module Geometry =
168193
Assert.Equal(Collision.None, CheckCollision level box1)
169194

170195
let box2 = { TopLeft = Point(GameRules.BrickSize.X, 0); Size = Vector(1, 1) }
171-
Assert.Equal(Collision.TouchesBrick, CheckCollision level box2)
196+
Assert.Equal(Collision.CollidesBrick, CheckCollision level box2)

0 commit comments

Comments
 (0)