Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
9.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame
  2. import akka.actor.typed.ActorRef
  3. import akka.actor.typed.ActorSystem
  4. import akka.actor.typed.Scheduler
  5. import akka.actor.typed.SpawnProtocol
  6. import akka.util.Timeout
  7. import cats.effect.concurrent.Deferred
  8. import com.jme3.app.state.AppStateManager
  9. import com.jme3.asset.AssetManager
  10. import com.jme3.asset.plugins.ZipLocator
  11. import com.jme3.bullet.BulletAppState
  12. import com.jme3.input.InputManager
  13. import com.jme3.renderer.Camera
  14. import com.jme3.renderer.ViewPort
  15. import com.jme3.scene.Node
  16. import com.softwaremill.macwire._
  17. import com.softwaremill.tagging._
  18. import io.odin.Logger
  19. import monix.bio.Fiber
  20. import monix.bio.IO
  21. import monix.bio.Task
  22. import scalafx.scene.control.TextArea
  23. import wow.doge.mygame.executors.Schedulers
  24. import wow.doge.mygame.game.GameApp
  25. import wow.doge.mygame.game.GameAppActor
  26. import wow.doge.mygame.game.GameAppTags
  27. import wow.doge.mygame.game.entities.PlayerController
  28. import wow.doge.mygame.game.entities.PlayerControllerTags
  29. import wow.doge.mygame.game.subsystems.input.GameInputHandler
  30. import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
  31. import wow.doge.mygame.implicits._
  32. import wow.doge.mygame.launcher.Launcher
  33. import wow.doge.mygame.launcher.Launcher.LauncherResult
  34. import wow.doge.mygame.math.ImVector3f
  35. import wow.doge.mygame.subsystems.events.EventBus
  36. import wow.doge.mygame.subsystems.events.EventsModule
  37. import wow.doge.mygame.subsystems.events.PlayerCameraEvent
  38. import wow.doge.mygame.subsystems.events.PlayerMovementEvent
  39. import wow.doge.mygame.subsystems.events.TickEvent
  40. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  41. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  42. import wow.doge.mygame.utils.AkkaUtils
  43. import wow.doge.mygame.utils.GenericConsoleStream
  44. import EventsModule.GameEventBus
  45. import wow.doge.mygame.game.entities.NpcMovementActor2
  46. import wow.doge.mygame.game.entities.NpcActorSupervisor
  47. import monix.execution.exceptions.DummyException
  48. import com.jme3.bullet.control.BetterCharacterControl
  49. class MainApp(
  50. logger: Logger[Task],
  51. gameApp: GameApp,
  52. implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
  53. jmeThread: monix.execution.Scheduler,
  54. schedulers: Schedulers,
  55. consoleStream: GenericConsoleStream[TextArea]
  56. )(implicit
  57. @annotation.unused timeout: Timeout,
  58. @annotation.unused scheduler: Scheduler
  59. ) {
  60. lazy val scriptSystemInit =
  61. new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init
  62. def gameInit: Task[Fiber[Throwable, Unit]] =
  63. for {
  64. eventsModule <- Task(new EventsModule(spawnProtocol))
  65. playerMovementEventBus <- eventsModule.playerMovementEventBusTask
  66. playerCameraEventBus <- eventsModule.playerCameraEventBusTask
  67. mainEventBus <- eventsModule.mainEventBusTask
  68. tickEventBus <- eventsModule.tickEventBusTask
  69. gameAppActor <- AkkaUtils.spawnActorL2(
  70. GameAppActor.Props(tickEventBus).create,
  71. "gameAppActor"
  72. )
  73. _ <- gameAppActor !! GameAppActor.Start
  74. gameAppFib <- gameApp.start.executeOn(jmeThread).start
  75. /**
  76. * schedule a task to run on the JME thread and wait for it's completion
  77. * before proceeding forward, as a signal that the JME thread has been
  78. * initialized, otherwise we'll get NPEs trying to access the fields
  79. * of the game app
  80. */
  81. res <- gameApp.enqueueL(() => "done")
  82. _ <- logger.info(s"Result = $res")
  83. /**
  84. * JME Thread has been initialized at this point. We can now access the
  85. * field of the game application
  86. */
  87. inputManager <- gameApp.inputManager
  88. assetManager <- gameApp.assetManager
  89. stateManager <- gameApp.stateManager
  90. camera <- gameApp.camera
  91. rootNode <- gameApp.rootNode
  92. enqueueR <- Task(gameApp.enqueue _)
  93. viewPort <- gameApp.viewPort
  94. _ <- logger.info("before")
  95. // jfxUI <- gameApp.jfxUI
  96. consoleTextArea <- Task(new TextArea {
  97. text = "hello \n"
  98. editable = false
  99. wrapText = true
  100. // maxHeight = 150
  101. // maxWidth = 300
  102. })
  103. // _ <- Task(consoleStream := consoleTextArea)
  104. // _ <- Task(jfxUI += consoleTextArea)
  105. _ <- logger.info("after")
  106. bulletAppState <- Task(new BulletAppState())
  107. _ <- Task(stateManager.attach(bulletAppState))
  108. _ <- logger.info("Initializing console stream")
  109. _ <- wire[MainAppDelegate].init(gameApp.scheduler)
  110. } yield (gameAppFib)
  111. lazy val program = for {
  112. scriptSystem <- scriptSystemInit
  113. /**
  114. * Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI
  115. * Without this, we get a "Toolkit already initialized" exception. The launch button
  116. * in the launcher completes the signal. The game init process which listens for this
  117. * signal can then continue
  118. */
  119. launchSignal <- Deferred[Task, Launcher.LauncherResult]
  120. launcher <- new Launcher.Props(schedulers, launchSignal).create
  121. cancelToken <- launcher.init()
  122. launchResult <- launchSignal.get
  123. _ <- cancelToken.cancel
  124. _ <-
  125. /**
  126. * User chose to quit
  127. */
  128. if (launchResult == LauncherResult.Exit)
  129. logger.info("Exiting")
  130. /**
  131. * User chose launch. Wait for game window to close
  132. */
  133. else
  134. gameInit.flatMap(_.join)
  135. } yield ()
  136. }
  137. /**
  138. * Class with all dependencies in one place for easy wiring
  139. */
  140. class MainAppDelegate(
  141. gameApp: GameApp,
  142. implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
  143. loggerL: Logger[Task],
  144. playerMovementEventBus: ActorRef[
  145. EventBus.Command[PlayerMovementEvent]
  146. ],
  147. playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]],
  148. tickEventBus: GameEventBus[TickEvent],
  149. inputManager: InputManager,
  150. assetManager: AssetManager,
  151. stateManager: AppStateManager,
  152. camera: Camera,
  153. viewPort: ViewPort,
  154. enqueueR: Function1[() => Unit, Unit],
  155. rootNode: Node @@ GameAppTags.RootNode,
  156. bulletAppState: BulletAppState
  157. )(implicit
  158. @annotation.unused timeout: Timeout,
  159. @annotation.unused scheduler: Scheduler
  160. ) {
  161. lazy val physicsSpace = bulletAppState.physicsSpace
  162. def init(
  163. appScheduler: monix.execution.Scheduler
  164. // consoleStream: GenericConsoleStream[TextArea]
  165. ) =
  166. for {
  167. _ <- loggerL.info("Initializing Systems")
  168. _ <- Task(
  169. assetManager.registerLocator(
  170. os.rel / "assets" / "town.zip",
  171. classOf[ZipLocator]
  172. )
  173. )
  174. _ <- loggerL.info("test")
  175. // _ <- Task(consoleStream.println("text"))
  176. _ <- DefaultGameLevel(assetManager, viewPort)
  177. .addToGame(
  178. rootNode,
  179. bulletAppState.physicsSpace
  180. )
  181. .executeOn(appScheduler)
  182. _ <- createPlayerController(appScheduler)
  183. .absorbWith(e => DummyException("boom"))
  184. .onErrorRestart(3)
  185. johnActor <- createNpc(appScheduler, "John").executeOn(appScheduler)
  186. _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  187. _ <- wire[GameInputHandler.Props].begin.onErrorRestart(3)
  188. } yield ()
  189. def createPlayerController(
  190. appScheduler: monix.execution.Scheduler
  191. ): IO[PlayerController.Error, Unit] = {
  192. val playerPos = ImVector3f.ZERO
  193. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  194. val playerPhysicsControl =
  195. PlayerController.Defaults.defaultPlayerPhysicsControl
  196. .taggedWith[PlayerControllerTags.PlayerTag]
  197. val camNode =
  198. PlayerController.Defaults
  199. .defaultCamerNode(camera, playerPos)
  200. .taggedWith[PlayerControllerTags.PlayerCameraNode]
  201. val mbPlayerNode = PlayerController.Defaults
  202. .defaultPlayerNode(
  203. assetManager,
  204. modelPath,
  205. playerPos,
  206. camNode,
  207. playerPhysicsControl
  208. )
  209. for {
  210. playerNode <- IO.fromEither(mbPlayerNode)
  211. _ <- wire[PlayerController.Props].create
  212. } yield ()
  213. }
  214. def createNpc(
  215. appScheduler: monix.execution.Scheduler,
  216. npcName: String
  217. ) =
  218. // : IO[PlayerController.Error, Unit] =
  219. {
  220. val initialPos = ImVector3f(100, 0, 0)
  221. // val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  222. lazy val npcPhysicsControl =
  223. new BetterCharacterControl(1f, 2.1f, 10f)
  224. // .withJumpForce(ImVector3f(0, 5f, 0))
  225. // val npcMovementActor = AkkaUtils.spawnActorL2(
  226. // new NpcMovementActor2.Props(
  227. // initialPos,
  228. // tickEventBus,
  229. // npcPhysicsControl
  230. // ).create,
  231. // s"${npcName}-npcMovementActor"
  232. // )
  233. lazy val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
  234. assetManager,
  235. initialPos,
  236. npcPhysicsControl,
  237. "John"
  238. )
  239. val npcActorTask = AkkaUtils.spawnActorL2(
  240. NpcActorSupervisor
  241. .Props(
  242. new NpcMovementActor2.Props(
  243. enqueueR,
  244. initialPos,
  245. tickEventBus,
  246. npcPhysicsControl
  247. ).create,
  248. npcName,
  249. initialPos
  250. )
  251. .create,
  252. s"${npcName}-npcMovementActorSupervisor"
  253. )
  254. // .taggedWith[PlayerControllerTags.PlayerTag]
  255. for {
  256. npcNode <- IO.fromEither(mbNpcNode)
  257. npcActor <- npcActorTask
  258. _ <- IO {
  259. physicsSpace += npcPhysicsControl
  260. physicsSpace += npcNode
  261. rootNode += npcNode
  262. }
  263. } yield (npcActor)
  264. }
  265. }