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.

294 lines
9.7 KiB

4 years ago
3 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
3 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 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.Resource
  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.NpcMovementActor
  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.EventsModule
  43. import wow.doge.mygame.subsystems.events.PlayerEvent
  44. import wow.doge.mygame.subsystems.events.TickEvent
  45. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  46. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  47. import wow.doge.mygame.utils.AkkaUtils
  48. import wow.doge.mygame.utils.GenericConsoleStream
  49. import cats.syntax.eq._
  50. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  51. class MainApp(
  52. logger: Logger[Task],
  53. jmeThread: monix.execution.Scheduler,
  54. schedulers: Schedulers,
  55. consoleStream: GenericConsoleStream[TextArea]
  56. )(implicit
  57. spawnProtocol: ActorSystem[SpawnProtocol.Command],
  58. @annotation.unused timeout: Timeout,
  59. @annotation.unused scheduler: Scheduler
  60. ) {
  61. val scriptSystemInit =
  62. new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
  63. val eventsModule = new EventsModule(spawnProtocol)
  64. def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
  65. GameApp.resource(logger, jmeThread, schedulers).evalMap {
  66. case gameApp -> gameAppFib =>
  67. for {
  68. playerEventBus <- eventsModule.playerEventBusTask
  69. mainEventBus <- eventsModule.mainEventBusTask
  70. tickEventBus <- eventsModule.tickEventBusTask
  71. gameAppActor <- AkkaUtils.spawnActorL(
  72. GameAppActor.Props(tickEventBus).behavior,
  73. "gameAppActor"
  74. )
  75. _ <- gameAppActor !! GameAppActor.Start
  76. inputManager <- gameApp.inputManager
  77. assetManager <- gameApp.assetManager
  78. stateManager <- gameApp.stateManager
  79. camera <- gameApp.camera
  80. rootNode <- gameApp.rootNode
  81. enqueueR <- Task(gameApp.enqueue _)
  82. viewPort <- gameApp.viewPort
  83. _ <- logger.info("before")
  84. // jfxUI <- gameApp.jfxUI
  85. consoleTextArea <- Task(new TextArea {
  86. text = "hello \n"
  87. editable = false
  88. wrapText = true
  89. // maxHeight = 150
  90. // maxWidth = 300
  91. })
  92. // _ <- Task(consoleStream := consoleTextArea)
  93. // _ <- Task(jfxUI += consoleTextArea)
  94. _ <- logger.info("after")
  95. bulletAppState <- Task(new BulletAppState())
  96. _ <- Task(stateManager.attach(bulletAppState))
  97. _ <- logger.info("Initializing console stream")
  98. _ <-
  99. wire[MainAppDelegate]
  100. .init(gameApp.scheduler)
  101. .executeOn(gameApp.scheduler)
  102. } yield gameAppFib
  103. }
  104. val program = for {
  105. scriptSystem <- scriptSystemInit
  106. launchSignal <- Deferred[Task, Launcher.LauncherResult]
  107. launcher <- new Launcher.Props(schedulers, launchSignal).create
  108. launchResult <- launcher.init.use(_ => launchSignal.get)
  109. _ <-
  110. /**
  111. * User chose to quit
  112. */
  113. if (launchResult === LauncherResult.Exit)
  114. logger.info("Exiting")
  115. /**
  116. * User chose launch. Wait for game window to close
  117. */
  118. else
  119. gameInit.use(_.join)
  120. } yield ()
  121. }
  122. /**
  123. * Class with all dependencies in one place for easy wiring
  124. */
  125. class MainAppDelegate(
  126. gameApp: GameApp,
  127. loggerL: Logger[Task],
  128. playerEventBus: GameEventBus[PlayerEvent],
  129. tickEventBus: GameEventBus[TickEvent],
  130. inputManager: InputManager,
  131. assetManager: AssetManager,
  132. stateManager: AppStateManager,
  133. camera: Camera,
  134. viewPort: ViewPort,
  135. enqueueR: Function1[() => Unit, Unit],
  136. rootNode: Node @@ GameAppTags.RootNode,
  137. bulletAppState: BulletAppState
  138. )(implicit
  139. spawnProtocol: ActorSystem[SpawnProtocol.Command],
  140. @annotation.unused timeout: Timeout,
  141. @annotation.unused scheduler: Scheduler
  142. ) {
  143. val physicsSpace = bulletAppState.physicsSpace
  144. def init(
  145. appScheduler: monix.execution.Scheduler
  146. // consoleStream: GenericConsoleStream[TextArea]
  147. ) =
  148. for {
  149. _ <- loggerL.info("Initializing Systems")
  150. _ <- Task(
  151. assetManager.registerLocator(
  152. os.rel / "assets" / "town.zip",
  153. classOf[ZipLocator]
  154. )
  155. )
  156. _ <- loggerL.info("test")
  157. // _ <- Task(consoleStream.println("text"))
  158. _ <- DefaultGameLevel(assetManager, viewPort)
  159. .addToGame(
  160. rootNode,
  161. bulletAppState.physicsSpace
  162. )
  163. _ <- createPlayerController(appScheduler)
  164. .absorbWith(e => DummyException("boom"))
  165. .onErrorRestart(3)
  166. _ <- wire[GameInputHandler.Props].begin.onErrorRestart(3)
  167. // johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler)
  168. // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  169. // _ <-
  170. // (johnActor !! NpcActorSupervisor.Move(
  171. // ImVector3f(-30, 0, 10)
  172. // )).executeAsync
  173. // .delayExecution(2.seconds)
  174. // _ <-
  175. // IOUtils
  176. // .toIO(
  177. // rootNode
  178. // .observableBreadthFirst()
  179. // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
  180. // .completedL
  181. // )
  182. // .executeOn(appScheduler)
  183. // .startAndForget
  184. } yield ()
  185. def createPlayerController(
  186. appScheduler: monix.execution.Scheduler
  187. ): IO[PlayerController.Error, Unit] = {
  188. val playerPos = ImVector3f.ZERO
  189. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  190. val playerPhysicsControl =
  191. PlayerController.Defaults.defaultPlayerPhysicsControl
  192. .taggedWith[PlayerControllerTags.PlayerTag]
  193. // lazy val camNode =
  194. // PlayerController.Defaults
  195. // .defaultCamerNode(camera, playerPos)
  196. // .taggedWith[PlayerControllerTags.PlayerCameraNode]
  197. val mbPlayerNode = PlayerController.Defaults
  198. .defaultPlayerNode(
  199. assetManager,
  200. modelPath,
  201. playerPos,
  202. // camNode
  203. playerPhysicsControl
  204. )
  205. val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
  206. .taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
  207. for {
  208. playerNode <- IO.fromEither(mbPlayerNode)
  209. _ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
  210. .onErrorHandleWith(e =>
  211. IO.raiseError(PlayerController.GenericError(e.getMessage()))
  212. )
  213. camNode <- IO(
  214. PlayerController.Defaults
  215. .defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
  216. ).onErrorHandleWith(e =>
  217. IO.raiseError(PlayerController.GenericError(e.getMessage()))
  218. ).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
  219. // _ <- Task {
  220. // val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
  221. // chaseCam.setSmoothMotion(false)
  222. // chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
  223. // chaseCam
  224. // }
  225. // .onErrorHandleWith(e =>
  226. // IO.raiseError(PlayerController.GenericError(e.getMessage()))
  227. // )
  228. _ <- wire[PlayerController.Props].create
  229. } yield ()
  230. }
  231. def createTestNpc(
  232. appScheduler: monix.execution.Scheduler,
  233. npcName: String
  234. ) = {
  235. val initialPos = ImVector3f(50, 5, 0)
  236. val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
  237. // (1f, 2.1f, 10f)
  238. .withJumpForce(ImVector3f(0, 5f, 0))
  239. val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
  240. assetManager,
  241. initialPos,
  242. npcPhysicsControl,
  243. npcName
  244. )
  245. val npcActorTask = AkkaUtils.spawnActorL(
  246. NpcActorSupervisor
  247. .Props(
  248. new NpcMovementActor.Props(
  249. enqueueR,
  250. initialPos,
  251. // tickEventBus,
  252. npcPhysicsControl
  253. ).behavior,
  254. npcName,
  255. initialPos
  256. )
  257. .behavior,
  258. s"${npcName}-npcActorSupervisor"
  259. )
  260. // .taggedWith[PlayerControllerTags.PlayerTag]
  261. for {
  262. npcNode <- IO.fromEither(mbNpcNode)
  263. npcActor <- npcActorTask
  264. _ <- IO {
  265. physicsSpace += npcPhysicsControl
  266. physicsSpace += npcNode
  267. rootNode += npcNode
  268. }
  269. } yield npcActor
  270. }
  271. }
  272. class FollowControl(playerNode: Node) extends AbstractControl {
  273. override def controlUpdate(tpf: Float): Unit = {
  274. this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
  275. }
  276. override def controlRender(
  277. rm: RenderManager,
  278. vp: ViewPort
  279. ): Unit = {}
  280. }