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.

488 lines
16 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
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
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
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
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
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 scala.concurrent.duration._
  3. import akka.actor.typed.ActorRef
  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.asset.plugins.ZipLocator
  10. import com.jme3.bullet.control.BetterCharacterControl
  11. import com.jme3.input.InputManager
  12. import com.jme3.material.Material
  13. import com.jme3.material.MaterialDef
  14. import com.jme3.math.ColorRGBA
  15. import com.jme3.math.FastMath
  16. import com.jme3.math.Quaternion
  17. import com.jme3.math.Vector3f
  18. import com.jme3.renderer.Camera
  19. import com.jme3.renderer.ViewPort
  20. import com.jme3.scene.Node
  21. import com.softwaremill.macwire._
  22. import com.softwaremill.tagging._
  23. import io.odin.Logger
  24. import monix.bio.Fiber
  25. import monix.bio.IO
  26. import monix.bio.Task
  27. import monix.bio.UIO
  28. import monix.eval.Coeval
  29. import monix.execution.exceptions.DummyException
  30. import monix.reactive.Observable
  31. import scalafx.scene.control.TextArea
  32. import wow.doge.mygame.AppError.TimeoutError
  33. import wow.doge.mygame.executors.Schedulers
  34. import wow.doge.mygame.game.GameApp
  35. import wow.doge.mygame.game.GameAppResource
  36. import wow.doge.mygame.game.controls.FollowControl
  37. import wow.doge.mygame.game.entities.CharacterStats
  38. import wow.doge.mygame.game.entities.EntityIds
  39. import wow.doge.mygame.game.entities.NpcActorSupervisor
  40. import wow.doge.mygame.game.entities.NpcMovementActor
  41. import wow.doge.mygame.game.entities.PlayerActorSupervisor
  42. import wow.doge.mygame.game.entities.PlayerController
  43. import wow.doge.mygame.game.subsystems.input.GameInputHandler
  44. import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
  45. import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
  46. import wow.doge.mygame.implicits._
  47. import wow.doge.mygame.launcher.Launcher
  48. import wow.doge.mygame.launcher.Launcher.LauncherResult
  49. import wow.doge.mygame.math.ImVector3f
  50. import wow.doge.mygame.subsystems.events.Event
  51. import wow.doge.mygame.subsystems.events.EventBus
  52. import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
  53. import wow.doge.mygame.subsystems.events.EventsModule
  54. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  55. import wow.doge.mygame.subsystems.events.PlayerCameraEvent
  56. import wow.doge.mygame.subsystems.events.PlayerEvent
  57. import wow.doge.mygame.subsystems.events.PlayerMovementEvent
  58. import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
  59. import wow.doge.mygame.subsystems.events.TickEvent
  60. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  61. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  62. import wow.doge.mygame.types._
  63. import wow.doge.mygame.utils.AkkaUtils
  64. import wow.doge.mygame.utils.GenericConsoleStream
  65. import wow.doge.mygame.utils.IOUtils
  66. import wow.doge.mygame.utils.wrappers.jme.AssetManager
  67. import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
  68. class MainApp(
  69. logger: Logger[Task],
  70. jmeThread: JmeScheduler,
  71. schedulers: Schedulers,
  72. consoleStream: GenericConsoleStream[TextArea]
  73. )(implicit
  74. spawnProtocol: ActorRef[SpawnProtocol.Command],
  75. timeout: Timeout,
  76. scheduler: AkkaScheduler
  77. ) {
  78. implicit val as = scheduler.value
  79. val scriptSystemInit =
  80. new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
  81. val eventsModule = new EventsModule(scheduler, spawnProtocol)
  82. def eval(
  83. tickEventBus: GameEventBus[TickEvent],
  84. gameApp: GameApp,
  85. fib: Fiber[Nothing, Unit]
  86. ) =
  87. for {
  88. // g <- UIO.pure(gameApp)
  89. playerEventBus <- eventsModule.playerEventBus
  90. mainEventBus <- eventsModule.mainEventBus
  91. obs <-
  92. playerEventBus
  93. .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
  94. .onErrorHandleWith(TimeoutError.from)
  95. _ <-
  96. IOUtils
  97. .toIO(
  98. obs
  99. .doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
  100. .completedL
  101. .startAndForget
  102. )
  103. .hideErrors
  104. inputManager <- gameApp.inputManager
  105. assetManager <- UIO.pure(gameApp.assetManager)
  106. camera <- gameApp.camera
  107. rootNode <- UIO.pure(gameApp.rootNode)
  108. enqueueR <- UIO(gameApp.enqueue _)
  109. viewPort <- gameApp.viewPort
  110. physicsSpace <- UIO.pure(gameApp.physicsSpace)
  111. _ <- logger.infoU("before")
  112. // jfxUI <- gameApp.jfxUI
  113. consoleTextArea <- UIO(new TextArea {
  114. text = "hello \n"
  115. editable = false
  116. wrapText = true
  117. // maxHeight = 150
  118. // maxWidth = 300
  119. })
  120. // _ <- Task(consoleStream := consoleTextArea)
  121. // _ <- Task(jfxUI += consoleTextArea)
  122. _ <- logger.infoU("after")
  123. _ <- logger.infoU("Initializing console stream")
  124. _ <-
  125. wire[MainAppDelegate]
  126. .init()
  127. .executeOn(gameApp.scheduler.value)
  128. } yield fib
  129. def gameInit(
  130. tickEventBus: GameEventBus[TickEvent]
  131. ): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
  132. wire[GameAppResource].resource.evalMap {
  133. case Right(gameApp -> gameAppFib) =>
  134. eval(tickEventBus, gameApp, gameAppFib).attempt
  135. case Left(error) => IO.terminate(new Exception(error.toString))
  136. }
  137. val program = for {
  138. // scriptSystem <- scriptSystemInit
  139. launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
  140. launcher <- new Launcher.Props(schedulers.fx, launchSignal).create
  141. launchResult <-
  142. launcher.init
  143. .use(_ => launchSignal.get)
  144. .hideErrors
  145. tickEventBus <-
  146. eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString))
  147. _ <-
  148. /**
  149. * User chose to quit
  150. */
  151. if (launchResult === LauncherResult.Exit)
  152. logger.infoU("Exiting")
  153. /**
  154. * User chose launch. Wait for game window to close
  155. */
  156. else
  157. gameInit(tickEventBus).use {
  158. case Right(fib) => fib.join >> Task.unit
  159. case Left(error) => IO.terminate(new Exception(error.toString))
  160. }.hideErrors
  161. } yield ()
  162. }
  163. /**
  164. * Class with all dependencies in one place for easy wiring
  165. */
  166. class MainAppDelegate(
  167. gameApp: GameApp,
  168. loggerL: Logger[Task],
  169. mainEventBus: GameEventBus[Event],
  170. playerEventBus: GameEventBus[PlayerEvent],
  171. tickEventBus: GameEventBus[TickEvent],
  172. inputManager: InputManager,
  173. assetManager: AssetManager,
  174. physicsSpace: PhysicsSpace,
  175. camera: Camera,
  176. viewPort: ViewPort,
  177. enqueueR: Function1[() => Unit, Unit],
  178. rootNode: RootNode,
  179. schedulers: Schedulers
  180. )(implicit
  181. spawnProtocol: ActorRef[SpawnProtocol.Command],
  182. timeout: Timeout,
  183. scheduler: AkkaScheduler
  184. ) {
  185. implicit val as = scheduler.value
  186. def init(
  187. // appScheduler: monix.execution.Scheduler
  188. // consoleStream: GenericConsoleStream[TextArea]
  189. ): IO[AppError, Unit] =
  190. for {
  191. _ <- loggerL.infoU("Initializing Systems")
  192. _ <- assetManager.registerLocator(
  193. os.rel / "assets" / "town.zip",
  194. classOf[ZipLocator]
  195. )
  196. _ <- loggerL.infoU("test")
  197. // _ <- Task(consoleStream.println("text"))
  198. level <- DefaultGameLevel(assetManager, viewPort)
  199. _ <- level.addToGame(rootNode, physicsSpace)
  200. playerActor <- createPlayerController()
  201. // .onErrorRestart(3)
  202. _ <- wire[GameInputHandler.Props].begin
  203. // .onErrorRestart(3)
  204. johnActor <- createTestNpc("John")
  205. // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  206. // _ <-
  207. // johnActor
  208. // .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
  209. // .delayExecution(2.seconds)
  210. _ <-
  211. rootNode.depthFirstTraversal
  212. .doOnNextF(spat => loggerL.debug(spat.getName).toTask)
  213. .completedL
  214. .toIO
  215. .hideErrors
  216. damageObs <-
  217. mainEventBus
  218. .askL[Observable[DamageEvent]](ObservableSubscription(_))
  219. .onErrorHandleWith(TimeoutError.from)
  220. _ <-
  221. damageObs
  222. .doOnNextF(event =>
  223. (loggerL.debug(s"Received Damage Event $event") >>
  224. (if (event.victimName === "PlayerNode")
  225. // playerActor !! PlayerActorSupervisor.TakeDamage(event.amount)
  226. playerActor.askL(
  227. PlayerActorSupervisor.TakeDamage2(event.amount, _)
  228. )
  229. else IO.unit)).toTask
  230. )
  231. .completedL
  232. .toIO
  233. .hideErrors
  234. .startAndForget
  235. _ <-
  236. Observable
  237. .interval(1.second)
  238. .doOnNextF(_ =>
  239. playerActor
  240. .askL(PlayerActorSupervisor.GetStatus)
  241. .flatMap(s => loggerL.debug(s"Player actor status: $s"))
  242. // .flatMap(s =>
  243. // if (s == Status.Alive)
  244. // playerActor
  245. // .askL(PlayerActorSupervisor.CurrentStats )
  246. // .flatMap(s => loggerL.debug(s"Got state $s"))
  247. // else IO.unit
  248. // )
  249. .toTask
  250. )
  251. // .doOnNextF(_ =>
  252. // playerActor
  253. // .askL(PlayerActorSupervisor.GetStatus )
  254. // .flatMap(s => loggerL.debug(s"Player actor status: $s"))
  255. // .toTask
  256. // )
  257. .completedL
  258. .toIO
  259. .hideErrors
  260. .startAndForget
  261. _ <-
  262. physicsSpace.collisionObservable
  263. // .filter(event =>
  264. // (for {
  265. // nodeA <- event.nodeA
  266. // nodeB <- event.nodeB
  267. // } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
  268. // nodeB.getName === "PlayerNode" && nodeA.getName === "John")
  269. // .getOrElse(false)
  270. // )
  271. // .doOnNextF(event =>
  272. // loggerL
  273. // .debug(s"$event ${event.appliedImpulse()}")
  274. // .toTask
  275. // )
  276. .filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
  277. .filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false))
  278. .doOnNextF(event =>
  279. (for {
  280. victim <- Coeval(for {
  281. nodeA <- event.nodeA
  282. nodeB <- event.nodeB
  283. } yield if (nodeB.getName === "John") nodeA else nodeB)
  284. _ <- Coeval(
  285. victim.foreach { v =>
  286. pprint.log(s"emitted event ${v.getName}")
  287. mainEventBus ! EventBus.Publish(
  288. DamageEvent(
  289. "John",
  290. v.getName,
  291. CharacterStats.DamageHealth(10)
  292. ),
  293. "damageHandler"
  294. )
  295. }
  296. )
  297. } yield ()).void
  298. )
  299. .completedL
  300. .toIO
  301. .hideErrors
  302. .startAndForget
  303. // _ <-
  304. // IOUtils
  305. // .toIO(
  306. // rootNode
  307. // .observableBreadthFirst()
  308. // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
  309. // .completedL
  310. // )
  311. // .executeOn(appScheduler)
  312. // .startAndForget
  313. } yield ()
  314. def createPlayerController(
  315. // appScheduler: monix.execution.Scheduler
  316. ): IO[AppError, PlayerActorSupervisor.Ref] = {
  317. val playerPos = ImVector3f.Zero
  318. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  319. // val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
  320. val playerPhysicsControl =
  321. PlayerController.Defaults.defaultPlayerPhysicsControl
  322. for {
  323. playerModel <-
  324. assetManager
  325. .loadModelAs[Node](modelPath)
  326. .map(_.withRotate(0, FastMath.PI, 0))
  327. .tapEval(m => UIO(m.center()))
  328. .mapError(AppError.AssetManagerError)
  329. playerNode <- UIO(
  330. PlayerController.Defaults
  331. .defaultPlayerNode(
  332. playerPos,
  333. playerModel,
  334. playerPhysicsControl
  335. )
  336. )
  337. cameraPivotNode <- UIO(
  338. new Node(EntityIds.CameraPivot.value)
  339. .withControl(new FollowControl(playerNode))
  340. .taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
  341. )
  342. camNode <- UIO(
  343. PlayerController.Defaults
  344. .defaultCamerNode(camera, playerPos)
  345. .taggedWith[PlayerController.Tags.PlayerCameraNode]
  346. )
  347. playerCameraEvents <-
  348. playerEventBus
  349. .askL[Observable[PlayerCameraEvent]](ObservableSubscription(_))
  350. .onErrorHandleWith(TimeoutError.from)
  351. _ <-
  352. inputManager
  353. .enumAnalogObservable(PlayerCameraInput)
  354. .sample(1.millis)
  355. .scan(new Quaternion) {
  356. case (rotationBuf, action) =>
  357. action.binding match {
  358. // case PlayerCameraEvent.CameraLeft =>
  359. case PlayerCameraInput.CameraRotateLeft =>
  360. // me.Task {
  361. val rot = rotationBuf
  362. .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  363. cameraPivotNode.rotate(rot)
  364. rotationBuf
  365. // }
  366. // case PlayerCameraEvent.CameraRight =>
  367. case PlayerCameraInput.CameraRotateRight =>
  368. // me.Task {
  369. val rot = rotationBuf
  370. .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  371. cameraPivotNode.rotate(rot)
  372. rotationBuf
  373. // }
  374. // case PlayerCameraEvent.CameraMovedUp =>
  375. case PlayerCameraInput.CameraRotateUp =>
  376. // me.Task {
  377. val rot = rotationBuf
  378. .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
  379. cameraPivotNode.rotate(rot)
  380. rotationBuf
  381. // }
  382. // case PlayerCameraEvent.CameraMovedDown =>
  383. case PlayerCameraInput.CameraRotateDown =>
  384. // me.Task {
  385. val rot = rotationBuf
  386. .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
  387. cameraPivotNode.rotate(rot)
  388. rotationBuf
  389. // }
  390. }
  391. }
  392. .completedL
  393. .toIO
  394. .hideErrors
  395. .startAndForget
  396. // _ <-
  397. // Observable
  398. // .interval(10.millis)
  399. // .doOnNextF(_ =>
  400. // Coeval {
  401. // val location = playerNode.getWorldTranslation()
  402. // cameraPivotNode.setLocalTranslation(location)
  403. // }
  404. // )
  405. // .completedL
  406. // .toIO
  407. // .hideErrors
  408. // .startAndForget
  409. sched <- UIO.pure(schedulers.async)
  410. playerActor <- wire[PlayerController.Props].create
  411. obs <-
  412. playerActor
  413. .askL(PlayerActorSupervisor.GetStatsObservable2)
  414. .onErrorHandleWith(TimeoutError.from)
  415. _ <-
  416. obs
  417. .doOnNext(s => loggerL.debug(s"Got state $s").toTask)
  418. .completedL
  419. .toIO
  420. .hideErrors
  421. .startAndForget
  422. } yield playerActor
  423. }
  424. def createTestNpc(
  425. // appScheduler: monix.execution.Scheduler,
  426. npcName: String
  427. ): IO[AppError, NpcActorSupervisor.Ref] = {
  428. val initialPos = ImVector3f(50, 5, 0)
  429. val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
  430. // (1f, 2.1f, 10f)
  431. .withJumpForce(ImVector3f(0, 5f, 0))
  432. val npcActorTask = AkkaUtils.spawnActorL(
  433. new NpcActorSupervisor.Props(
  434. new NpcMovementActor.Props(
  435. enqueueR,
  436. initialPos,
  437. npcName,
  438. npcPhysicsControl
  439. ).behavior,
  440. npcName,
  441. initialPos
  442. ).behavior,
  443. actorName = Some(s"${npcName}-npcActorSupervisor")
  444. )
  445. (for {
  446. materialDef <-
  447. assetManager
  448. .loadAssetAs[MaterialDef](
  449. os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
  450. )
  451. .mapError(AppError.AssetManagerError)
  452. material = new Material(materialDef)
  453. _ = material.setColor("Color", ColorRGBA.Blue)
  454. mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
  455. npcNode = PlayerController.Defaults.defaultNpcNode(
  456. mesh,
  457. initialPos,
  458. npcPhysicsControl,
  459. npcName
  460. )
  461. _ <- (for {
  462. _ <- physicsSpace += npcPhysicsControl
  463. _ <- physicsSpace += npcNode
  464. _ <- rootNode += npcNode
  465. } yield ()).mapError(AppError.AppNodeError)
  466. npcActor <- npcActorTask
  467. } yield npcActor)
  468. }
  469. }