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.

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