main.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. -- Copyright (c) 2012-2026 Daniele Bartolini et al.
  2. -- SPDX-License-Identifier: MIT
  3. require "core/game/camera"
  4. Game = Game or {
  5. physics_world = nil,
  6. render_world = nil,
  7. scene_graph = nil,
  8. camera = nil,
  9. world_gui = nil,
  10. cursor_disabled = false,
  11. -- Player movement.
  12. character_unit = nil,
  13. gravity = 6*9.8,
  14. vertical_speed = 0,
  15. walk_speed = 8,
  16. run_multiplier = 1.8,
  17. -- Bullets management.
  18. max_bullets = 20,
  19. num_bullets = 0,
  20. bullets_head = 1,
  21. bullets_tail = 1,
  22. bullets = {},
  23. ages = {},
  24. -- Actionable door.
  25. door_open = false,
  26. -- Debug.
  27. debug_graphics = false,
  28. debug_physics = false,
  29. }
  30. GameBase.game = Game
  31. GameBase.game_level = "levels/mover"
  32. GameBase.show_help = true
  33. GameBase.show_build = true
  34. function Game.level_loaded()
  35. Game.physics_world = World.physics_world(GameBase.world)
  36. Game.render_world = World.render_world(GameBase.world)
  37. Game.scene_graph = World.scene_graph(GameBase.world)
  38. Game.camera = FPSCamera(GameBase.world, GameBase.camera_unit)
  39. Game.world_gui = World.create_world_gui(GameBase.world)
  40. -- Create character controller.
  41. Game.character_unit = World.unit_by_name(GameBase.world, "character")
  42. -- Link camera to mover.
  43. if Game.character_unit then
  44. local character_transform = SceneGraph.instance(Game.scene_graph, Game.character_unit)
  45. local camera_transform = SceneGraph.instance(Game.scene_graph, GameBase.camera_unit)
  46. if SceneGraph.parent(Game.scene_graph, camera_transform) == nil then
  47. SceneGraph.link(Game.scene_graph, character_transform, camera_transform)
  48. end
  49. end
  50. -- Create a pool of reusable bullets.
  51. for i=1, Game.max_bullets do
  52. local bullet_unit = World.spawn_unit(GameBase.world, "units/bullet/bullet", Vector3(0, 0, -100))
  53. local bullet_actor = PhysicsWorld.actor_instance(Game.physics_world, bullet_unit)
  54. -- Avoid overlapping bullets interaction.
  55. PhysicsWorld.actor_disable_gravity(Game.physics_world, bullet_actor)
  56. PhysicsWorld.actor_disable_collision(Game.physics_world, bullet_actor)
  57. Game.bullets[i] = bullet_unit
  58. Game.ages[i] = 0
  59. end
  60. -- Debug.
  61. PhysicsWorld.enable_debug_drawing(Game.physics_world, Game.debug_physics)
  62. RenderWorld.enable_debug_drawing(Game.render_world, Game.debug_graphics)
  63. end
  64. function Game.update(dt)
  65. -- Toggle cursor state and quit logic.
  66. if Keyboard.any_pressed() then
  67. if Keyboard.pressed(Keyboard.button_id("escape")) then
  68. if Game.cursor_disabled then
  69. Window.set_cursor_mode("normal")
  70. Game.cursor_disabled = false
  71. else
  72. Device.quit()
  73. end
  74. else
  75. Window.set_cursor_mode("disabled")
  76. Game.cursor_disabled = true
  77. end
  78. end
  79. local camera_world = Game.camera:world_pose()
  80. World.set_listener_pose(GameBase.world, camera_world)
  81. -- Recycle used bullets after they expire.
  82. local head = Game.bullets_head
  83. for i=1, Game.num_bullets do
  84. if Game.ages[head] < 4 then
  85. Game.ages[head] = Game.ages[head] + dt
  86. head = head % Game.max_bullets + 1
  87. else
  88. local bullet_unit = Game.bullets[Game.bullets_head]
  89. local bullet_actor = PhysicsWorld.actor_instance(Game.physics_world, bullet_unit)
  90. -- Avoid overlapping bullets interaction.
  91. PhysicsWorld.actor_disable_gravity(Game.physics_world, bullet_actor)
  92. PhysicsWorld.actor_disable_collision(Game.physics_world, bullet_actor)
  93. -- Bullets may have been recycled while still in motion.
  94. PhysicsWorld.actor_set_linear_velocity(Game.physics_world, bullet_actor, Vector3.zero())
  95. PhysicsWorld.actor_set_angular_velocity(Game.physics_world, bullet_actor, Vector3.zero())
  96. -- Teleport bullet somewhere player can't see it.
  97. PhysicsWorld.actor_teleport_world_position(Game.physics_world, bullet_actor, Vector3(0, 0, -100))
  98. Game.ages[Game.bullets_head] = 0
  99. Game.bullets_head = Game.bullets_head % Game.max_bullets + 1
  100. Game.num_bullets = Game.num_bullets - 1
  101. end
  102. end
  103. -- Shoot a bullet when left mouse button is pressed.
  104. if Mouse.pressed(Mouse.button_id("left")) then
  105. local tr = SceneGraph.instance(Game.scene_graph, Game.camera:unit())
  106. local pos = SceneGraph.world_position(Game.scene_graph, tr)
  107. local dir = Matrix4x4.y(SceneGraph.local_pose(Game.scene_graph, tr))
  108. Vector3.normalize(dir)
  109. if Game.num_bullets == Game.max_bullets then
  110. -- Empty magazine. Play a sad sound maybe.
  111. else
  112. local bullet_unit = Game.bullets[Game.bullets_tail]
  113. local bullet_actor = PhysicsWorld.actor_instance(Game.physics_world, bullet_unit)
  114. -- Gravity and collision is disabled when the bullet gets recycled. Re-enable both.
  115. PhysicsWorld.actor_enable_gravity(Game.physics_world, bullet_actor)
  116. PhysicsWorld.actor_enable_collision(Game.physics_world, bullet_actor)
  117. -- Move the bullet in front of player and shoot it forward.
  118. PhysicsWorld.actor_teleport_world_position(Game.physics_world, bullet_actor, pos + dir * 1.5)
  119. PhysicsWorld.actor_add_impulse(Game.physics_world, bullet_actor, dir * 50.0)
  120. World.play_sound(GameBase.world, "sfx/shoot", false, 0.6, 150.0)
  121. Game.bullets_tail = Game.bullets_tail % Game.max_bullets + 1
  122. Game.num_bullets = Game.num_bullets + 1
  123. end
  124. end
  125. -- Actionable door.
  126. if Keyboard.pressed(Keyboard.button_id("e")) then
  127. local camera_transform = SceneGraph.instance(Game.scene_graph, Game.camera:unit())
  128. local camera_position = SceneGraph.world_position(Game.scene_graph, camera_transform)
  129. local camera_forward = Matrix4x4.y(SceneGraph.world_pose(Game.scene_graph, camera_transform))
  130. local hit, hit_pos, normal, time, unit, actor = PhysicsWorld.cast_ray(Game.physics_world, camera_position, camera_forward, 100)
  131. if hit then
  132. local door_button = World.unit_by_name(GameBase.world, "raycast_door_button")
  133. -- If we hit the door button and we are not too far, open/close the door.
  134. if unit == door_button and Vector3.distance(camera_position, hit_pos) < 10 then
  135. local door = World.unit_by_name(GameBase.world, "raycast_door")
  136. local door_transform = SceneGraph.instance(Game.scene_graph, door)
  137. local door_position = SceneGraph.local_position(Game.scene_graph, door_transform)
  138. local door_dx = 2.3
  139. local new_door_position = door_position + Vector3(Game.door_open and door_dx or -door_dx, 0, 0)
  140. SceneGraph.set_local_position(Game.scene_graph, door_transform, new_door_position)
  141. Game.door_open = not Game.door_open
  142. end
  143. end
  144. end
  145. local dx = Keyboard.button(Keyboard.button_id("d")) - Keyboard.button(Keyboard.button_id("a"))
  146. local dy = Keyboard.button(Keyboard.button_id("w")) - Keyboard.button(Keyboard.button_id("s"))
  147. if Game.character_unit then
  148. local mover = PhysicsWorld.mover_instance(Game.physics_world, Game.character_unit)
  149. -- Player mover.
  150. local coll_sides = PhysicsWorld.mover_collides_sides(Game.physics_world, mover)
  151. local coll_up = PhysicsWorld.mover_collides_up(Game.physics_world, mover)
  152. local coll_down = PhysicsWorld.mover_collides_down(Game.physics_world, mover)
  153. local camera_transform = SceneGraph.instance(Game.scene_graph, Game.camera:unit())
  154. local camera_forward = Matrix4x4.y(SceneGraph.local_pose(Game.scene_graph, camera_transform))
  155. local camera_right = Matrix4x4.x(SceneGraph.local_pose(Game.scene_graph, camera_transform))
  156. local delta = Vector3(camera_forward.x, camera_forward.y, 0) * dy
  157. + Vector3(camera_right.x, camera_right.y, 0) * dx
  158. if Vector3.length(delta) > 0.0001 then
  159. Vector3.normalize(delta)
  160. end
  161. local jump_pressed = Keyboard.pressed(Keyboard.button_id("space"))
  162. local run_pressed = Keyboard.button(Keyboard.button_id("shift_left"))
  163. local speed = Game.walk_speed
  164. if run_pressed ~= 0 then
  165. speed = speed * Game.run_multiplier
  166. end
  167. delta = delta * speed
  168. if jump_pressed and coll_down then
  169. Game.vertical_speed = Game.gravity * 0.25
  170. else
  171. if coll_down then
  172. Game.vertical_speed = -0.1
  173. else
  174. Game.vertical_speed = math.max(-50, Game.vertical_speed - Game.gravity * dt)
  175. end
  176. end
  177. delta.z = Game.vertical_speed
  178. PhysicsWorld.mover_move(Game.physics_world, mover, delta*(1/120))
  179. -- Copy mover position to character position.
  180. local mover_pos = PhysicsWorld.mover_position(Game.physics_world, mover)
  181. local character_transform = SceneGraph.instance(Game.scene_graph, Game.character_unit)
  182. assert(character_transform)
  183. SceneGraph.set_local_position(Game.scene_graph, character_transform, mover_pos)
  184. else
  185. Game.camera:move(dt, dx, dy)
  186. end
  187. -- Update camera.
  188. if Game.cursor_disabled then
  189. local cursor_delta = Mouse.axis(Mouse.axis_id("cursor_delta"))
  190. Game.camera:rotate(dt, cursor_delta.x, cursor_delta.y)
  191. end
  192. -- Toggle help.
  193. if Keyboard.pressed(Keyboard.button_id("f1")) then
  194. GameBase.show_help = not GameBase.show_help
  195. end
  196. -- Draw 3D labels.
  197. local camera_view = Matrix4x4.y(camera_world)
  198. local camera_right = Matrix4x4.x(camera_world)
  199. for k, v in pairs({ label_slopes = "Slopes", label_moving_platforms = "Moving Platforms", label_trigger = "Trigger", label_raycast = "Raycast" }) do
  200. local label_unit = World.unit_by_name(GameBase.world, k)
  201. if label_unit then
  202. local label_transform = SceneGraph.instance(Game.scene_graph, label_unit)
  203. local label_position = SceneGraph.local_position(Game.scene_graph, label_transform)
  204. local font_resource = "core/game/hud/debug"
  205. local font_size = 1.5
  206. local label_extents = Gui.text_extents(Game.world_gui, font_size, v, font_resource)
  207. local label_pose = Matrix4x4.from_axes(camera_right
  208. , Vector3(0, 0, 1)
  209. , camera_view
  210. , label_position - camera_right * label_extents.x * 0.5
  211. )
  212. Gui.text_3d(Game.world_gui, label_pose, Vector3.zero(), font_size, v, font_resource)
  213. end
  214. end
  215. -- Draw crosshair.
  216. local win_w, win_h = Device.resolution()
  217. Gui.rect(GameBase.screen_gui, Vector2(win_w/2, win_h/2), Vector2(4, 4), Color4(255, 0, 255, 255))
  218. GameBase.draw_help({{ key = "f1", desc = "Toggle help" },
  219. { key = "w/a/s/d", desc = "Walk" },
  220. { key = "shift", desc = "Run" },
  221. { key = "space", desc = "Jump" },
  222. { key = "e", desc = "Interact" },
  223. { key = "left click", desc = "Shoot" },
  224. { key = "z", desc = "Toggle physics debug" },
  225. { key = "x", desc = "Toggle graphics debug" },
  226. { key = "esc", desc = "Quit or Enable cursor" }}
  227. , "Crown Physics Sample"
  228. )
  229. -- Toggle debug drawing.
  230. if Keyboard.released(Keyboard.button_id("z")) then
  231. Game.debug_physics = not Game.debug_physics
  232. PhysicsWorld.enable_debug_drawing(Game.physics_world, Game.debug_physics)
  233. end
  234. if Keyboard.released(Keyboard.button_id("x")) then
  235. Game.debug_graphics = not Game.debug_graphics
  236. RenderWorld.enable_debug_drawing(Game.render_world, Game.debug_graphics)
  237. end
  238. end
  239. function Game.render(dt)
  240. end
  241. function Game.shutdown()
  242. end