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.

336 lines
11 KiB

4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 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
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 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
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 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
4 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
4 years ago
3 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
4 years ago
3 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
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
  1. package wow.doge.mygame
  2. import java.util.concurrent.TimeoutException
  3. import scala.concurrent.duration._
  4. import akka.actor.typed.ActorRef
  5. import akka.actor.typed.Scheduler
  6. import akka.actor.typed.SpawnProtocol
  7. import akka.util.Timeout
  8. import cats.effect.Resource
  9. import cats.effect.concurrent.Deferred
  10. import cats.syntax.eq._
  11. import com.jme3.asset.plugins.ZipLocator
  12. import com.jme3.bullet.control.BetterCharacterControl
  13. import com.jme3.input.InputManager
  14. import com.jme3.material.Material
  15. import com.jme3.material.MaterialDef
  16. import com.jme3.math.ColorRGBA
  17. import com.jme3.math.FastMath
  18. import com.jme3.renderer.Camera
  19. import com.jme3.renderer.RenderManager
  20. import com.jme3.renderer.ViewPort
  21. import com.jme3.scene.Node
  22. import com.jme3.scene.control.AbstractControl
  23. import com.softwaremill.macwire._
  24. import com.softwaremill.tagging._
  25. import io.odin.Logger
  26. import monix.bio.Fiber
  27. import monix.bio.IO
  28. import monix.bio.Task
  29. import monix.bio.UIO
  30. import monix.eval.Coeval
  31. import monix.reactive.Observable
  32. import scalafx.scene.control.TextArea
  33. import wow.doge.mygame.executors.Schedulers
  34. import wow.doge.mygame.game.GameApp
  35. import wow.doge.mygame.game.GameAppActor
  36. import wow.doge.mygame.game.GameAppResource
  37. import wow.doge.mygame.game.GameAppTags
  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.entities.PlayerControllerTags
  44. import wow.doge.mygame.game.subsystems.input.GameInputHandler
  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.EventBus.ObservableSubscription
  51. import wow.doge.mygame.subsystems.events.EventsModule
  52. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  53. import wow.doge.mygame.subsystems.events.PlayerEvent
  54. import wow.doge.mygame.subsystems.events.PlayerMovementEvent
  55. import wow.doge.mygame.subsystems.events.TickEvent
  56. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  57. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  58. import wow.doge.mygame.utils.AkkaUtils
  59. import wow.doge.mygame.utils.GenericConsoleStream
  60. import wow.doge.mygame.utils.IOUtils
  61. import wow.doge.mygame.utils.wrappers.jme.AppNode2
  62. import wow.doge.mygame.utils.wrappers.jme.AssetManager
  63. import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
  64. class MainApp(
  65. logger: Logger[Task],
  66. jmeThread: monix.execution.Scheduler,
  67. schedulers: Schedulers,
  68. consoleStream: GenericConsoleStream[TextArea]
  69. )(implicit
  70. spawnProtocol: ActorRef[SpawnProtocol.Command],
  71. timeout: Timeout,
  72. scheduler: Scheduler
  73. ) {
  74. val scriptSystemInit =
  75. new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
  76. val eventsModule = new EventsModule(scheduler, spawnProtocol)
  77. class TestClass(
  78. playerEventBus: GameEventBus[PlayerEvent],
  79. tickEventBus: GameEventBus[TickEvent]
  80. )
  81. def eval(gameApp: GameApp, fib: Fiber[Nothing, Unit]) =
  82. for {
  83. // g <- UIO.pure(gameApp)
  84. playerEventBus <- eventsModule.playerEventBus
  85. mainEventBus <- eventsModule.mainEventBus
  86. tickEventBus <- eventsModule.tickEventBus
  87. obs <-
  88. playerEventBus
  89. .askL[Observable[PlayerMovementEvent]](
  90. ObservableSubscription(_)
  91. )
  92. .onErrorHandleWith {
  93. case ex: TimeoutException =>
  94. IO.raiseError(AppError.TimeoutError(ex.getMessage()))
  95. }
  96. _ <-
  97. IOUtils
  98. .toIO(
  99. obs
  100. .doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
  101. .completedL
  102. .startAndForget
  103. )
  104. .hideErrors
  105. inputManager <- gameApp.inputManager
  106. assetManager <- UIO.pure(gameApp.assetManager)
  107. camera <- gameApp.camera
  108. rootNode <- UIO.pure(gameApp.rootNode)
  109. enqueueR <- UIO(gameApp.enqueue _)
  110. viewPort <- gameApp.viewPort
  111. physicsSpace <- UIO.pure(gameApp.physicsSpace)
  112. _ <- logger.infoU("before")
  113. // jfxUI <- gameApp.jfxUI
  114. gameAppActor <- gameApp.spawnGameActor(
  115. GameAppActor.Props(tickEventBus).behavior,
  116. "gameAppActor"
  117. )
  118. _ <- gameAppActor !! GameAppActor.Start
  119. consoleTextArea <- UIO(new TextArea {
  120. text = "hello \n"
  121. editable = false
  122. wrapText = true
  123. // maxHeight = 150
  124. // maxWidth = 300
  125. })
  126. // _ <- Task(consoleStream := consoleTextArea)
  127. // _ <- Task(jfxUI += consoleTextArea)
  128. _ <- logger.infoU("after")
  129. _ <- logger.infoU("Initializing console stream")
  130. _ <-
  131. wire[MainAppDelegate]
  132. .init()
  133. .executeOn(gameApp.scheduler)
  134. } yield fib
  135. def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
  136. wire[GameAppResource].resource.evalMap {
  137. case Right(gameApp -> gameAppFib) =>
  138. eval(gameApp, gameAppFib).attempt
  139. case Left(error) => IO.terminate(new Exception(error.toString))
  140. }
  141. // val x: Task[Unit] = for {
  142. // tickEventBus <- eventsModule.tickEventBusTask
  143. // playerEventBus <- eventsModule.playerEventBusTask
  144. // _ <- UIO(wire[TestClass])
  145. // _ <- gameInit(tickEventBus).use(_.join)
  146. // } yield ()
  147. val program = for {
  148. // scriptSystem <- scriptSystemInit
  149. launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
  150. launcher <- new Launcher.Props(schedulers, launchSignal).create
  151. launchResult <- launcher.init.use(_ => launchSignal.get).hideErrors
  152. _ <-
  153. /**
  154. * User chose to quit
  155. */
  156. if (launchResult === LauncherResult.Exit)
  157. logger.infoU("Exiting")
  158. /**
  159. * User chose launch. Wait for game window to close
  160. */
  161. else
  162. gameInit.use {
  163. case Right(fib) => fib.join >> Task.unit
  164. case Left(error) => IO.terminate(new Exception(error.toString))
  165. }.hideErrors
  166. } yield ()
  167. }
  168. /**
  169. * Class with all dependencies in one place for easy wiring
  170. */
  171. class MainAppDelegate(
  172. gameApp: GameApp,
  173. loggerL: Logger[Task],
  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: AppNode2 @@ GameAppTags.RootNode
  183. )(implicit
  184. spawnProtocol: ActorRef[SpawnProtocol.Command],
  185. timeout: Timeout,
  186. scheduler: Scheduler
  187. ) {
  188. def init(
  189. // appScheduler: monix.execution.Scheduler
  190. // consoleStream: GenericConsoleStream[TextArea]
  191. ): IO[AppError, Unit] =
  192. for {
  193. _ <- loggerL.infoU("Initializing Systems")
  194. _ <- assetManager.registerLocator(
  195. os.rel / "assets" / "town.zip",
  196. classOf[ZipLocator]
  197. )
  198. _ <- loggerL.infoU("test")
  199. // _ <- Task(consoleStream.println("text"))
  200. level <- DefaultGameLevel(assetManager, viewPort)
  201. _ <- level.addToGame(rootNode, physicsSpace)
  202. _ <- createPlayerController()
  203. // .onErrorRestart(3)
  204. _ <- wire[GameInputHandler.Props].begin
  205. // .onErrorRestart(3)
  206. johnActor <- createTestNpc("John")
  207. // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  208. _ <-
  209. johnActor
  210. .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
  211. .delayExecution(2.seconds)
  212. // _ <-
  213. // IOUtils
  214. // .toIO(
  215. // rootNode
  216. // .observableBreadthFirst()
  217. // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
  218. // .completedL
  219. // )
  220. // .executeOn(appScheduler)
  221. // .startAndForget
  222. } yield ()
  223. def createPlayerController(
  224. // appScheduler: monix.execution.Scheduler
  225. ): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = {
  226. val playerPos = ImVector3f.Zero
  227. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  228. val playerPhysicsControl =
  229. PlayerController.Defaults.defaultPlayerPhysicsControl
  230. .taggedWith[PlayerControllerTags.PlayerTag]
  231. for {
  232. playerModel <-
  233. assetManager
  234. .loadModelAs[Node](modelPath)
  235. .map(_.withRotate(0, FastMath.PI, 0))
  236. .mapError(AppError.AssetManagerError)
  237. playerNode <- UIO(
  238. PlayerController.Defaults
  239. .defaultPlayerNode(
  240. playerPos,
  241. playerModel,
  242. playerPhysicsControl
  243. )
  244. )
  245. cameraPivotNode <- UIO(
  246. new Node(EntityIds.CameraPivot.value)
  247. .withControl(new FollowControl(playerNode))
  248. .taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
  249. )
  250. camNode <- UIO(
  251. PlayerController.Defaults
  252. .defaultCamerNode(camera, playerPos)
  253. .taggedWith[PlayerControllerTags.PlayerCameraNode]
  254. )
  255. playerActor <- wire[PlayerController.Props].create
  256. } yield playerActor
  257. }
  258. def createTestNpc(
  259. // appScheduler: monix.execution.Scheduler,
  260. npcName: String
  261. ): IO[AppError, ActorRef[NpcActorSupervisor.Command]] = {
  262. val initialPos = ImVector3f(50, 5, 0)
  263. val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
  264. // (1f, 2.1f, 10f)
  265. .withJumpForce(ImVector3f(0, 5f, 0))
  266. val npcActorTask = AkkaUtils.spawnActorL(
  267. NpcActorSupervisor
  268. .Props(
  269. new NpcMovementActor.Props(
  270. enqueueR,
  271. initialPos,
  272. // tickEventBus,
  273. npcName,
  274. npcPhysicsControl
  275. ).behavior,
  276. npcName,
  277. initialPos
  278. )
  279. .behavior,
  280. s"${npcName}-npcActorSupervisor"
  281. )
  282. (for {
  283. materialDef <-
  284. assetManager
  285. .loadAssetAs[MaterialDef](
  286. os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
  287. )
  288. .mapError(AppError.AssetManagerError)
  289. material = new Material(materialDef)
  290. _ = material.setColor("Color", ColorRGBA.Blue)
  291. mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
  292. npcNode = PlayerController.Defaults.defaultNpcNode(
  293. mesh,
  294. initialPos,
  295. npcPhysicsControl,
  296. npcName
  297. )
  298. _ <- (for {
  299. _ <- physicsSpace += npcPhysicsControl
  300. _ <- physicsSpace += npcNode
  301. _ <- rootNode += npcNode
  302. } yield ()).mapError(AppError.AppNodeError)
  303. npcActor <- npcActorTask
  304. } yield npcActor)
  305. }
  306. }
  307. class FollowControl(playerNode: Node) extends AbstractControl {
  308. override def controlUpdate(tpf: Float): Unit =
  309. this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
  310. override def controlRender(
  311. rm: RenderManager,
  312. vp: ViewPort
  313. ): Unit = {}
  314. }