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.

300 lines
10 KiB

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