├── shatter.gif ├── data ├── sound │ ├── boop.ogg │ └── CREDITS.md ├── texture │ ├── mini64.png │ └── CREDITS.md └── config │ ├── sound.ini │ ├── score.ini │ ├── end.ini │ ├── mini64.ini │ ├── paddle.ini │ ├── ball.ini │ ├── walls.ini │ ├── sparks.ini │ ├── shatter.ini │ └── blocks.ini ├── src ├── dune └── shatter.ml ├── .ocamlformat ├── .gitignore ├── Makefile ├── dune-project ├── shatter.opam ├── README.md └── LICENSE.md /shatter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcarty/shatter/HEAD/shatter.gif -------------------------------------------------------------------------------- /data/sound/boop.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcarty/shatter/HEAD/data/sound/boop.ogg -------------------------------------------------------------------------------- /data/texture/mini64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcarty/shatter/HEAD/data/texture/mini64.png -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (public_name shatter) 3 | (promote (until-clean)) 4 | (libraries orx)) 5 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.24.1 2 | parse-docstrings = true 3 | wrap-comments = true 4 | indicate-multiline-delimiters = closing-on-separate-line 5 | -------------------------------------------------------------------------------- /data/sound/CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits for third party assets 2 | 3 | ## boop.ogg 4 | 5 | - CC0 license 6 | - 7 | -------------------------------------------------------------------------------- /data/texture/CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits for third party assets 2 | 3 | ## mini64.png 4 | 5 | - Kenney Mini font 6 | - CC0 license 7 | - 8 | -------------------------------------------------------------------------------- /data/config/sound.ini: -------------------------------------------------------------------------------- 1 | [Boop] 2 | Sound = boop.ogg 3 | KeepInCache = true; <= This indicates orx to load the associated sound file once for all and to keep it in memory 4 | Pitch = 0.9 ~ 1.1 5 | Volume = 0.9 ~ 1.1 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OCaml files 2 | _build/ 3 | 4 | # Temporarily vendored libraries 5 | vendor/ 6 | 7 | # Orx logs 8 | *.log 9 | 10 | # Editor files 11 | .vscode/ 12 | *.swp 13 | 14 | # Libraries and executables 15 | *.exe 16 | *.dylib 17 | *.so 18 | *.dll 19 | -------------------------------------------------------------------------------- /data/config/score.ini: -------------------------------------------------------------------------------- 1 | [ScoreObject] 2 | ParentCamera = MainCamera 3 | UseParentSpace = position 4 | Position = (-0.488, -0.49, 0.1) 5 | Scale = 0.8 6 | Graphic = @ 7 | Text = @ 8 | Pivot = top left 9 | Font = mini64 10 | RGB = (0, 255, 255) 11 | TrackList = ScoreUpdateTrack 12 | 13 | [ScoreUpdateTrack] 14 | 0 = > Get Runtime Score # 15 | Object.SetText ^ "Score: <" 16 | Loop = true 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default 2 | default: build 3 | 4 | .PHONY: init 5 | init: 6 | cp ${ORX}/lib/dynamic/liborx* src/ 7 | 8 | .PHONY: build 9 | build: 10 | dune build src/shatter.exe 11 | 12 | .PHONY: exec 13 | exec: build 14 | ./src/shatter.exe 15 | 16 | .PHONY: check 17 | check: 18 | dune build @check 19 | 20 | .PHONY: watch 21 | watch: 22 | dune build @check -w 23 | 24 | .PHONY: clean 25 | clean: 26 | dune clean 27 | -------------------------------------------------------------------------------- /data/config/end.ini: -------------------------------------------------------------------------------- 1 | [EndText] 2 | ParentCamera = MainCamera 3 | UseParentSpace = position 4 | Position = (0, 0, 0.1) 5 | Scale = 1.0 6 | Graphic = @ 7 | Text = @ 8 | Pivot = center 9 | Font = mini64 10 | RGB = (255, 0, 255) 11 | TrackList = EndTextUpdateTrack 12 | 13 | [EndTextUpdateTrack] 14 | 0 = > Get BlockSpawner TotalObject # 15 | > Get Runtime Score # 16 | Object.SetText ^ "Cleared with < of < points" 17 | Loop = false -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | 3 | (name shatter) 4 | 5 | (license MIT) 6 | 7 | (maintainers "Hezekiah M. Carty ") 8 | 9 | (authors "Hezekiah M. Carty ") 10 | 11 | (source 12 | (github hcarty/shatter)) 13 | 14 | (generate_opam_files true) 15 | 16 | (package 17 | (name shatter) 18 | (synopsis "A block breaking game") 19 | (depends 20 | orx 21 | (dune 22 | (>= 2.0)) 23 | (ocaml 24 | (>= 4.10.0)))) 25 | -------------------------------------------------------------------------------- /data/config/mini64.ini: -------------------------------------------------------------------------------- 1 | [mini64] 2 | CharacterList = " !""$',-.0123456789:?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 3 | CharacterWidthList = 16 # 16 # 31 # 39 # 16 # 16 # 31 # 16 # 39 # 24 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 16 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 16 # 31 # 39 # 31 # 47 # 39 # 39 # 39 # 39 # 39 # 39 # 31 # 39 # 47 # 47 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 39 # 31 # 39 # 39 # 16 # 31 # 39 # 23 # 47 # 39 # 39 # 39 # 39 # 39 # 39 # 31 # 39 # 47 # 47 # 39 # 39 # 39 4 | CharacterHeight = 64.000000 5 | CharacterSpacing = (2, 2, 0) 6 | Texture = mini64.png -------------------------------------------------------------------------------- /shatter.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "A block breaking game" 4 | maintainer: ["Hezekiah M. Carty "] 5 | authors: ["Hezekiah M. Carty "] 6 | license: "MIT" 7 | homepage: "https://github.com/hcarty/shatter" 8 | bug-reports: "https://github.com/hcarty/shatter/issues" 9 | depends: [ 10 | "orx" 11 | "dune" {>= "2.0"} 12 | "ocaml" {>= "4.10.0"} 13 | ] 14 | build: [ 15 | ["dune" "subst"] {pinned} 16 | [ 17 | "dune" 18 | "build" 19 | "-p" 20 | name 21 | "-j" 22 | jobs 23 | "@install" 24 | "@runtest" {with-test} 25 | "@doc" {with-doc} 26 | ] 27 | ] 28 | dev-repo: "git+https://github.com/hcarty/shatter.git" 29 | -------------------------------------------------------------------------------- /data/config/paddle.ini: -------------------------------------------------------------------------------- 1 | ; Define the paddle for the game engine 2 | 3 | [PaddleObject] 4 | Graphic = PlayerPaddleGraphic 5 | Color = (255, 255, 0) 6 | Size = (200, 20, 0) 7 | Position = (0, 350, 0) 8 | Pivot = center 9 | ID = PlayerPaddle 10 | TrackList = StoreID 11 | Body = PaddleBody 12 | 13 | [PlayerPaddleGraphic] 14 | Texture = pixel 15 | Pivot = center 16 | 17 | [PaddleBody] 18 | Dynamic = true 19 | LinearDamping = 50.0 20 | AngularDamping = 100.0 21 | PartList = PaddleBodyPart 22 | 23 | [PaddleBodyPart] 24 | Type = box 25 | Solid = true 26 | Restitution = 1.5 27 | Friction = 1.0 28 | SelfFlags = paddle 29 | CheckMask = ball # wall 30 | 31 | ; General information about the paddle 32 | 33 | [Paddle] 34 | Speed = 1500.0 35 | -------------------------------------------------------------------------------- /data/config/ball.ini: -------------------------------------------------------------------------------- 1 | ; Define the ball for the game engine 2 | 3 | [BallObject] 4 | Graphic = BallGraphic 5 | Size = (35, 35, 0) 6 | Scale = 0.5 7 | Speed = (0, -250, 0) 8 | Position = (0, 300, 0) 9 | Pivot = center 10 | ID = Ball 11 | TrackList = StoreID 12 | Body = BallBody 13 | 14 | [BallGraphic] 15 | Texture = pixel 16 | Pivot = center 17 | 18 | [BallBody] 19 | Dynamic = true 20 | HighSpeed = true 21 | LinearDamping = 0.0 22 | AngularDamping = 3.0 23 | PartList = BallBodyPart 24 | 25 | [BallBodyPart] 26 | Type = box 27 | Solid = true 28 | Density = 3.0 29 | Restitution = 1.0 30 | Friction = 10.0 31 | SelfFlags = ball 32 | CheckMask = paddle # wall # block 33 | 34 | ; General information about the ball 35 | 36 | [Ball] 37 | TargetSpeed = 600.0 38 | -------------------------------------------------------------------------------- /data/config/walls.ini: -------------------------------------------------------------------------------- 1 | [WallsSceneObject] 2 | ChildList = LeftWallObject # RightWallObject # TopWallObject # BottomWallObject 3 | 4 | [WallGraphic] 5 | Texture = pixel 6 | 7 | [WallObject] 8 | Graphic = WallGraphic 9 | Body = WallBody 10 | 11 | [WallBody] 12 | Dynamic = false 13 | PartList = WallBodyPart 14 | 15 | [WallBodyPart] 16 | Type = box 17 | Solid = true 18 | Friction = 0.0 19 | SelfFlags = wall 20 | CheckMask = paddle # ball # spark 21 | 22 | [LeftWallObject@WallObject] 23 | Position = (-600, -450, 0) 24 | Scale = (10, 1210, 0) 25 | 26 | [RightWallObject@WallObject] 27 | Position = (590, -450, 0) 28 | Scale = (10, 1210, 0) 29 | 30 | [TopWallObject@WallObject] 31 | Position = (-600, -450, 0) 32 | Scale = (1210, 10, 0) 33 | 34 | [BottomWallObject@WallObject] 35 | Position = (-600, 440, 0) 36 | Scale = (1210, 10, 0) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shatter - a block breaking game 2 | 3 | Shatter is a block breaking game written in OCaml using the 4 | [Orx](https://orx-project.org/) game engine. 5 | 6 | The game was written primarily to play with visual effects in Orx. 7 | 8 | ![Gameplay example](shatter.gif) 9 | 10 | ## Building and running the game 11 | 12 | 1. Install Orx, following the instructions on the [wiki](https://wiki.orx-project.org/). 13 | 1. You will need opam and OCaml version 4.10.0 or newer. 14 | 1. Pin [ocaml-orx](https://github.com/orx/ocaml-orx) with opam: 15 | ``` 16 | opam pin add orx git+https://github.com/orx/ocaml-orx.git#master 17 | ``` 18 | 1. Build and run the game with `dune` 19 | ``` 20 | dune exec src/shatter.exe` 21 | ``` 22 | 23 | ## Controls 24 | 25 | - Left/right arrows or `A`/`D` keys to move the paddle 26 | - `Esc`ape key to quit 27 | -------------------------------------------------------------------------------- /data/config/sparks.ini: -------------------------------------------------------------------------------- 1 | [CollisionSparksObject] 2 | Spawner = SparkSpawner 3 | 4 | [SparkSpawner] 5 | Object = SparkObject 6 | WaveSize = 10 7 | WaveDelay = 0.0 8 | TotalObject = 10 9 | 10 | [SparkGraphic] 11 | Texture = pixel 12 | Pivot = center 13 | 14 | [SparkObject] 15 | Graphic = SparkGraphic 16 | Speed = (-500, -500, 0) ~ (500, 500, 0) 17 | Color = (255, 0, 0) ~ (255, 255, 0) 18 | Rotation = 0 ~ 90 19 | AngularVelocity = -90 ~ 90 20 | LifeTime = 2 ~ 4 21 | Scale = 1 ~ 5 22 | Body = SparkBody 23 | FXList = SparkFadeFX 24 | 25 | [SparkBody] 26 | Dynamic = true 27 | PartList = SparkBodyPart 28 | CustomGravity = (0, 1000, 0) 29 | 30 | [SparkBodyPart] 31 | Type = sphere 32 | Solid = true 33 | Density = 0.1 34 | Restitution = 0.5 35 | Friction = 0.2 36 | SelfFlags = spark 37 | CheckMask = block # wall # paddle 38 | 39 | [SparkFadeFX] 40 | SlotList = @ 41 | Type = alpha 42 | StartValue = 0 43 | EndValue = -1 44 | StartTime = 1 45 | EndTime = @SparkObject.LifeTime 46 | Curve = linear -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hezekiah M. Carty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /data/config/shatter.ini: -------------------------------------------------------------------------------- 1 | ; Basic engine display setup (screen/window, game camera) 2 | 3 | [Display] 4 | Title = Shatter 5 | FullScreen = false 6 | Decoration = false 7 | Smoothing = true 8 | VSync = true 9 | 10 | [Viewport] 11 | Camera = MainCamera 12 | BackgroundColor = (0, 0, 0) 13 | 14 | [MainCamera] 15 | GroupList = default # blocks 16 | FrustumWidth = 1200 17 | FrustumHeight = 900 18 | FrustumFar = 20 19 | FrustumNear = 0 20 | Position = (0, 0, -5) 21 | 22 | ; Game assets 23 | [Resource] 24 | Texture = data/texture 25 | Sound = data/sound 26 | 27 | ; Player input 28 | 29 | [MainInput] 30 | KEY_ESCAPE = Quit 31 | KEY_LEFT = Left 32 | KEY_RIGHT = Right 33 | KEY_A = Left 34 | KEY_D = Right 35 | 36 | [Input] 37 | SetList = MainInput 38 | 39 | ; Main game loop clock 40 | 41 | [MainClock] 42 | Frequency = 120 43 | 44 | ; Objects in the game world 45 | 46 | @paddle.ini@ 47 | @ball.ini@ 48 | @walls.ini@ 49 | @blocks.ini@ 50 | @sparks.ini@ 51 | @score.ini@ 52 | @sound.ini@ 53 | @end.ini@ 54 | 55 | ; Fonts 56 | 57 | @mini64.ini@ 58 | 59 | ; Tracks to automatically set values directly from within Orx 60 | 61 | [StoreID] 62 | 0 = > Object.GetName ^ # 63 | > Get < ID # 64 | Set Runtime < ^ 65 | Immediate = true 66 | -------------------------------------------------------------------------------- /data/config/blocks.ini: -------------------------------------------------------------------------------- 1 | ; Define the blocks for the game engine 2 | 3 | [BlockSpawner] 4 | Spawner = @ 5 | Object = BlockObject 6 | TotalObject = 50 7 | WaveSize = 50 8 | WaveDelay = 0 9 | CleanOnDelete = false 10 | LifeTime = spawn 11 | 12 | [BlockObject] 13 | Graphic = BlockGraphic 14 | Size = (70, 45, 0) 15 | Color = (255, 0, 0) 16 | Alpha = 0.75 17 | Position = (-525, -350, 0) ~ (90, 60) ~ (575, 0, 0) 18 | Rotation = -10 ~ 10 19 | Pivot = center 20 | Body = BlockBody 21 | Group = blocks 22 | 23 | [BlockGraphic] 24 | Texture = pixel 25 | Pivot = center 26 | 27 | [BlockBody] 28 | Dynamic = true 29 | LinearDamping = 1.0 30 | AngularDamping = 100.0 31 | PartList = BlockBodyPart 32 | 33 | [BlockBodyPart] 34 | Type = box 35 | Solid = true 36 | Density = 1 37 | Restitution = 1 38 | Friction = 0.2 39 | SelfFlags = block 40 | CheckMask = ball # spark 41 | 42 | [DisappearFX] 43 | SlotList = DesaturateFX # FadeFX 44 | 45 | [DesaturateFX] 46 | Type = hsv 47 | StartValue = (0, 0, 0) 48 | EndValue = (0, -1, 0) 49 | StartTime = 0 50 | EndTime = 0.25 51 | Curve = linear 52 | 53 | [FadeFX] 54 | Type = alpha 55 | StartValue = 0 56 | EndValue = -1 57 | StartTime = 0 58 | EndTime = 1.0 59 | Curve = linear 60 | 61 | [BlockDisappearance] 62 | CustomGravity = (0, 0, 0) 63 | LifeTime = @FadeFX.EndTime 64 | -------------------------------------------------------------------------------- /src/shatter.ml: -------------------------------------------------------------------------------- 1 | module Runtime = struct 2 | let section = "Runtime" 3 | 4 | module Block_spawner = struct 5 | let key = "BlockSpawnerGUID" 6 | 7 | let set spawner = 8 | Orx.Config.(set set_guid) ~section ~key (Orx.Object.to_guid spawner) 9 | 10 | let no_more_blocks = 11 | let spawner_finished = ref false in 12 | fun () -> 13 | let all_blocks_spawned = 14 | if !spawner_finished then true 15 | else 16 | let block_spawner = 17 | Orx.Config.(get get_guid) ~section ~key |> Orx.Object.of_guid 18 | in 19 | spawner_finished := Option.is_none block_spawner; 20 | !spawner_finished 21 | in 22 | if all_blocks_spawned then 23 | match (Orx.Object.get_group (Group "blocks")) () with 24 | | Nil -> true 25 | | Cons _ -> false 26 | else false 27 | end 28 | 29 | module Entity = struct 30 | type t = Paddle | Ball 31 | 32 | let to_key : t -> string = function 33 | | Paddle -> "PlayerPaddle" 34 | | Ball -> "Ball" 35 | 36 | let get_by_guid key = 37 | Orx.Config.(get get_guid) ~section ~key 38 | |> Orx.Object.of_guid |> Option.get 39 | 40 | let get what = get_by_guid (to_key what) 41 | 42 | let get_speed what = 43 | match what with 44 | | Paddle -> Orx.Config.(get get_float) ~section:"Paddle" ~key:"Speed" 45 | | Ball -> Orx.Config.(get get_float) ~section:"Ball" ~key:"TargetSpeed" 46 | end 47 | 48 | module Game_over = struct 49 | let key = "GameOver" 50 | let set () = Orx.Config.(set set_bool) ~section ~key:"GameOver" true 51 | let is_game_over () = Orx.Config.(get get_bool) ~section ~key:"GameOver" 52 | end 53 | 54 | module Score = struct 55 | let key = "Score" 56 | let get () = Orx.Config.(get get_int) ~section ~key 57 | 58 | let set score = 59 | if not (Game_over.is_game_over ()) then 60 | Orx.Config.(set set_int) ~section ~key score 61 | 62 | let increment_score () = set (get () + 1) 63 | let bottom_wall_hit () = set (get () - 5) 64 | end 65 | end 66 | 67 | module Input = struct 68 | let check_player () = 69 | let paddle = Runtime.Entity.get Paddle in 70 | let paddle_speed = Runtime.Entity.get_speed Paddle in 71 | let right_speed = Orx.Vector.make ~x:paddle_speed ~y:0.0 ~z:0.0 in 72 | let left_speed = Orx.Vector.mulf right_speed ~-.1.0 in 73 | if Orx.Input.is_active "Left" then Orx.Object.set_speed paddle left_speed; 74 | if Orx.Input.is_active "Right" then Orx.Object.set_speed paddle right_speed 75 | end 76 | 77 | module Stabilize = struct 78 | module Paddle = struct 79 | let stabilize_rotation obj target = 80 | let rotation = Orx.Object.get_rotation obj in 81 | let diff = rotation -. target in 82 | if Float.abs diff < 0.1 then () 83 | else 84 | let torque = 5.0 *. if diff < 0.0 then 1.0 else ~-.1.0 in 85 | Orx.Object.apply_torque obj torque 86 | 87 | let stabilize_y_position obj target = 88 | let y = Orx.Object.get_world_position obj |> Orx.Vector.get_y in 89 | let diff = target -. y in 90 | if Float.abs diff < 10.0 then () 91 | else 92 | let force_y = diff in 93 | Orx.Object.apply_force obj (Orx.Vector.make ~x:0.0 ~y:force_y ~z:0.0) 94 | 95 | let stabilize () = 96 | let paddle = Runtime.Entity.get Paddle in 97 | let original_position_y = 98 | Orx.Config.(get get_vector) ~section:"PaddleObject" ~key:"Position" 99 | |> Orx.Vector.get_y 100 | in 101 | stabilize_rotation paddle 0.0; 102 | stabilize_y_position paddle original_position_y 103 | end 104 | 105 | module Ball = struct 106 | let get_stabilized_ball_direction ball = 107 | let speed = Orx.Object.get_speed ball in 108 | let direction = Orx.Vector.normalize speed in 109 | let x = Orx.Vector.get_x direction in 110 | let y = Orx.Vector.get_y direction in 111 | let () = 112 | if x = 0.0 || y >= x then () 113 | else 114 | let y' = if y = 0.0 then 0.5 else Float.abs x *. (y /. Float.abs y) in 115 | Orx.Vector.set_y direction y'; 116 | Orx.Vector.normalize' ~target:direction direction 117 | in 118 | direction 119 | 120 | let stabilize () = 121 | let ball = Runtime.Entity.get Ball in 122 | let direction = get_stabilized_ball_direction ball in 123 | let target_magnitude = Runtime.Entity.get_speed Ball in 124 | let corrected_speed = Orx.Vector.mulf direction target_magnitude in 125 | Orx.Object.set_speed ball corrected_speed 126 | end 127 | 128 | let run () = 129 | Paddle.stabilize (); 130 | Ball.stabilize () 131 | end 132 | 133 | module Sound = struct 134 | let boop (o : Orx.Object.t) = Orx.Object.add_sound_exn o "Boop" 135 | end 136 | 137 | module Collision = struct 138 | let find_colliders name event payload = 139 | let sender_name = 140 | Orx.Physics_event.get_sender_part payload |> Orx.Body_part.get_name 141 | in 142 | let recipient_name = 143 | Orx.Physics_event.get_recipient_part payload |> Orx.Body_part.get_name 144 | in 145 | let is_sender = String.equal sender_name name in 146 | let is_recipient = String.equal recipient_name name in 147 | let sender = Orx.Event.get_sender_object event in 148 | let recipient = Orx.Event.get_recipient_object event in 149 | if is_sender then (sender, recipient) 150 | else if is_recipient then (recipient, sender) 151 | else (None, None) 152 | 153 | let find_spark event (payload : Orx.Physics_event.payload) = 154 | let name = "SparkBodyPart" in 155 | fst (find_colliders name event payload) 156 | 157 | let find_block event (payload : Orx.Physics_event.payload) = 158 | let name = "BlockBodyPart" in 159 | fst (find_colliders name event payload) 160 | 161 | let find_ball event (payload : Orx.Physics_event.payload) = 162 | let name = "BallBodyPart" in 163 | fst (find_colliders name event payload) 164 | 165 | let find_bottom_wall event (payload : Orx.Physics_event.payload) = 166 | let name = "WallBodyPart" in 167 | match find_colliders name event payload with 168 | | None, _ -> None 169 | | Some _, None -> None 170 | | (Some wall as o), Some collider -> 171 | let wall_is_bottom = 172 | String.equal (Orx.Object.get_name wall) "BottomWallObject" 173 | in 174 | let collider_is_ball = 175 | String.equal (Orx.Object.get_name collider) "BallObject" 176 | in 177 | if wall_is_bottom && collider_is_ball then o else None 178 | 179 | let add_sparks payload = 180 | let contact_position = Orx.Physics_event.get_position payload in 181 | let sparks = Orx.Object.create_from_config_exn "CollisionSparksObject" in 182 | Orx.Object.set_position sparks contact_position 183 | 184 | let disable_block event payload = 185 | match (find_block event payload, find_ball event payload) with 186 | | None, _ | _, None -> () 187 | | Some block, Some _ball -> 188 | Runtime.Score.increment_score (); 189 | (* Disable collisions with the block so that it can't have any more 190 | influence on the ball or other world objects *) 191 | let body = Orx.Object.get_structure block Body |> Option.get in 192 | Seq.iter 193 | (fun part -> Orx.Body_part.set_self_flags part 0) 194 | (Orx.Body.get_parts body); 195 | (* Gravity has power over the blocks now *) 196 | let custom_gravity = 197 | Orx.Config.(get get_vector) 198 | ~section:"BlockDisappearance" ~key:"CustomGravity" 199 | in 200 | Orx.Object.set_custom_gravity block (Some custom_gravity); 201 | (* Make the block disappear *) 202 | let life_time = 203 | Orx.Config.(get get_float) 204 | ~section:"BlockDisappearance" ~key:"LifeTime" 205 | in 206 | Orx.Object.set_life_time block life_time; 207 | Orx.Object.add_unique_fx_exn block "DisappearFX" 208 | 209 | let check_bottom_wall event payload = 210 | match find_bottom_wall event payload with 211 | | None -> () 212 | | Some _wall -> Runtime.Score.bottom_wall_hit () 213 | 214 | let play_boop event event_payload = 215 | match find_ball event event_payload with 216 | | None -> () 217 | | Some ball -> Sound.boop ball 218 | 219 | let callback (event : Orx.Event.t) (physics_event : Orx.Physics_event.t) 220 | (payload : Orx.Physics_event.payload) = 221 | match physics_event with 222 | | Contact_remove -> Ok () 223 | | Contact_add -> 224 | disable_block event payload; 225 | check_bottom_wall event payload; 226 | let spark = find_spark event payload in 227 | if Option.is_some spark then 228 | (* Friends don't let sparks create sparks *) 229 | Ok () 230 | else ( 231 | add_sparks payload; 232 | play_boop event payload; 233 | Ok () 234 | ) 235 | end 236 | 237 | let spawner_key = "BlockSpawnerGUID" 238 | 239 | let init () = 240 | (* Setup the scene *) 241 | let _viewport = Orx.Viewport.create_from_config_exn "Viewport" in 242 | let _player = Orx.Object.create_from_config_exn "PaddleObject" in 243 | let _ball = Orx.Object.create_from_config_exn "BallObject" in 244 | let _walls = Orx.Object.create_from_config_exn "WallsSceneObject" in 245 | let _score = Orx.Object.create_from_config_exn "ScoreObject" in 246 | let block_spawner = Orx.Object.create_from_config_exn "BlockSpawner" in 247 | 248 | Runtime.Block_spawner.set block_spawner; 249 | 250 | Orx.Config.(set set_int) ~section:"Runtime" ~key:"Score" 0; 251 | 252 | (* Setup callbacks *) 253 | Orx.Event.add_handler Physics Collision.callback; 254 | 255 | Ok () 256 | 257 | let run () = 258 | if Orx.Input.is_active "Quit" then 259 | (* Return an error to indicate that it's time to quit the engine *) 260 | Orx.Status.error 261 | else ( 262 | (* Just keep going *) 263 | Input.check_player (); 264 | Stabilize.run (); 265 | let game_over = Runtime.Game_over.is_game_over () in 266 | let no_more_blocks = Runtime.Block_spawner.no_more_blocks () in 267 | ( if (not game_over) && no_more_blocks then 268 | let (_ : Orx.Object.t) = Orx.Object.create_from_config_exn "EndText" in 269 | Runtime.Game_over.set () 270 | ); 271 | Orx.Status.ok 272 | ) 273 | 274 | let () = 275 | (* Start the main game engine loop *) 276 | Orx.Main.start ~config_dir:"data/config" ~init ~run "shatter" 277 | --------------------------------------------------------------------------------