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.

325 lines
11 KiB

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