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.
 
 

294 lines
9.7 KiB

package wow.doge.mygame
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 com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetManager
import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager
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.execution.exceptions.DummyException
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.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.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.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 cats.syntax.eq._
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
class MainApp(
logger: Logger[Task],
jmeThread: monix.execution.Scheduler,
schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea]
)(implicit
spawnProtocol: ActorSystem[SpawnProtocol.Command],
@annotation.unused timeout: Timeout,
@annotation.unused scheduler: Scheduler
) {
val scriptSystemInit =
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
val eventsModule = new EventsModule(spawnProtocol)
def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
GameApp.resource(logger, jmeThread, schedulers).evalMap {
case gameApp -> gameAppFib =>
for {
playerEventBus <- eventsModule.playerEventBusTask
mainEventBus <- eventsModule.mainEventBusTask
tickEventBus <- eventsModule.tickEventBusTask
gameAppActor <- AkkaUtils.spawnActorL(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
)
_ <- gameAppActor !! GameAppActor.Start
inputManager <- gameApp.inputManager
assetManager <- gameApp.assetManager
stateManager <- gameApp.stateManager
camera <- gameApp.camera
rootNode <- gameApp.rootNode
enqueueR <- Task(gameApp.enqueue _)
viewPort <- gameApp.viewPort
_ <- logger.info("before")
// jfxUI <- gameApp.jfxUI
consoleTextArea <- Task(new TextArea {
text = "hello \n"
editable = false
wrapText = true
// maxHeight = 150
// maxWidth = 300
})
// _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea)
_ <- logger.info("after")
bulletAppState <- Task(new BulletAppState())
_ <- Task(stateManager.attach(bulletAppState))
_ <- logger.info("Initializing console stream")
_ <-
wire[MainAppDelegate]
.init(gameApp.scheduler)
.executeOn(gameApp.scheduler)
} yield gameAppFib
}
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,
camera: Camera,
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: Node @@ GameAppTags.RootNode,
bulletAppState: BulletAppState
)(implicit
spawnProtocol: ActorSystem[SpawnProtocol.Command],
@annotation.unused timeout: Timeout,
@annotation.unused scheduler: Scheduler
) {
val physicsSpace = bulletAppState.physicsSpace
def init(
appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea]
) =
for {
_ <- loggerL.info("Initializing Systems")
_ <- Task(
assetManager.registerLocator(
os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
)
_ <- loggerL.info("test")
// _ <- Task(consoleStream.println("text"))
_ <- DefaultGameLevel(assetManager, viewPort)
.addToGame(
rootNode,
bulletAppState.physicsSpace
)
_ <- createPlayerController(appScheduler)
.absorbWith(e => DummyException("boom"))
.onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin.onErrorRestart(3)
// johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler)
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
// _ <-
// (johnActor !! NpcActorSupervisor.Move(
// ImVector3f(-30, 0, 10)
// )).executeAsync
// .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, Unit] = {
val playerPos = ImVector3f.ZERO
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
// lazy val camNode =
// PlayerController.Defaults
// .defaultCamerNode(camera, playerPos)
// .taggedWith[PlayerControllerTags.PlayerCameraNode]
val mbPlayerNode = PlayerController.Defaults
.defaultPlayerNode(
assetManager,
modelPath,
playerPos,
// camNode
playerPhysicsControl
)
val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
for {
playerNode <- IO.fromEither(mbPlayerNode)
_ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
.onErrorHandleWith(e =>
IO.raiseError(PlayerController.GenericError(e.getMessage()))
)
camNode <- IO(
PlayerController.Defaults
.defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
).onErrorHandleWith(e =>
IO.raiseError(PlayerController.GenericError(e.getMessage()))
).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
// _ <- Task {
// val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
// chaseCam.setSmoothMotion(false)
// chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
// chaseCam
// }
// .onErrorHandleWith(e =>
// IO.raiseError(PlayerController.GenericError(e.getMessage()))
// )
_ <- wire[PlayerController.Props].create
} yield ()
}
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 mbNpcNode = PlayerController.Defaults.defaultNpcNode(
assetManager,
initialPos,
npcPhysicsControl,
npcName
)
val npcActorTask = AkkaUtils.spawnActorL(
NpcActorSupervisor
.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
// tickEventBus,
npcPhysicsControl
).behavior,
npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
)
// .taggedWith[PlayerControllerTags.PlayerTag]
for {
npcNode <- IO.fromEither(mbNpcNode)
npcActor <- npcActorTask
_ <- IO {
physicsSpace += npcPhysicsControl
physicsSpace += npcNode
rootNode += npcNode
}
} 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 = {}
}