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.
 
 

319 lines
10 KiB

package wow.doge.mygame
import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.syntax.eq._
import com.jme3.app.state.AppStateManager
import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager
import com.jme3.material.Material
import com.jme3.material.MaterialDef
import com.jme3.math.FastMath
import com.jme3.renderer.Camera
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort
import com.jme3.scene.Node
import com.jme3.scene.control.AbstractControl
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.Fiber
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import scalafx.scene.control.TextArea
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.GameAppResource
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.PlayerControllerTags
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EventsModule
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.wrappers.jme.AppNode
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import monix.reactive.Observable
import monix.eval.Coeval
import wow.doge.mygame.utils.IOUtils
import com.jme3.math.ColorRGBA
class MainApp(
logger: Logger[Task],
jmeThread: monix.execution.Scheduler,
schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea]
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
scheduler: Scheduler
) {
val scriptSystemInit =
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
val eventsModule = new EventsModule(scheduler, spawnProtocol)
class TestClass(
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent]
)
def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
wire[GameAppResource].resource.evalMap {
case gameApp -> gameAppFib =>
for {
playerEventBus <- eventsModule.playerEventBusTask
mainEventBus <- eventsModule.mainEventBusTask
tickEventBus <- eventsModule.tickEventBusTask
obs <- playerEventBus.askL[Observable[PlayerMovementEvent]](
ObservableSubscription(_)
)
_ <- IOUtils.toIO(
obs
.doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
.completedL
.startAndForget
)
inputManager <- gameApp.inputManager
assetManager <- UIO.pure(gameApp.assetManager)
stateManager <- gameApp.stateManager
camera <- gameApp.camera
rootNode <- UIO.pure(gameApp.rootNode)
enqueueR <- UIO(gameApp.enqueue _)
viewPort <- gameApp.viewPort
physicsSpace <- UIO.pure(gameApp.physicsSpace)
_ <- logger.info("before")
// jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- Task(new TextArea {
text = "hello \n"
editable = false
wrapText = true
// maxHeight = 150
// maxWidth = 300
})
// _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea)
_ <- logger.info("after")
_ <- logger.info("Initializing console stream")
_ <-
wire[MainAppDelegate]
.init()
.executeOn(gameApp.scheduler)
} yield gameAppFib
}
// val x: Task[Unit] = for {
// tickEventBus <- eventsModule.tickEventBusTask
// playerEventBus <- eventsModule.playerEventBusTask
// _ <- UIO(wire[TestClass])
// _ <- gameInit(tickEventBus).use(_.join)
// } yield ()
val program = for {
scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult]
launcher <- new Launcher.Props(schedulers, launchSignal).create
launchResult <- launcher.init.use(_ => launchSignal.get)
_ <-
/**
* User chose to quit
*/
if (launchResult === LauncherResult.Exit)
logger.info("Exiting")
/**
* User chose launch. Wait for game window to close
*/
else
gameInit.use(_.join)
} yield ()
}
/**
* Class with all dependencies in one place for easy wiring
*/
class MainAppDelegate(
gameApp: GameApp,
loggerL: Logger[Task],
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager,
assetManager: AssetManager,
stateManager: AppStateManager,
physicsSpace: PhysicsSpace[Task],
camera: Camera,
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode[Task] @@ GameAppTags.RootNode
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
scheduler: Scheduler
) {
def init(
// appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea]
) =
for {
_ <- loggerL.info("Initializing Systems")
_ <- assetManager.registerLocator(
os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
_ <- loggerL.info("test")
// _ <- Task(consoleStream.println("text"))
_ <- DefaultGameLevel(assetManager, viewPort)
.flatMap(_.addToGame(rootNode, physicsSpace).hideErrors)
.onErrorHandleWith(e => loggerL.error(e.toString))
_ <- createPlayerController()
.onErrorHandleWith(e => loggerL.error(e.toString))
// .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin
// .onErrorRestart(3)
johnActor <- createTestNpc("John")
.onErrorHandleWith(e => IO.raiseError(new Throwable(e.toString)))
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
// _ <-
// johnActor
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
// .delayExecution(2.seconds)
// _ <-
// IOUtils
// .toIO(
// rootNode
// .observableBreadthFirst()
// .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
// .completedL
// )
// .executeOn(appScheduler)
// .startAndForget
} yield ()
def createPlayerController(
// appScheduler: monix.execution.Scheduler
): IO[PlayerController.Error, ActorRef[PlayerActorSupervisor.Command]] = {
val playerPos = ImVector3f.ZERO
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
for {
playerModel <-
assetManager
.loadModelAs[Node](modelPath)
.map(_.withRotate(0, FastMath.PI, 0))
.mapError(PlayerController.CouldNotCreatePlayerModel)
playerNode <- UIO(
PlayerController.Defaults
.defaultPlayerNode(
playerPos,
playerModel,
playerPhysicsControl
)
)
cameraPivotNode <- UIO(
new Node(EntityIds.CameraPivot.value)
.withControl(new FollowControl(playerNode))
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
)
camNode <- UIO(
PlayerController.Defaults
.defaultCamerNode(camera, playerPos)
.taggedWith[PlayerControllerTags.PlayerCameraNode]
)
playerActor <- wire[PlayerController.Props].create
} yield playerActor
}
def createTestNpc(
// appScheduler: monix.execution.Scheduler,
npcName: String
) = {
val initialPos = ImVector3f(50, 5, 0)
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
// (1f, 2.1f, 10f)
.withJumpForce(ImVector3f(0, 5f, 0))
val npcActorTask = AkkaUtils.spawnActorL(
NpcActorSupervisor
.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
// tickEventBus,
npcName,
npcPhysicsControl
).behavior,
npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
)
for {
materialDef <- assetManager.loadAssetAs[MaterialDef](
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
material = new Material(materialDef)
_ = material.setColor("Color", ColorRGBA.Blue)
mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
npcNode = PlayerController.Defaults.defaultNpcNode(
mesh,
initialPos,
npcPhysicsControl,
npcName
)
npcActor <- npcActorTask.hideErrors
_ <- (for {
_ <- physicsSpace += npcPhysicsControl
_ <- physicsSpace += npcNode
_ <- rootNode += npcNode
} yield ()).hideErrors
} yield npcActor
}
}
class FollowControl(playerNode: Node) extends AbstractControl {
override def controlUpdate(tpf: Float): Unit =
this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
override def controlRender(
rm: RenderManager,
vp: ViewPort
): Unit = {}
}