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.

318 lines
11 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
4 years ago
  1. package wow.doge.mygame
  2. import akka.actor.typed.ActorSystem
  3. import akka.actor.typed.Scheduler
  4. import akka.actor.typed.SpawnProtocol
  5. import akka.util.Timeout
  6. import cats.effect.concurrent.Deferred
  7. import com.jme3.app.state.AppStateManager
  8. import com.jme3.asset.AssetManager
  9. import com.jme3.asset.plugins.ZipLocator
  10. import com.jme3.bullet.BulletAppState
  11. import com.jme3.bullet.control.BetterCharacterControl
  12. import com.jme3.input.InputManager
  13. import com.jme3.renderer.Camera
  14. import com.jme3.renderer.RenderManager
  15. import com.jme3.renderer.ViewPort
  16. import com.jme3.scene.Node
  17. import com.jme3.scene.control.AbstractControl
  18. import com.softwaremill.macwire._
  19. import com.softwaremill.tagging._
  20. import io.odin.Logger
  21. import monix.bio.Fiber
  22. import monix.bio.IO
  23. import monix.bio.Task
  24. import monix.execution.exceptions.DummyException
  25. import scalafx.scene.control.TextArea
  26. import wow.doge.mygame.executors.Schedulers
  27. import wow.doge.mygame.game.GameApp
  28. import wow.doge.mygame.game.GameAppActor
  29. import wow.doge.mygame.game.GameAppTags
  30. import wow.doge.mygame.game.entities.EntityIds
  31. import wow.doge.mygame.game.entities.NpcActorSupervisor
  32. import wow.doge.mygame.game.entities.NpcMovementActor
  33. import wow.doge.mygame.game.entities.PlayerController
  34. import wow.doge.mygame.game.entities.PlayerControllerTags
  35. import wow.doge.mygame.game.subsystems.input.GameInputHandler
  36. import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
  37. import wow.doge.mygame.implicits._
  38. import wow.doge.mygame.launcher.Launcher
  39. import wow.doge.mygame.launcher.Launcher.LauncherResult
  40. import wow.doge.mygame.math.ImVector3f
  41. import wow.doge.mygame.subsystems.events.EventsModule
  42. import wow.doge.mygame.subsystems.events.PlayerEvent
  43. import wow.doge.mygame.subsystems.events.TickEvent
  44. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  45. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  46. import wow.doge.mygame.utils.AkkaUtils
  47. import wow.doge.mygame.utils.GenericConsoleStream
  48. import wow.doge.mygame.utils.IOUtils
  49. import EventsModule.GameEventBus
  50. class MainApp(
  51. logger: Logger[Task],
  52. gameApp: GameApp,
  53. implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
  54. jmeThread: monix.execution.Scheduler,
  55. schedulers: Schedulers,
  56. consoleStream: GenericConsoleStream[TextArea]
  57. )(implicit
  58. @annotation.unused timeout: Timeout,
  59. @annotation.unused scheduler: Scheduler
  60. ) {
  61. lazy val scriptSystemInit =
  62. new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init
  63. def gameInit: Task[Fiber[Throwable, Unit]] =
  64. for {
  65. eventsModule <- Task(new EventsModule(spawnProtocol))
  66. playerEventBus <- eventsModule.playerEventBusTask
  67. mainEventBus <- eventsModule.mainEventBusTask
  68. tickEventBus <- eventsModule.tickEventBusTask
  69. gameAppActor <- AkkaUtils.spawnActorL2(
  70. GameAppActor.Props(tickEventBus).behavior,
  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. playerEventBus: GameEventBus[PlayerEvent],
  145. tickEventBus: GameEventBus[TickEvent],
  146. inputManager: InputManager,
  147. assetManager: AssetManager,
  148. stateManager: AppStateManager,
  149. camera: Camera,
  150. viewPort: ViewPort,
  151. enqueueR: Function1[() => Unit, Unit],
  152. rootNode: Node @@ GameAppTags.RootNode,
  153. bulletAppState: BulletAppState
  154. )(implicit
  155. @annotation.unused timeout: Timeout,
  156. @annotation.unused scheduler: Scheduler
  157. ) {
  158. lazy val physicsSpace = bulletAppState.physicsSpace
  159. def init(
  160. appScheduler: monix.execution.Scheduler
  161. // consoleStream: GenericConsoleStream[TextArea]
  162. ) =
  163. for {
  164. _ <- loggerL.info("Initializing Systems")
  165. _ <- Task(
  166. assetManager.registerLocator(
  167. os.rel / "assets" / "town.zip",
  168. classOf[ZipLocator]
  169. )
  170. )
  171. _ <- loggerL.info("test")
  172. // _ <- Task(consoleStream.println("text"))
  173. _ <- DefaultGameLevel(assetManager, viewPort)
  174. .addToGame(
  175. rootNode,
  176. bulletAppState.physicsSpace
  177. )
  178. .executeOn(appScheduler)
  179. _ <- createPlayerController(appScheduler)
  180. .absorbWith(e => DummyException("boom"))
  181. .onErrorRestart(3)
  182. _ <- wire[GameInputHandler.Props].begin.onErrorRestart(3)
  183. // johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler)
  184. // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  185. // _ <- (johnActor !! NpcActorSupervisor.Move(
  186. // ImVector3f(-80, 0, 100)
  187. // )).executeAsync.delayExecution(2.seconds)
  188. _ <-
  189. IOUtils
  190. .toIO(
  191. rootNode
  192. .observableBreadthFirst()
  193. .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
  194. .completedL
  195. )
  196. .executeOn(appScheduler)
  197. .startAndForget
  198. } yield ()
  199. def createPlayerController(
  200. appScheduler: monix.execution.Scheduler
  201. ): IO[PlayerController.Error, Unit] = {
  202. val playerPos = ImVector3f.ZERO
  203. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  204. lazy val playerPhysicsControl =
  205. PlayerController.Defaults.defaultPlayerPhysicsControl
  206. .taggedWith[PlayerControllerTags.PlayerTag]
  207. // lazy val camNode =
  208. // PlayerController.Defaults
  209. // .defaultCamerNode(camera, playerPos)
  210. // .taggedWith[PlayerControllerTags.PlayerCameraNode]
  211. lazy val mbPlayerNode = PlayerController.Defaults
  212. .defaultPlayerNode(
  213. assetManager,
  214. modelPath,
  215. playerPos,
  216. // camNode
  217. playerPhysicsControl
  218. )
  219. lazy val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
  220. .taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
  221. for {
  222. playerNode <- IO.fromEither(mbPlayerNode)
  223. _ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
  224. .onErrorHandleWith(e =>
  225. IO.raiseError(PlayerController.GenericError(e.getMessage()))
  226. )
  227. camNode <- IO(
  228. PlayerController.Defaults
  229. .defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
  230. ).onErrorHandleWith(e =>
  231. IO.raiseError(PlayerController.GenericError(e.getMessage()))
  232. ).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
  233. // _ <- Task {
  234. // val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
  235. // chaseCam.setSmoothMotion(false)
  236. // chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
  237. // chaseCam
  238. // }
  239. // .onErrorHandleWith(e =>
  240. // IO.raiseError(PlayerController.GenericError(e.getMessage()))
  241. // )
  242. _ <- wire[PlayerController.Props].create
  243. } yield ()
  244. }
  245. def createTestNpc(
  246. appScheduler: monix.execution.Scheduler,
  247. npcName: String
  248. ) =
  249. // : IO[PlayerController.Error, Unit] =
  250. {
  251. val initialPos = ImVector3f(100, 0, 0)
  252. // val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  253. lazy val npcPhysicsControl =
  254. new BetterCharacterControl(1f, 2.1f, 10f)
  255. // .withJumpForce(ImVector3f(0, 5f, 0))
  256. // val npcMovementActor = AkkaUtils.spawnActorL2(
  257. // new NpcMovementActor2.Props(
  258. // initialPos,
  259. // tickEventBus,
  260. // npcPhysicsControl
  261. // ).behavior,
  262. // s"${npcName}-npcMovementActor"
  263. // )
  264. lazy val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
  265. assetManager,
  266. initialPos,
  267. npcPhysicsControl,
  268. npcName
  269. )
  270. val npcActorTask = AkkaUtils.spawnActorL2(
  271. NpcActorSupervisor
  272. .Props(
  273. new NpcMovementActor.Props(
  274. enqueueR,
  275. initialPos,
  276. // tickEventBus,
  277. npcPhysicsControl
  278. ).behavior,
  279. npcName,
  280. initialPos
  281. )
  282. .behavior,
  283. s"${npcName}-npcActorSupervisor"
  284. )
  285. // .taggedWith[PlayerControllerTags.PlayerTag]
  286. for {
  287. npcNode <- IO.fromEither(mbNpcNode)
  288. npcActor <- npcActorTask
  289. _ <- IO {
  290. physicsSpace += npcPhysicsControl
  291. physicsSpace += npcNode
  292. rootNode += npcNode
  293. }
  294. } yield (npcActor)
  295. }
  296. }
  297. class FollowControl(playerNode: Node) extends AbstractControl {
  298. override def controlUpdate(tpf: Float): Unit = {
  299. this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
  300. }
  301. override def controlRender(
  302. rm: RenderManager,
  303. vp: ViewPort
  304. ): Unit = {}
  305. }