Many changes
Enhanced NPC state machine Made player movement direction depend on camera direction
This commit is contained in:
parent
73d657952f
commit
88293cebde
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ project/plugins/project/
|
|||||||
metals.sbt
|
metals.sbt
|
||||||
.metals
|
.metals
|
||||||
.bloop
|
.bloop
|
||||||
|
.ammonite
|
||||||
|
|
||||||
# Scala-IDE specific
|
# Scala-IDE specific
|
||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
|
@ -10,21 +10,28 @@ import com.jme3.app.state.AppStateManager
|
|||||||
import com.jme3.asset.AssetManager
|
import com.jme3.asset.AssetManager
|
||||||
import com.jme3.asset.plugins.ZipLocator
|
import com.jme3.asset.plugins.ZipLocator
|
||||||
import com.jme3.bullet.BulletAppState
|
import com.jme3.bullet.BulletAppState
|
||||||
|
import com.jme3.bullet.control.BetterCharacterControl
|
||||||
import com.jme3.input.InputManager
|
import com.jme3.input.InputManager
|
||||||
import com.jme3.renderer.Camera
|
import com.jme3.renderer.Camera
|
||||||
|
import com.jme3.renderer.RenderManager
|
||||||
import com.jme3.renderer.ViewPort
|
import com.jme3.renderer.ViewPort
|
||||||
import com.jme3.scene.Node
|
import com.jme3.scene.Node
|
||||||
|
import com.jme3.scene.control.AbstractControl
|
||||||
import com.softwaremill.macwire._
|
import com.softwaremill.macwire._
|
||||||
import com.softwaremill.tagging._
|
import com.softwaremill.tagging._
|
||||||
import io.odin.Logger
|
import io.odin.Logger
|
||||||
import monix.bio.Fiber
|
import monix.bio.Fiber
|
||||||
import monix.bio.IO
|
import monix.bio.IO
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
|
import monix.execution.exceptions.DummyException
|
||||||
import scalafx.scene.control.TextArea
|
import scalafx.scene.control.TextArea
|
||||||
import wow.doge.mygame.executors.Schedulers
|
import wow.doge.mygame.executors.Schedulers
|
||||||
import wow.doge.mygame.game.GameApp
|
import wow.doge.mygame.game.GameApp
|
||||||
import wow.doge.mygame.game.GameAppActor
|
import wow.doge.mygame.game.GameAppActor
|
||||||
import wow.doge.mygame.game.GameAppTags
|
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.NpcMovementActor2
|
||||||
import wow.doge.mygame.game.entities.PlayerController
|
import wow.doge.mygame.game.entities.PlayerController
|
||||||
import wow.doge.mygame.game.entities.PlayerControllerTags
|
import wow.doge.mygame.game.entities.PlayerControllerTags
|
||||||
import wow.doge.mygame.game.subsystems.input.GameInputHandler
|
import wow.doge.mygame.game.subsystems.input.GameInputHandler
|
||||||
@ -42,12 +49,9 @@ import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
|
|||||||
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
|
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
|
||||||
import wow.doge.mygame.utils.AkkaUtils
|
import wow.doge.mygame.utils.AkkaUtils
|
||||||
import wow.doge.mygame.utils.GenericConsoleStream
|
import wow.doge.mygame.utils.GenericConsoleStream
|
||||||
|
import wow.doge.mygame.utils.IOUtils
|
||||||
|
|
||||||
import EventsModule.GameEventBus
|
import EventsModule.GameEventBus
|
||||||
import wow.doge.mygame.game.entities.NpcMovementActor2
|
|
||||||
import wow.doge.mygame.game.entities.NpcActorSupervisor
|
|
||||||
import monix.execution.exceptions.DummyException
|
|
||||||
import com.jme3.bullet.control.BetterCharacterControl
|
|
||||||
|
|
||||||
class MainApp(
|
class MainApp(
|
||||||
logger: Logger[Task],
|
logger: Logger[Task],
|
||||||
@ -189,9 +193,23 @@ class MainAppDelegate(
|
|||||||
_ <- createPlayerController(appScheduler)
|
_ <- createPlayerController(appScheduler)
|
||||||
.absorbWith(e => DummyException("boom"))
|
.absorbWith(e => DummyException("boom"))
|
||||||
.onErrorRestart(3)
|
.onErrorRestart(3)
|
||||||
johnActor <- createNpc(appScheduler, "John").executeOn(appScheduler)
|
|
||||||
_ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
|
|
||||||
_ <- wire[GameInputHandler.Props].begin.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(-80, 0, 100)
|
||||||
|
// )).executeAsync.delayExecution(2.seconds)
|
||||||
|
_ <-
|
||||||
|
IOUtils
|
||||||
|
.toIO(
|
||||||
|
rootNode
|
||||||
|
.observableBreadthFirst()
|
||||||
|
.doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
|
||||||
|
.completedL
|
||||||
|
)
|
||||||
|
.executeOn(appScheduler)
|
||||||
|
.startAndForget
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def createPlayerController(
|
def createPlayerController(
|
||||||
@ -199,28 +217,49 @@ class MainAppDelegate(
|
|||||||
): IO[PlayerController.Error, Unit] = {
|
): IO[PlayerController.Error, Unit] = {
|
||||||
val playerPos = ImVector3f.ZERO
|
val playerPos = ImVector3f.ZERO
|
||||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||||
val playerPhysicsControl =
|
lazy val playerPhysicsControl =
|
||||||
PlayerController.Defaults.defaultPlayerPhysicsControl
|
PlayerController.Defaults.defaultPlayerPhysicsControl
|
||||||
.taggedWith[PlayerControllerTags.PlayerTag]
|
.taggedWith[PlayerControllerTags.PlayerTag]
|
||||||
val camNode =
|
// lazy val camNode =
|
||||||
PlayerController.Defaults
|
// PlayerController.Defaults
|
||||||
.defaultCamerNode(camera, playerPos)
|
// .defaultCamerNode(camera, playerPos)
|
||||||
.taggedWith[PlayerControllerTags.PlayerCameraNode]
|
// .taggedWith[PlayerControllerTags.PlayerCameraNode]
|
||||||
val mbPlayerNode = PlayerController.Defaults
|
lazy val mbPlayerNode = PlayerController.Defaults
|
||||||
.defaultPlayerNode(
|
.defaultPlayerNode(
|
||||||
assetManager,
|
assetManager,
|
||||||
modelPath,
|
modelPath,
|
||||||
playerPos,
|
playerPos,
|
||||||
camNode,
|
// camNode
|
||||||
playerPhysicsControl
|
playerPhysicsControl
|
||||||
)
|
)
|
||||||
|
lazy val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
|
||||||
|
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
|
||||||
|
|
||||||
for {
|
for {
|
||||||
playerNode <- IO.fromEither(mbPlayerNode)
|
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
|
_ <- wire[PlayerController.Props].create
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
def createNpc(
|
def createTestNpc(
|
||||||
appScheduler: monix.execution.Scheduler,
|
appScheduler: monix.execution.Scheduler,
|
||||||
npcName: String
|
npcName: String
|
||||||
) =
|
) =
|
||||||
@ -243,7 +282,7 @@ class MainAppDelegate(
|
|||||||
assetManager,
|
assetManager,
|
||||||
initialPos,
|
initialPos,
|
||||||
npcPhysicsControl,
|
npcPhysicsControl,
|
||||||
"John"
|
npcName
|
||||||
)
|
)
|
||||||
val npcActorTask = AkkaUtils.spawnActorL2(
|
val npcActorTask = AkkaUtils.spawnActorL2(
|
||||||
NpcActorSupervisor
|
NpcActorSupervisor
|
||||||
@ -251,14 +290,14 @@ class MainAppDelegate(
|
|||||||
new NpcMovementActor2.Props(
|
new NpcMovementActor2.Props(
|
||||||
enqueueR,
|
enqueueR,
|
||||||
initialPos,
|
initialPos,
|
||||||
tickEventBus,
|
// tickEventBus,
|
||||||
npcPhysicsControl
|
npcPhysicsControl
|
||||||
).create,
|
).create,
|
||||||
npcName,
|
npcName,
|
||||||
initialPos
|
initialPos
|
||||||
)
|
)
|
||||||
.create,
|
.create,
|
||||||
s"${npcName}-npcMovementActorSupervisor"
|
s"${npcName}-npcActorSupervisor"
|
||||||
)
|
)
|
||||||
// .taggedWith[PlayerControllerTags.PlayerTag]
|
// .taggedWith[PlayerControllerTags.PlayerTag]
|
||||||
|
|
||||||
@ -274,3 +313,13 @@ class MainAppDelegate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {}
|
||||||
|
}
|
||||||
|
@ -67,10 +67,7 @@ class SimpleAppExt(
|
|||||||
|
|
||||||
object JMEExecutorService extends GUIExecutorService {
|
object JMEExecutorService extends GUIExecutorService {
|
||||||
override def execute(command: Runnable): Unit =
|
override def execute(command: Runnable): Unit =
|
||||||
enqueueScala(() => command.run())
|
enqueue(command)
|
||||||
// enqueue(command)
|
|
||||||
// new SingleThreadEventExecutor()
|
|
||||||
// sys.addShutdownHook(JMEExecutorService.shutdown())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val scheduler = Scheduler(JMEExecutorService)
|
lazy val scheduler = Scheduler(JMEExecutorService)
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package wow.doge.mygame.game.entities
|
||||||
|
|
||||||
|
class EntityId(val value: String) extends AnyVal
|
||||||
|
object EntityIds {
|
||||||
|
val CameraPivot = new EntityId("CameraPivot")
|
||||||
|
}
|
@ -1,35 +1,43 @@
|
|||||||
package wow.doge.mygame.game.entities
|
package wow.doge.mygame.game.entities
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Success
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
import akka.actor.typed.SupervisorStrategy
|
import akka.actor.typed.SupervisorStrategy
|
||||||
import akka.actor.typed.scaladsl.ActorContext
|
import akka.actor.typed.scaladsl.ActorContext
|
||||||
import akka.actor.typed.scaladsl.Behaviors
|
import akka.actor.typed.scaladsl.Behaviors
|
||||||
|
import akka.util.Timeout
|
||||||
|
import monix.execution.CancelableFuture
|
||||||
|
import monix.execution.CancelablePromise
|
||||||
|
import wow.doge.mygame.game.subsystems.movement.CanMove
|
||||||
|
import wow.doge.mygame.implicits._
|
||||||
|
import wow.doge.mygame.math.ImVector3f
|
||||||
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent
|
||||||
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
|
||||||
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
|
||||||
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
|
||||||
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
|
||||||
import wow.doge.mygame.subsystems.events.Event
|
import wow.doge.mygame.subsystems.events.Event
|
||||||
import wow.doge.mygame.subsystems.events.EventBus
|
import wow.doge.mygame.subsystems.events.EventBus
|
||||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||||
import wow.doge.mygame.subsystems.events.TickEvent
|
import wow.doge.mygame.subsystems.events.TickEvent
|
||||||
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
|
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
|
||||||
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
||||||
import wow.doge.mygame.subsystems.events.EntityMovementEvent
|
|
||||||
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
|
|
||||||
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
|
|
||||||
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
|
|
||||||
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
|
|
||||||
import wow.doge.mygame.math.ImVector3f
|
|
||||||
import wow.doge.mygame.game.subsystems.movement.CanMove
|
|
||||||
import wow.doge.mygame.implicits._
|
|
||||||
import akka.util.Timeout
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.util.Success
|
|
||||||
import scala.util.Failure
|
|
||||||
|
|
||||||
object NpcActorSupervisor {
|
object NpcActorSupervisor {
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
case class Move(pos: ImVector3f) extends Command
|
final case class Move(pos: ImVector3f) extends Command
|
||||||
private case class UpdatePosition(pos: ImVector3f) extends Command
|
private final case class InternalMove(
|
||||||
|
move: Move,
|
||||||
|
signal: CancelableFuture[NpcMovementActor2.DoneMoving.type]
|
||||||
|
) extends Command
|
||||||
|
private case object DoneMoving extends Command
|
||||||
|
// private case class MovementResponse(response: CancelableFuture[_]) extends Command
|
||||||
private case class LogError(err: Throwable) extends Command
|
private case class LogError(err: Throwable) extends Command
|
||||||
case object MovementTick extends Command
|
private case object NoOp extends Command
|
||||||
|
|
||||||
final case class Props(
|
final case class Props(
|
||||||
npcMovementActorBehavior: Behavior[NpcMovementActor2.Command],
|
npcMovementActorBehavior: Behavior[NpcMovementActor2.Command],
|
||||||
@ -43,115 +51,186 @@ object NpcActorSupervisor {
|
|||||||
s"npc-${npcName}-NpcMovementActor"
|
s"npc-${npcName}-NpcMovementActor"
|
||||||
)
|
)
|
||||||
|
|
||||||
new NpcActorSupervisor(ctx, this)
|
new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
|
||||||
.idle(State(npcMovementActor, initialPos))
|
.idle(State())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final case class State(
|
final case class State(
|
||||||
npcMovementActor: ActorRef[NpcMovementActor2.Command],
|
)
|
||||||
currentPos: ImVector3f
|
final case class Children(
|
||||||
|
npcMovementActor: ActorRef[NpcMovementActor2.Command]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class NpcActorSupervisor(
|
class NpcActorSupervisor(
|
||||||
ctx: ActorContext[NpcActorSupervisor.Command],
|
ctx: ActorContext[NpcActorSupervisor.Command],
|
||||||
props: NpcActorSupervisor.Props
|
props: NpcActorSupervisor.Props,
|
||||||
|
children: NpcActorSupervisor.Children
|
||||||
) {
|
) {
|
||||||
import NpcActorSupervisor._
|
import NpcActorSupervisor._
|
||||||
implicit val timeout = Timeout(1.second)
|
implicit val timeout = Timeout(1.second)
|
||||||
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
|
|
||||||
Behaviors.receiveMessage[Command] {
|
private val movementTimer = ctx.spawn(
|
||||||
case Move(pos) => {
|
GenericTimerActor
|
||||||
state.npcMovementActor ! NpcMovementActor2.Move(pos)
|
.Props(
|
||||||
val movementTimer = ctx.spawn(
|
children.npcMovementActor,
|
||||||
GenericTimerActor.Props(ctx.self, MovementTick, 100.millis).create,
|
NpcMovementActor2.MovementTick,
|
||||||
s"npc-${props.npcName}-NpcActorTimer"
|
100.millis
|
||||||
)
|
)
|
||||||
movementTimer ! GenericTimerActor.Start
|
.create,
|
||||||
moving(state, pos, movementTimer)
|
s"npc-John-NpcActorTimer"
|
||||||
|
)
|
||||||
|
|
||||||
|
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
|
||||||
|
Behaviors.setup { _ =>
|
||||||
|
ctx.log.info("Inside Idle State")
|
||||||
|
Behaviors.receiveMessage[Command] {
|
||||||
|
case m @ Move(pos) =>
|
||||||
|
ctx.ask(
|
||||||
|
children.npcMovementActor,
|
||||||
|
NpcMovementActor2.MoveTo(pos, _)
|
||||||
|
) {
|
||||||
|
case Success(signal) => InternalMove(m, signal)
|
||||||
|
case Failure(exception) => LogError(exception)
|
||||||
}
|
}
|
||||||
|
Behaviors.same
|
||||||
|
case InternalMove(move, signal) =>
|
||||||
|
moving(state, move.pos, signal)
|
||||||
|
|
||||||
case LogError(err) =>
|
case LogError(err) =>
|
||||||
ctx.log.warn(err.getMessage())
|
ctx.log.warn(err.getMessage())
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case _ => Behaviors.unhandled
|
case _ => Behaviors.unhandled
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def moving(
|
def moving(
|
||||||
state: State,
|
state: State,
|
||||||
targetPos: ImVector3f,
|
targetPos: ImVector3f,
|
||||||
movementTimer: ActorRef[GenericTimerActor.Command]
|
signal: CancelableFuture[NpcMovementActor2.DoneMoving.type]
|
||||||
): Behavior[NpcActorSupervisor.Command] =
|
): Behavior[NpcActorSupervisor.Command] =
|
||||||
Behaviors.receiveMessagePartial[Command] {
|
Behaviors.setup { _ =>
|
||||||
case LogError(err) =>
|
movementTimer ! GenericTimerActor.Start
|
||||||
ctx.log.warn(err.getMessage())
|
|
||||||
Behaviors.same
|
|
||||||
case Move(pos) => moving(state, pos, movementTimer)
|
|
||||||
case UpdatePosition(pos) =>
|
|
||||||
ctx.log.trace("Current pos = " + state.currentPos.toString())
|
|
||||||
moving(state.copy(currentPos = pos), targetPos, movementTimer)
|
|
||||||
case MovementTick =>
|
|
||||||
val dst = ImVector3f.dst(targetPos, state.currentPos)
|
|
||||||
if (dst <= 10f) {
|
|
||||||
state.npcMovementActor ! NpcMovementActor2.StopMoving
|
|
||||||
movementTimer ! GenericTimerActor.Stop
|
|
||||||
idle(state)
|
|
||||||
} else {
|
|
||||||
// ctx.log.debug("Difference = " + dst.toString())
|
|
||||||
// ctx.log.debug("Current pos = " + state.currentPos.toString())
|
|
||||||
|
|
||||||
ctx.ask(state.npcMovementActor, NpcMovementActor2.AskPosition(_)) {
|
// ctx
|
||||||
case Success(value) =>
|
// .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
|
||||||
UpdatePosition(value)
|
// _.fold(LogError(_), MovementResponse(_))
|
||||||
|
// )
|
||||||
|
ctx.pipeToSelf(signal) {
|
||||||
|
case Success(value) => DoneMoving
|
||||||
case Failure(exception) => LogError(exception)
|
case Failure(exception) => LogError(exception)
|
||||||
}
|
}
|
||||||
// Behaviors.same
|
Behaviors.receiveMessagePartial[Command] {
|
||||||
moving(state, targetPos, movementTimer)
|
case LogError(err) =>
|
||||||
|
ctx.log.error(err.getMessage())
|
||||||
|
Behaviors.same
|
||||||
|
case m @ Move(pos) =>
|
||||||
|
movementTimer ! GenericTimerActor.Stop
|
||||||
|
children.npcMovementActor ! NpcMovementActor2.StopMoving
|
||||||
|
signal.cancel()
|
||||||
|
ctx.ask(
|
||||||
|
children.npcMovementActor,
|
||||||
|
NpcMovementActor2.MoveTo(pos, _)
|
||||||
|
) {
|
||||||
|
case Success(signal) => InternalMove(m, signal)
|
||||||
|
case Failure(exception) => LogError(exception)
|
||||||
|
}
|
||||||
|
Behaviors.same
|
||||||
|
case InternalMove(move, signal) =>
|
||||||
|
moving(state, targetPos, signal)
|
||||||
|
case NoOp => Behaviors.same
|
||||||
|
// case MovementResponse(x: CancelableFuture[_]) =>
|
||||||
|
// // ctx.pipeToSelf(x)(_.)
|
||||||
|
case DoneMoving =>
|
||||||
|
movementTimer ! GenericTimerActor.Stop
|
||||||
|
idle(state)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object NpcMovementActor2 {
|
object NpcMovementActor2 {
|
||||||
|
|
||||||
|
case object DoneMoving
|
||||||
|
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
|
case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
|
||||||
|
case object MovementTick extends Command
|
||||||
case object StopMoving extends Command
|
case object StopMoving extends Command
|
||||||
case class Move(target: ImVector3f) extends Command
|
case class MoveTo(
|
||||||
|
target: ImVector3f,
|
||||||
|
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
|
||||||
|
) extends Command
|
||||||
|
|
||||||
final class Props[T: CanMove](
|
final class Props[T: CanMove](
|
||||||
val enqueueR: Function1[() => Unit, Unit],
|
val enqueueR: Function1[() => Unit, Unit],
|
||||||
val initialPos: ImVector3f,
|
val initialPos: ImVector3f,
|
||||||
val tickEventBus: GameEventBus[TickEvent],
|
// val tickEventBus: GameEventBus[TickEvent],
|
||||||
val movable: T
|
val movable: T
|
||||||
) {
|
) {
|
||||||
def create =
|
def create =
|
||||||
Behaviors.setup[Command] { ctx =>
|
Behaviors.setup[Command] { ctx =>
|
||||||
new NpcMovementActor2(ctx, this).receive(State(initialPos))
|
new NpcMovementActor2(ctx, this).receive(State())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class State(currentPos: ImVector3f)
|
final case class State()
|
||||||
}
|
}
|
||||||
class NpcMovementActor2[T](
|
class NpcMovementActor2[T](
|
||||||
ctx: ActorContext[NpcMovementActor2.Command],
|
ctx: ActorContext[NpcMovementActor2.Command],
|
||||||
props: NpcMovementActor2.Props[T]
|
props: NpcMovementActor2.Props[T]
|
||||||
) {
|
)(implicit cm: CanMove[T]) {
|
||||||
import NpcMovementActor2._
|
import NpcMovementActor2._
|
||||||
|
|
||||||
|
def location = cm.location(props.movable)
|
||||||
|
|
||||||
def receive(
|
def receive(
|
||||||
state: State
|
state: State
|
||||||
)(implicit cm: CanMove[T]): Behavior[NpcMovementActor2.Command] =
|
): Behavior[NpcMovementActor2.Command] =
|
||||||
Behaviors.receiveMessage[Command] {
|
Behaviors.receiveMessagePartial {
|
||||||
case AskPosition(replyTo) =>
|
case AskPosition(replyTo) =>
|
||||||
replyTo ! cm.location(props.movable)
|
replyTo ! location
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case Move(target: ImVector3f) =>
|
|
||||||
props.enqueueR(() =>
|
|
||||||
cm.move(props.movable, (target - state.currentPos) * 0.005f)
|
|
||||||
)
|
|
||||||
receive(state = state.copy(currentPos = cm.location(props.movable)))
|
|
||||||
case StopMoving =>
|
case StopMoving =>
|
||||||
ctx.log.debug(
|
ctx.log.debug(
|
||||||
"Position at Stop = " + cm.location(props.movable).toString
|
"Position at Stop = " + location.toString
|
||||||
)
|
)
|
||||||
props.enqueueR(() => cm.stop(props.movable))
|
props.enqueueR(() => cm.stop(props.movable))
|
||||||
receive(state = state.copy(currentPos = cm.location(props.movable)))
|
receive(state)
|
||||||
|
case MoveTo(
|
||||||
|
target: ImVector3f,
|
||||||
|
replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
|
||||||
|
) =>
|
||||||
|
props.enqueueR(() => cm.move(props.movable, target - location))
|
||||||
|
val p = CancelablePromise[DoneMoving.type]()
|
||||||
|
replyTo ! p.future
|
||||||
|
ticking(p, target, state)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def ticking(
|
||||||
|
reachDestination: CancelablePromise[DoneMoving.type],
|
||||||
|
targetPos: ImVector3f,
|
||||||
|
state: State
|
||||||
|
): Behavior[NpcMovementActor2.Command] =
|
||||||
|
Behaviors.receiveMessagePartial {
|
||||||
|
case StopMoving =>
|
||||||
|
ctx.log.debug(
|
||||||
|
"Position at Stop = " + location.toString
|
||||||
|
)
|
||||||
|
props.enqueueR(() => cm.stop(props.movable))
|
||||||
|
receive(state)
|
||||||
|
|
||||||
|
case MovementTick =>
|
||||||
|
val dst = ImVector3f.dst(targetPos, location)
|
||||||
|
if (dst <= 10f) {
|
||||||
|
ctx.self ! StopMoving
|
||||||
|
reachDestination.success(DoneMoving)
|
||||||
|
receive(state)
|
||||||
|
} else {
|
||||||
|
ctx.log.trace("Difference = " + dst.toString())
|
||||||
|
ctx.log.trace("Current pos = " + location.toString())
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ object PlayerActorSupervisor {
|
|||||||
playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]],
|
playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]],
|
||||||
tickEventBus: GameEventBus[TickEvent],
|
tickEventBus: GameEventBus[TickEvent],
|
||||||
imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
||||||
playerCamELBehavior: Behavior[PlayerCameraEvent]
|
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
|
||||||
) {
|
) {
|
||||||
def create[T: CanMove](movable: T) =
|
def create[T: CanMove](movable: T) =
|
||||||
Behaviors.logMessages(
|
Behaviors.logMessages(
|
||||||
@ -50,23 +50,29 @@ object PlayerActorSupervisor {
|
|||||||
.onFailure[Exception](SupervisorStrategy.restart),
|
.onFailure[Exception](SupervisorStrategy.restart),
|
||||||
"playerMovementActorChild"
|
"playerMovementActorChild"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val playerCameraActor =
|
||||||
|
ctx.spawn(playerCameraActorBehavior, "playerCameraActor")
|
||||||
|
|
||||||
|
val playerCameraEl = ctx.spawn(
|
||||||
|
PlayerCameraEventListener(playerCameraActor),
|
||||||
|
"playerCameraActorEl"
|
||||||
|
)
|
||||||
|
|
||||||
ctx.spawn(
|
ctx.spawn(
|
||||||
PlayerMovementActor
|
PlayerMovementActor
|
||||||
.Props(movementActor, playerMovementEventBus, tickEventBus)
|
.Props(
|
||||||
|
movementActor,
|
||||||
|
playerCameraActor,
|
||||||
|
playerMovementEventBus,
|
||||||
|
tickEventBus
|
||||||
|
)
|
||||||
.create,
|
.create,
|
||||||
"playerMovementAcor"
|
"playerMovementAcor"
|
||||||
)
|
)
|
||||||
lazy val playerCameraHandler = {
|
|
||||||
ctx.spawn(
|
|
||||||
Behaviors
|
|
||||||
.supervise(playerCamELBehavior)
|
|
||||||
.onFailure[Exception](SupervisorStrategy.restart),
|
|
||||||
"playerCameraHandler"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//init actors
|
//init actors
|
||||||
playerCameraEventBus ! EventBus.Subscribe(playerCameraHandler)
|
playerCameraEventBus ! EventBus.Subscribe(playerCameraEl)
|
||||||
|
|
||||||
new PlayerActorSupervisor(
|
new PlayerActorSupervisor(
|
||||||
ctx,
|
ctx,
|
||||||
@ -100,6 +106,7 @@ object PlayerMovementActor {
|
|||||||
sealed trait Command
|
sealed trait Command
|
||||||
final case class Props(
|
final case class Props(
|
||||||
movementActor: ActorRef[ImMovementActor.Command],
|
movementActor: ActorRef[ImMovementActor.Command],
|
||||||
|
playerCameraActor: ActorRef[PlayerCameraActor.Command],
|
||||||
playerMovementEventBus: ActorRef[
|
playerMovementEventBus: ActorRef[
|
||||||
EventBus.Command[PlayerMovementEvent]
|
EventBus.Command[PlayerMovementEvent]
|
||||||
],
|
],
|
||||||
@ -118,14 +125,13 @@ object PlayerMovementActor {
|
|||||||
Behaviors.receiveMessage[RenderTick.type] {
|
Behaviors.receiveMessage[RenderTick.type] {
|
||||||
case RenderTick =>
|
case RenderTick =>
|
||||||
movementActor ! ImMovementActor.Tick
|
movementActor ! ImMovementActor.Tick
|
||||||
|
// playerCameraActor ! PlayerCameraActor.Tick
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
val renderTickEl =
|
val renderTickEl =
|
||||||
ctx.spawn(renderTickElBehavior, "playerMovementTickListener")
|
ctx.spawn(renderTickElBehavior, "playerMovementTickListener")
|
||||||
|
|
||||||
playerMovementEventBus ! EventBus.Subscribe(
|
playerMovementEventBus ! EventBus.Subscribe(playerMovementEl)
|
||||||
playerMovementEl
|
|
||||||
)
|
|
||||||
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
||||||
Behaviors.receiveMessage { msg => Behaviors.same }
|
Behaviors.receiveMessage { msg => Behaviors.same }
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,40 @@
|
|||||||
package wow.doge.mygame.game.entities
|
package wow.doge.mygame.game.entities
|
||||||
|
|
||||||
|
import akka.actor.typed.Behavior
|
||||||
|
import akka.actor.typed.LogOptions
|
||||||
import akka.actor.typed.scaladsl.ActorContext
|
import akka.actor.typed.scaladsl.ActorContext
|
||||||
import akka.actor.typed.scaladsl.Behaviors
|
import akka.actor.typed.scaladsl.Behaviors
|
||||||
|
import com.jme3.math.FastMath
|
||||||
|
import com.jme3.math.Quaternion
|
||||||
|
import com.jme3.math.Vector3f
|
||||||
|
import com.jme3.scene.Node
|
||||||
|
import com.typesafe.scalalogging.Logger
|
||||||
|
import org.slf4j.event.Level
|
||||||
|
|
||||||
object PlayerCameraActor {
|
object PlayerCameraActor {
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
|
case object RotateLeft extends Command
|
||||||
|
case object RotateRight extends Command
|
||||||
|
case object RotateUp extends Command
|
||||||
|
case object RotateDown extends Command
|
||||||
|
case object Tick extends Command
|
||||||
|
|
||||||
class Props() {
|
class Props(
|
||||||
|
val cameraPivotNode: Node,
|
||||||
|
val enqueueR: Function1[() => Unit, Unit],
|
||||||
|
val getPlayerLocation: Function0[Vector3f]
|
||||||
|
) {
|
||||||
def create =
|
def create =
|
||||||
|
Behaviors.logMessages(
|
||||||
|
LogOptions()
|
||||||
|
.withLevel(Level.TRACE)
|
||||||
|
.withLogger(
|
||||||
|
Logger[PlayerCameraActor].underlying
|
||||||
|
),
|
||||||
Behaviors.setup[Command] { ctx =>
|
Behaviors.setup[Command] { ctx =>
|
||||||
new PlayerCameraActor(ctx, this).receive(State.empty)
|
new PlayerCameraActor(ctx, this).receive()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class State()
|
case class State()
|
||||||
@ -23,8 +47,36 @@ class PlayerCameraActor(
|
|||||||
props: PlayerCameraActor.Props
|
props: PlayerCameraActor.Props
|
||||||
) {
|
) {
|
||||||
import PlayerCameraActor._
|
import PlayerCameraActor._
|
||||||
def receive(state: State) =
|
def receive(
|
||||||
Behaviors.receiveMessage[Command] {
|
rotationBuf: Quaternion = new Quaternion(),
|
||||||
case _ => Behaviors.same
|
state: State = State.empty
|
||||||
|
): Behavior[Command] =
|
||||||
|
Behaviors.receiveMessage {
|
||||||
|
case RotateLeft =>
|
||||||
|
val rot = rotationBuf
|
||||||
|
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
||||||
|
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
||||||
|
Behaviors.same
|
||||||
|
case RotateRight =>
|
||||||
|
val rot = rotationBuf
|
||||||
|
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
||||||
|
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
||||||
|
Behaviors.same
|
||||||
|
case RotateUp =>
|
||||||
|
val rot = rotationBuf
|
||||||
|
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
|
||||||
|
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
||||||
|
Behaviors.same
|
||||||
|
case RotateDown =>
|
||||||
|
val rot = rotationBuf
|
||||||
|
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
|
||||||
|
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
||||||
|
Behaviors.same
|
||||||
|
case Tick =>
|
||||||
|
props.enqueueR(() => {
|
||||||
|
val location = props.getPlayerLocation()
|
||||||
|
props.cameraPivotNode.setLocalTranslation(location)
|
||||||
|
})
|
||||||
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package wow.doge.mygame.game.entities
|
package wow.doge.mygame.game.entities
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Props
|
|
||||||
import akka.actor.typed.Scheduler
|
import akka.actor.typed.Scheduler
|
||||||
import akka.actor.typed.SpawnProtocol
|
import akka.actor.typed.SpawnProtocol
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -15,9 +14,7 @@ import com.jme3.renderer.Camera
|
|||||||
import com.jme3.scene.CameraNode
|
import com.jme3.scene.CameraNode
|
||||||
import com.jme3.scene.Geometry
|
import com.jme3.scene.Geometry
|
||||||
import com.jme3.scene.Node
|
import com.jme3.scene.Node
|
||||||
import com.jme3.scene.control.CameraControl.ControlDirection
|
|
||||||
import com.jme3.scene.shape.Box
|
import com.jme3.scene.shape.Box
|
||||||
import com.softwaremill.macwire._
|
|
||||||
import com.softwaremill.tagging._
|
import com.softwaremill.tagging._
|
||||||
import io.odin.Logger
|
import io.odin.Logger
|
||||||
import monix.bio.IO
|
import monix.bio.IO
|
||||||
@ -38,6 +35,7 @@ import wow.doge.mygame.utils.AkkaUtils
|
|||||||
object PlayerControllerTags {
|
object PlayerControllerTags {
|
||||||
sealed trait PlayerTag
|
sealed trait PlayerTag
|
||||||
sealed trait PlayerCameraNode
|
sealed trait PlayerCameraNode
|
||||||
|
sealed trait PlayerCameraPivotNode
|
||||||
}
|
}
|
||||||
|
|
||||||
object PlayerController {
|
object PlayerController {
|
||||||
@ -60,7 +58,9 @@ object PlayerController {
|
|||||||
appScheduler: monix.execution.Scheduler,
|
appScheduler: monix.execution.Scheduler,
|
||||||
playerNode: Node @@ PlayerControllerTags.PlayerTag,
|
playerNode: Node @@ PlayerControllerTags.PlayerTag,
|
||||||
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
|
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
|
||||||
tickEventBus: GameEventBus[TickEvent]
|
cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
|
||||||
|
tickEventBus: GameEventBus[TickEvent],
|
||||||
|
camera: Camera
|
||||||
)(implicit timeout: Timeout, scheduler: Scheduler) {
|
)(implicit timeout: Timeout, scheduler: Scheduler) {
|
||||||
val create: IO[Error, Unit] =
|
val create: IO[Error, Unit] =
|
||||||
(for {
|
(for {
|
||||||
@ -71,17 +71,31 @@ object PlayerController {
|
|||||||
playerMovementEventBus,
|
playerMovementEventBus,
|
||||||
playerCameraEventBus,
|
playerCameraEventBus,
|
||||||
tickEventBus,
|
tickEventBus,
|
||||||
ImMovementActor
|
new ImMovementActor.Props(
|
||||||
.Props(enqueueR, playerPhysicsControl)
|
enqueueR,
|
||||||
.create,
|
playerPhysicsControl,
|
||||||
wireWith(PlayerCameraEventListener.apply _)
|
camera
|
||||||
|
).create,
|
||||||
|
// wireWith(PlayerCameraEventListener.apply _)
|
||||||
|
// PlayerCameraEventListener()
|
||||||
|
new PlayerCameraActor.Props(
|
||||||
|
cameraPivotNode,
|
||||||
|
enqueueR,
|
||||||
|
playerNode.getWorldTranslation _
|
||||||
|
).create
|
||||||
).create(playerPhysicsControl)
|
).create(playerPhysicsControl)
|
||||||
)
|
)
|
||||||
|
_ <- Task(rootNode += playerNode)
|
||||||
_ <- IO {
|
_ <- IO {
|
||||||
physicsSpace += playerNode
|
physicsSpace += playerNode
|
||||||
physicsSpace += playerPhysicsControl
|
physicsSpace += playerPhysicsControl
|
||||||
|
// rootNode += cameraNode
|
||||||
|
cameraPivotNode += cameraNode
|
||||||
|
// playerNode += cameraPivotNode
|
||||||
|
rootNode += cameraPivotNode
|
||||||
|
|
||||||
}
|
}
|
||||||
_ <- Task(rootNode += playerNode)
|
|
||||||
} yield ())
|
} yield ())
|
||||||
.onErrorHandleWith(e => IO.raiseError(GenericError(e.getMessage())))
|
.onErrorHandleWith(e => IO.raiseError(GenericError(e.getMessage())))
|
||||||
.executeOn(appScheduler)
|
.executeOn(appScheduler)
|
||||||
@ -129,9 +143,27 @@ object PlayerController {
|
|||||||
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
||||||
)
|
)
|
||||||
|
|
||||||
def defaultCamerNode(cam: Camera, playerPos: ImVector3f) =
|
// new CameraControl(cam) {
|
||||||
new CameraNode("CameraNode", cam)
|
// override def controlUpdate(tpf: Float) = {
|
||||||
.withControlDir(ControlDirection.SpatialToCamera)
|
// this.getCamera().setRotation(spatial.getWorldRotation())
|
||||||
|
// cameraPivotNode.setLocalTranslation(
|
||||||
|
// playerNode.getWorldTranslation()
|
||||||
|
// )
|
||||||
|
// this.getCamera().setLocation(spatial.getWorldTranslation())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
def defaultCamerNode(
|
||||||
|
cam: Camera,
|
||||||
|
playerNode: Node,
|
||||||
|
cameraPivotNode: Node,
|
||||||
|
playerPos: ImVector3f
|
||||||
|
) =
|
||||||
|
new CameraNode(
|
||||||
|
"CameraNode",
|
||||||
|
cam
|
||||||
|
)
|
||||||
|
// .withControlDir(ControlDirection.SpatialToCamera)
|
||||||
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
|
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
|
||||||
.withLookAt(playerPos, ImVector3f.UNIT_Y)
|
.withLookAt(playerPos, ImVector3f.UNIT_Y)
|
||||||
|
|
||||||
@ -139,14 +171,14 @@ object PlayerController {
|
|||||||
assetManager: AssetManager,
|
assetManager: AssetManager,
|
||||||
modelPath: os.RelPath,
|
modelPath: os.RelPath,
|
||||||
playerPos: ImVector3f,
|
playerPos: ImVector3f,
|
||||||
camNode: CameraNode,
|
// camNode: CameraNode,
|
||||||
playerPhysicsControl: BetterCharacterControl
|
playerPhysicsControl: BetterCharacterControl
|
||||||
) =
|
) =
|
||||||
Either
|
Either
|
||||||
.catchNonFatal(
|
.catchNonFatal(
|
||||||
Node("PlayerNode")
|
Node("PlayerNode")
|
||||||
.withChildren(
|
.withChildren(
|
||||||
camNode,
|
// camNode,
|
||||||
assetManager
|
assetManager
|
||||||
.loadModel(modelPath)
|
.loadModel(modelPath)
|
||||||
.asInstanceOf[Node]
|
.asInstanceOf[Node]
|
||||||
@ -187,45 +219,7 @@ object PlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Methods {
|
object Methods {}
|
||||||
def spawnMovementActor(
|
|
||||||
enqueueR: Function1[() => Unit, Unit],
|
|
||||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
|
||||||
movable: BetterCharacterControl @@ PlayerControllerTags.PlayerTag,
|
|
||||||
playerMovementEventBus: ActorRef[
|
|
||||||
EventBus.Command[PlayerMovementEvent]
|
|
||||||
],
|
|
||||||
loggerL: Logger[Task]
|
|
||||||
)(implicit timeout: Timeout, scheduler: Scheduler) =
|
|
||||||
spawnProtocol.askL[ActorRef[ImMovementActor.Command]](
|
|
||||||
SpawnProtocol.Spawn(
|
|
||||||
ImMovementActor.Props(enqueueR, movable).create,
|
|
||||||
"imMovementActor",
|
|
||||||
Props.empty,
|
|
||||||
_
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// def spawnPlayerActor(
|
|
||||||
// app: GameApp,
|
|
||||||
// spawnProtocol: ActorRef[SpawnProtocol.Command],
|
|
||||||
// movable: BetterCharacterControl @@ Player,
|
|
||||||
// playerMovementEventBus: ActorRef[
|
|
||||||
// EventBus.Command[PlayerMovementEvent]
|
|
||||||
// ]
|
|
||||||
// )(implicit timeout: Timeout, scheduler: Scheduler) =
|
|
||||||
// spawnProtocol.askL[ActorRef[PlayerActorSupervisor.Command]](
|
|
||||||
// SpawnProtocol.Spawn(
|
|
||||||
// new PlayerActorSupervisor.Props(
|
|
||||||
// app,
|
|
||||||
// movable,
|
|
||||||
// playerMovementEventBus
|
|
||||||
// ).create,
|
|
||||||
// "playerActor",
|
|
||||||
// Props.empty,
|
|
||||||
// _
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
|
|
||||||
// camNode <- IO(
|
// camNode <- IO(
|
||||||
// _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))
|
// _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))
|
||||||
|
@ -3,18 +3,17 @@ package wow.doge.mygame.game.entities
|
|||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.LogOptions
|
import akka.actor.typed.LogOptions
|
||||||
import akka.actor.typed.scaladsl.Behaviors
|
import akka.actor.typed.scaladsl.Behaviors
|
||||||
import com.jme3.scene.CameraNode
|
|
||||||
import com.typesafe.scalalogging.Logger
|
import com.typesafe.scalalogging.Logger
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
||||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent.CameraMovedDown
|
|
||||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent.CameraMovedUp
|
|
||||||
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
|
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
|
||||||
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
||||||
|
|
||||||
object PlayerMovementEventListener {
|
object PlayerMovementEventListener {
|
||||||
import PlayerMovementEvent._
|
import PlayerMovementEvent._
|
||||||
def apply(movementActor: ActorRef[ImMovementActor.Command]) =
|
def apply(
|
||||||
|
movementActor: ActorRef[ImMovementActor.Command]
|
||||||
|
) =
|
||||||
Behaviors.logMessages(
|
Behaviors.logMessages(
|
||||||
LogOptions()
|
LogOptions()
|
||||||
.withLevel(Level.TRACE)
|
.withLevel(Level.TRACE)
|
||||||
@ -22,7 +21,7 @@ object PlayerMovementEventListener {
|
|||||||
Logger[PlayerMovementEventListener.type].underlying
|
Logger[PlayerMovementEventListener.type].underlying
|
||||||
),
|
),
|
||||||
Behaviors.setup[PlayerMovementEvent](ctx =>
|
Behaviors.setup[PlayerMovementEvent](ctx =>
|
||||||
Behaviors.receiveMessagePartial {
|
Behaviors.receiveMessage {
|
||||||
case PlayerMovedLeft(pressed) =>
|
case PlayerMovedLeft(pressed) =>
|
||||||
movementActor ! ImMovementActor.MovedLeft(pressed)
|
movementActor ! ImMovementActor.MovedLeft(pressed)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
@ -38,27 +37,21 @@ object PlayerMovementEventListener {
|
|||||||
case PlayerJumped =>
|
case PlayerJumped =>
|
||||||
movementActor ! ImMovementActor.Jump
|
movementActor ! ImMovementActor.Jump
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case PlayerRotatedRight =>
|
// case PlayerTurnedRight =>
|
||||||
movementActor ! ImMovementActor.RotateRight
|
// movementActor ! ImMovementActor.RotateRight
|
||||||
Behaviors.same
|
// Behaviors.same
|
||||||
case PlayerRotatedLeft =>
|
// case PlayerTurnedLeft =>
|
||||||
movementActor ! ImMovementActor.RotateLeft
|
// movementActor ! ImMovementActor.RotateLeft
|
||||||
Behaviors.same
|
// Behaviors.same
|
||||||
case PlayerCameraUp =>
|
|
||||||
ctx.log.warn("camera up not implemented yet")
|
|
||||||
Behaviors.same
|
|
||||||
case PlayerCameraDown =>
|
|
||||||
ctx.log.warn("camera down not implemented yet")
|
|
||||||
Behaviors.same
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
object PlayerCameraEventListener {
|
object PlayerCameraEventListener {
|
||||||
|
import PlayerCameraEvent._
|
||||||
def apply(
|
def apply(
|
||||||
camNode: CameraNode,
|
playerCameraActor: ActorRef[PlayerCameraActor.Command]
|
||||||
enqueueR: Function1[() => Unit, Unit]
|
|
||||||
) =
|
) =
|
||||||
Behaviors.logMessages(
|
Behaviors.logMessages(
|
||||||
LogOptions()
|
LogOptions()
|
||||||
@ -69,16 +62,17 @@ object PlayerCameraEventListener {
|
|||||||
Behaviors.setup[PlayerCameraEvent](ctx =>
|
Behaviors.setup[PlayerCameraEvent](ctx =>
|
||||||
Behaviors.receiveMessagePartial {
|
Behaviors.receiveMessagePartial {
|
||||||
case CameraMovedUp =>
|
case CameraMovedUp =>
|
||||||
enqueueR(() => {
|
playerCameraActor ! PlayerCameraActor.RotateUp
|
||||||
|
|
||||||
camNode.move(0, 1, 0)
|
|
||||||
})
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case CameraMovedDown =>
|
case CameraMovedDown =>
|
||||||
enqueueR(() => {
|
playerCameraActor ! PlayerCameraActor.RotateDown
|
||||||
|
Behaviors.same
|
||||||
camNode.move(0, -1, 0)
|
case CameraLeft =>
|
||||||
})
|
playerCameraActor ! PlayerCameraActor.RotateLeft
|
||||||
|
Behaviors.same
|
||||||
|
case CameraRight =>
|
||||||
|
playerCameraActor ! PlayerCameraActor.RotateRight
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -3,11 +3,11 @@ package wow.doge.mygame.game.subsystems.ai
|
|||||||
import scala.collection.immutable.ArraySeq
|
import scala.collection.immutable.ArraySeq
|
||||||
|
|
||||||
import com.badlogic.gdx.ai.pfa.Connection
|
import com.badlogic.gdx.ai.pfa.Connection
|
||||||
import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph
|
|
||||||
import com.badlogic.gdx.ai.steer.Steerable
|
import com.badlogic.gdx.ai.steer.Steerable
|
||||||
import com.badlogic.gdx.math.Vector3
|
|
||||||
import com.badlogic.gdx.ai.utils.Location
|
|
||||||
import com.badlogic.gdx.ai.steer.behaviors.Arrive
|
import com.badlogic.gdx.ai.steer.behaviors.Arrive
|
||||||
|
import com.badlogic.gdx.ai.utils.Location
|
||||||
|
import com.badlogic.gdx.math.Vector3
|
||||||
|
import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph
|
||||||
// import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph
|
// import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph
|
||||||
// import scala.jdk.javaapi.CollectionConverters._
|
// import scala.jdk.javaapi.CollectionConverters._
|
||||||
|
|
||||||
|
@ -3,12 +3,14 @@ package wow.doge.mygame.game.subsystems.input
|
|||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
|
import cats.effect.concurrent.Ref
|
||||||
import com.jme3.input.InputManager
|
import com.jme3.input.InputManager
|
||||||
import com.jme3.input.KeyInput
|
import com.jme3.input.KeyInput
|
||||||
import com.jme3.input.MouseInput
|
import com.jme3.input.MouseInput
|
||||||
import com.jme3.input.controls.KeyTrigger
|
import com.jme3.input.controls.KeyTrigger
|
||||||
import com.jme3.input.controls.MouseAxisTrigger
|
import com.jme3.input.controls.MouseAxisTrigger
|
||||||
import monix.bio.UIO
|
import monix.bio.UIO
|
||||||
|
import monix.{eval => me}
|
||||||
import wow.doge.mygame.implicits._
|
import wow.doge.mygame.implicits._
|
||||||
import wow.doge.mygame.subsystems.events.EventBus
|
import wow.doge.mygame.subsystems.events.EventBus
|
||||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||||
@ -18,7 +20,7 @@ import wow.doge.mygame.utils.IOUtils._
|
|||||||
|
|
||||||
object GameInputHandler {
|
object GameInputHandler {
|
||||||
|
|
||||||
final case class Props(
|
final class Props(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
playerMovementEventBus: GameEventBus[PlayerMovementEvent],
|
playerMovementEventBus: GameEventBus[PlayerMovementEvent],
|
||||||
playerCameraEventBus: GameEventBus[PlayerCameraEvent]
|
playerCameraEventBus: GameEventBus[PlayerCameraEvent]
|
||||||
@ -27,7 +29,8 @@ object GameInputHandler {
|
|||||||
def begin =
|
def begin =
|
||||||
for {
|
for {
|
||||||
_ <- UIO(setupMovementKeys(inputManager))
|
_ <- UIO(setupMovementKeys(inputManager))
|
||||||
_ <- UIO(setupKeys(inputManager))
|
// _ <- UIO(setupAnalogMovementKeys)
|
||||||
|
_ <- UIO(setupCameraKeys())
|
||||||
_ <- toIO(
|
_ <- toIO(
|
||||||
generateMovementInputEvents(
|
generateMovementInputEvents(
|
||||||
inputManager,
|
inputManager,
|
||||||
@ -35,7 +38,7 @@ object GameInputHandler {
|
|||||||
).completedL.startAndForget
|
).completedL.startAndForget
|
||||||
)
|
)
|
||||||
_ <- toIO(
|
_ <- toIO(
|
||||||
generateRotateEvents(
|
generateAnalogMovementEvents(
|
||||||
inputManager,
|
inputManager,
|
||||||
playerMovementEventBus
|
playerMovementEventBus
|
||||||
).completedL.startAndForget
|
).completedL.startAndForget
|
||||||
@ -46,8 +49,10 @@ object GameInputHandler {
|
|||||||
playerCameraEventBus
|
playerCameraEventBus
|
||||||
).completedL.startAndForget
|
).completedL.startAndForget
|
||||||
)
|
)
|
||||||
|
_ <- toIO(
|
||||||
|
Ref.of[me.Task, Boolean](false).flatMap(value => cursorToggle(value))
|
||||||
|
)
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
|
||||||
|
|
||||||
def setupMovementKeys(inputManager: InputManager) =
|
def setupMovementKeys(inputManager: InputManager) =
|
||||||
inputManager.withEnumMappings(PlayerMovementInput) {
|
inputManager.withEnumMappings(PlayerMovementInput) {
|
||||||
@ -63,28 +68,65 @@ object GameInputHandler {
|
|||||||
Seq(new KeyTrigger(KeyInput.KEY_SPACE))
|
Seq(new KeyTrigger(KeyInput.KEY_SPACE))
|
||||||
}
|
}
|
||||||
|
|
||||||
def setupKeys(inputManager: InputManager) =
|
def setupAnalogMovementKeys() =
|
||||||
inputManager
|
inputManager.withEnumMappings(PlayerAnalogMovementInput) {
|
||||||
.withMapping(
|
case PlayerAnalogMovementInput.TurnRight =>
|
||||||
PlayerAnalogMovementInput.TurnRight.entryName,
|
Seq(new KeyTrigger(KeyInput.KEY_D))
|
||||||
new KeyTrigger(KeyInput.KEY_RIGHT),
|
case PlayerAnalogMovementInput.TurnLeft =>
|
||||||
new MouseAxisTrigger(MouseInput.AXIS_X, true)
|
Seq(new KeyTrigger(KeyInput.KEY_A))
|
||||||
)
|
}
|
||||||
.withMapping(
|
|
||||||
PlayerAnalogMovementInput.TurnLeft.entryName,
|
def setupCameraKeys() =
|
||||||
|
inputManager.withEnumMappings(PlayerCameraInput) {
|
||||||
|
case PlayerCameraInput.CameraRotateLeft =>
|
||||||
|
Seq(
|
||||||
new KeyTrigger(KeyInput.KEY_LEFT),
|
new KeyTrigger(KeyInput.KEY_LEFT),
|
||||||
new MouseAxisTrigger(MouseInput.AXIS_X, false)
|
new MouseAxisTrigger(MouseInput.AXIS_X, false)
|
||||||
)
|
)
|
||||||
.withMapping(
|
case PlayerCameraInput.CameraRotateRight =>
|
||||||
"CAMERA_UP",
|
Seq(
|
||||||
// new KeyTrigger(KeyInput.KEY_LEFT),
|
new KeyTrigger(KeyInput.KEY_RIGHT),
|
||||||
|
new MouseAxisTrigger(MouseInput.AXIS_X, true)
|
||||||
|
)
|
||||||
|
case PlayerCameraInput.CameraRotateUp =>
|
||||||
|
Seq(
|
||||||
|
new KeyTrigger(KeyInput.KEY_UP),
|
||||||
new MouseAxisTrigger(MouseInput.AXIS_Y, false)
|
new MouseAxisTrigger(MouseInput.AXIS_Y, false)
|
||||||
)
|
)
|
||||||
.withMapping(
|
case PlayerCameraInput.CameraRotateDown =>
|
||||||
"CAMERA_DOWN",
|
Seq(
|
||||||
// new KeyTrigger(KeyInput.KEY_LEFT),
|
new KeyTrigger(KeyInput.KEY_DOWN),
|
||||||
new MouseAxisTrigger(MouseInput.AXIS_Y, true)
|
new MouseAxisTrigger(MouseInput.AXIS_Y, true)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def cursorToggle(toggleRef: Ref[me.Task, Boolean]) =
|
||||||
|
for {
|
||||||
|
_ <- me.Task(
|
||||||
|
inputManager.withMapping(
|
||||||
|
MiscInput.ToggleCursor,
|
||||||
|
new KeyTrigger(KeyInput.KEY_Z)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ <-
|
||||||
|
inputManager
|
||||||
|
.enumEntryObservableAction(MiscInput.ToggleCursor)
|
||||||
|
.doOnNext(action =>
|
||||||
|
action.binding match {
|
||||||
|
case MiscInput.ToggleCursor =>
|
||||||
|
if (action.value) for {
|
||||||
|
value <- toggleRef.getAndUpdate(!_)
|
||||||
|
_ <- me.Task(inputManager.setCursorVisible(value))
|
||||||
|
} yield ()
|
||||||
|
else me.Task.unit
|
||||||
|
// case _ => me.Task.unit
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.completedL
|
||||||
|
.startAndForget
|
||||||
|
} yield ()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
def generateMovementInputEvents(
|
def generateMovementInputEvents(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
@ -99,74 +141,74 @@ object GameInputHandler {
|
|||||||
.doOnNext { action =>
|
.doOnNext { action =>
|
||||||
action.binding match {
|
action.binding match {
|
||||||
case PlayerMovementInput.WalkLeft =>
|
case PlayerMovementInput.WalkLeft =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkRight =>
|
case PlayerMovementInput.WalkRight =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkForward =>
|
case PlayerMovementInput.WalkForward =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkBackward =>
|
case PlayerMovementInput.WalkBackward =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.Jump =>
|
case PlayerMovementInput.Jump =>
|
||||||
if (action.value) {
|
if (action.value) {
|
||||||
toTask(
|
me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerJumped,
|
PlayerMovementEvent.PlayerJumped,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else monix.eval.Task.unit
|
} else me.Task.unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateRotateEvents(
|
def generateAnalogMovementEvents(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
playerMovementEventBus: ActorRef[
|
playerMovementEventBus: ActorRef[
|
||||||
EventBus.Command[PlayerMovementEvent]
|
EventBus.Command[PlayerMovementEvent]
|
||||||
]
|
]
|
||||||
) = {
|
) = {
|
||||||
val name = "rotateMovementEventsGenerator"
|
// val name = "analogMovementEventsGenerator"
|
||||||
inputManager
|
inputManager
|
||||||
.enumAnalogObservable(PlayerAnalogMovementInput)
|
.enumAnalogObservable(PlayerAnalogMovementInput)
|
||||||
.sample(1.millis)
|
.sample(1.millis)
|
||||||
.doOnNext(analogEvent =>
|
// .doOnNext(analogEvent =>
|
||||||
analogEvent.binding match {
|
// analogEvent.binding match {
|
||||||
case PlayerAnalogMovementInput.TurnRight =>
|
// case PlayerAnalogMovementInput.TurnRight =>
|
||||||
toTask(
|
// me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
// playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerRotatedRight,
|
// PlayerMovementEvent.PlayerTurnedRight,
|
||||||
name
|
// name
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
case PlayerAnalogMovementInput.TurnLeft =>
|
// case PlayerAnalogMovementInput.TurnLeft =>
|
||||||
toTask(
|
// me.Task(
|
||||||
playerMovementEventBus !! EventBus.Publish(
|
// playerMovementEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerRotatedLeft,
|
// PlayerMovementEvent.PlayerTurnedLeft,
|
||||||
name
|
// name
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateCameraEvents(
|
def generateCameraEvents(
|
||||||
@ -175,25 +217,38 @@ object GameInputHandler {
|
|||||||
) = {
|
) = {
|
||||||
val name = "cameraMovementEventsGenerator"
|
val name = "cameraMovementEventsGenerator"
|
||||||
inputManager
|
inputManager
|
||||||
.analogObservable("CAMERA_UP", "CAMERA_DOWN")
|
.enumAnalogObservable(PlayerCameraInput)
|
||||||
.sample(1.millis)
|
.sample(1.millis)
|
||||||
.doOnNext(analogEvent =>
|
.doOnNext(analogEvent =>
|
||||||
analogEvent.binding.name match {
|
analogEvent.binding match {
|
||||||
case "CAMERA_UP" =>
|
case PlayerCameraInput.CameraRotateLeft =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerCameraEventBus !! EventBus.Publish(
|
playerCameraEventBus ! EventBus.Publish(
|
||||||
|
PlayerCameraEvent.CameraLeft,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case PlayerCameraInput.CameraRotateRight =>
|
||||||
|
me.Task(
|
||||||
|
playerCameraEventBus ! EventBus.Publish(
|
||||||
|
PlayerCameraEvent.CameraRight,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case PlayerCameraInput.CameraRotateUp =>
|
||||||
|
me.Task(
|
||||||
|
playerCameraEventBus ! EventBus.Publish(
|
||||||
PlayerCameraEvent.CameraMovedUp,
|
PlayerCameraEvent.CameraMovedUp,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case "CAMERA_DOWN" =>
|
case PlayerCameraInput.CameraRotateDown =>
|
||||||
toTask(
|
me.Task(
|
||||||
playerCameraEventBus !! EventBus.Publish(
|
playerCameraEventBus ! EventBus.Publish(
|
||||||
PlayerCameraEvent.CameraMovedDown,
|
PlayerCameraEvent.CameraMovedDown,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case _ => monix.eval.Task.unit
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,33 @@ import enumeratum.EnumEntry._
|
|||||||
import enumeratum._
|
import enumeratum._
|
||||||
|
|
||||||
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
|
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
|
||||||
object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
final object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
case object WalkForward extends PlayerMovementInput
|
final case object WalkForward extends PlayerMovementInput
|
||||||
case object WalkRight extends PlayerMovementInput
|
final case object WalkRight extends PlayerMovementInput
|
||||||
case object WalkLeft extends PlayerMovementInput
|
final case object WalkLeft extends PlayerMovementInput
|
||||||
case object WalkBackward extends PlayerMovementInput
|
final case object WalkBackward extends PlayerMovementInput
|
||||||
case object Jump extends PlayerMovementInput
|
final case object Jump extends PlayerMovementInput
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
|
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
|
||||||
object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
case object TurnRight extends PlayerAnalogMovementInput
|
final case object TurnRight extends PlayerAnalogMovementInput
|
||||||
case object TurnLeft extends PlayerAnalogMovementInput
|
final case object TurnLeft extends PlayerAnalogMovementInput
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase
|
||||||
|
final object PlayerCameraInput extends Enum[PlayerCameraInput] {
|
||||||
|
val values = findValues
|
||||||
|
final case object CameraRotateLeft extends PlayerCameraInput
|
||||||
|
final case object CameraRotateRight extends PlayerCameraInput
|
||||||
|
final case object CameraRotateUp extends PlayerCameraInput
|
||||||
|
final case object CameraRotateDown extends PlayerCameraInput
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait MiscInput extends EnumEntry with UpperSnakecase
|
||||||
|
final object MiscInput extends Enum[MiscInput] {
|
||||||
|
val values = findValues
|
||||||
|
final case object ToggleCursor extends MiscInput
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import wow.doge.mygame.subsystems.movement.RotateDir
|
|||||||
|
|
||||||
trait CanMove[-A] {
|
trait CanMove[-A] {
|
||||||
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
|
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
|
||||||
def move(inst: A, direction: ImVector3f): Unit
|
def move(inst: A, direction: ImVector3f, speedFactor: Float = 20f): Unit
|
||||||
def location(inst: A): ImVector3f
|
def location(inst: A): ImVector3f
|
||||||
def jump(inst: A): Unit
|
def jump(inst: A): Unit
|
||||||
def stop(inst: A): Unit
|
def stop(inst: A): Unit
|
||||||
@ -24,12 +24,12 @@ object CanMove {
|
|||||||
new CanMove[BetterCharacterControl] {
|
new CanMove[BetterCharacterControl] {
|
||||||
override def move(
|
override def move(
|
||||||
inst: BetterCharacterControl,
|
inst: BetterCharacterControl,
|
||||||
direction: ImVector3f
|
direction: ImVector3f,
|
||||||
|
speedFactor: Float = 20f
|
||||||
): Unit = {
|
): Unit = {
|
||||||
// val dir = direction.mutable
|
val dir = direction.mutable.normalizeLocal()
|
||||||
// inst.setViewDirection(dir)
|
inst.setViewDirection(dir.negate())
|
||||||
// inst.setViewDirection(direction.mutable)
|
inst.setWalkDirection(dir.mult(speedFactor))
|
||||||
inst.setWalkDirection(direction.mutable.multLocal(50f))
|
|
||||||
}
|
}
|
||||||
override def location(inst: BetterCharacterControl) =
|
override def location(inst: BetterCharacterControl) =
|
||||||
inst.getSpatial().getLocalTranslation().immutable
|
inst.getSpatial().getLocalTranslation().immutable
|
||||||
@ -42,10 +42,10 @@ object CanMove {
|
|||||||
rotateDir match {
|
rotateDir match {
|
||||||
case RotateDir.Left =>
|
case RotateDir.Left =>
|
||||||
new Quaternion()
|
new Quaternion()
|
||||||
.fromAngleAxis(-5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
.fromAngleNormalAxis(5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
||||||
case RotateDir.Right =>
|
case RotateDir.Right =>
|
||||||
new Quaternion()
|
new Quaternion()
|
||||||
.fromAngleAxis(5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
.fromAngleAxis(-5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tmp = new Vector3f()
|
val tmp = new Vector3f()
|
||||||
@ -57,8 +57,12 @@ object CanMove {
|
|||||||
}
|
}
|
||||||
|
|
||||||
implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
|
implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
|
||||||
override def move(inst: Spatial, direction: ImVector3f): Unit = {
|
override def move(
|
||||||
inst.move(direction.mutable)
|
inst: Spatial,
|
||||||
|
direction: ImVector3f,
|
||||||
|
speedFactor: Float = 1f
|
||||||
|
): Unit = {
|
||||||
|
inst.move(direction.mutable multLocal speedFactor)
|
||||||
}
|
}
|
||||||
override def location(inst: Spatial) =
|
override def location(inst: Spatial) =
|
||||||
inst.getLocalTranslation().immutable
|
inst.getLocalTranslation().immutable
|
||||||
|
@ -4,6 +4,7 @@ import akka.actor.typed.Behavior
|
|||||||
import akka.actor.typed.scaladsl.ActorContext
|
import akka.actor.typed.scaladsl.ActorContext
|
||||||
import akka.actor.typed.scaladsl.Behaviors
|
import akka.actor.typed.scaladsl.Behaviors
|
||||||
import com.jme3.math.Vector3f
|
import com.jme3.math.Vector3f
|
||||||
|
import com.jme3.renderer.Camera
|
||||||
import com.softwaremill.quicklens._
|
import com.softwaremill.quicklens._
|
||||||
import wow.doge.mygame.game.subsystems.movement.CanMove
|
import wow.doge.mygame.game.subsystems.movement.CanMove
|
||||||
import wow.doge.mygame.implicits._
|
import wow.doge.mygame.implicits._
|
||||||
@ -27,15 +28,16 @@ object ImMovementActor {
|
|||||||
final case class MovedRight(pressed: Boolean) extends Movement
|
final case class MovedRight(pressed: Boolean) extends Movement
|
||||||
final case class MovedDown(pressed: Boolean) extends Movement
|
final case class MovedDown(pressed: Boolean) extends Movement
|
||||||
final case object Jump extends Movement
|
final case object Jump extends Movement
|
||||||
final case object RotateRight extends Movement
|
// final case object RotateRight extends Movement
|
||||||
final case object RotateLeft extends Movement
|
// final case object RotateLeft extends Movement
|
||||||
|
|
||||||
final case class Props[T: CanMove](
|
final class Props[T: CanMove](
|
||||||
enqueueR: Function1[() => Unit, Unit],
|
val enqueueR: Function1[() => Unit, Unit],
|
||||||
movable: T
|
val movable: T,
|
||||||
// playerMovementEventBus: ActorRef[
|
// playerMovementEventBus: ActorRef[
|
||||||
// EventBus.Command[PlayerMovementEvent]
|
// EventBus.Command[PlayerMovementEvent]
|
||||||
// ]
|
// ]
|
||||||
|
val camera: Camera
|
||||||
) {
|
) {
|
||||||
def create: Behavior[Command] =
|
def create: Behavior[Command] =
|
||||||
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
|
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
|
||||||
@ -78,25 +80,47 @@ class ImMovementActor[T](
|
|||||||
case Jump =>
|
case Jump =>
|
||||||
props.enqueueR(() => cm.jump(props.movable))
|
props.enqueueR(() => cm.jump(props.movable))
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case RotateLeft =>
|
|
||||||
props.enqueueR(() => cm.rotate(props.movable, RotateDir.Left))
|
|
||||||
Behaviors.same
|
|
||||||
case RotateRight =>
|
|
||||||
props.enqueueR(() => cm.rotate(props.movable, RotateDir.Right))
|
|
||||||
Behaviors.same
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case Tick =>
|
case Tick =>
|
||||||
val walkDir =
|
val walkDir =
|
||||||
getDirection(state.cardinalDir, ctx.log.trace)
|
getDirection2(state.cardinalDir, ctx.log.trace)
|
||||||
if (walkDir != ImVector3f.ZERO) {
|
// if (walkDir != ImVector3f.ZERO) {
|
||||||
val tmp = walkDir * 25f * (1f / 144)
|
val tmp = walkDir * 25f * (1f / 144)
|
||||||
props.enqueueR { () =>
|
props.enqueueR { () =>
|
||||||
cm.move(props.movable, tmp)
|
cm.move(props.movable, tmp)
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getDirection2(cardinalDir: CardinalDirection, debug: String => Unit) = {
|
||||||
|
val camDir =
|
||||||
|
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
|
||||||
|
val camLeft = props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
|
||||||
|
val dir = cardinalDir
|
||||||
|
val walkDir = {
|
||||||
|
val mutWalkDir = new Vector3f()
|
||||||
|
if (dir.up) {
|
||||||
|
debug("up")
|
||||||
|
mutWalkDir += camDir
|
||||||
|
}
|
||||||
|
if (dir.left) {
|
||||||
|
debug("left")
|
||||||
|
mutWalkDir += camLeft
|
||||||
|
}
|
||||||
|
if (dir.right) {
|
||||||
|
debug("right")
|
||||||
|
mutWalkDir += -camLeft
|
||||||
|
}
|
||||||
|
if (dir.down) {
|
||||||
|
debug("down")
|
||||||
|
mutWalkDir += -camDir
|
||||||
|
}
|
||||||
|
mutWalkDir.immutable
|
||||||
|
}
|
||||||
|
walkDir
|
||||||
|
}
|
||||||
}
|
}
|
||||||
object Methods {
|
object Methods {
|
||||||
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
|
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
|
||||||
|
@ -26,6 +26,7 @@ import com.jme3.input.controls.AnalogListener
|
|||||||
import com.jme3.input.controls.InputListener
|
import com.jme3.input.controls.InputListener
|
||||||
import com.jme3.input.controls.Trigger
|
import com.jme3.input.controls.Trigger
|
||||||
import com.jme3.light.Light
|
import com.jme3.light.Light
|
||||||
|
import com.jme3.material.Material
|
||||||
import com.jme3.math.Vector3f
|
import com.jme3.math.Vector3f
|
||||||
import com.jme3.scene.CameraNode
|
import com.jme3.scene.CameraNode
|
||||||
import com.jme3.scene.Geometry
|
import com.jme3.scene.Geometry
|
||||||
@ -50,7 +51,6 @@ import monix.reactive.OverflowStrategy
|
|||||||
import monix.reactive.observers.Subscriber
|
import monix.reactive.observers.Subscriber
|
||||||
import wow.doge.mygame.math.ImVector3f
|
import wow.doge.mygame.math.ImVector3f
|
||||||
import wow.doge.mygame.state.MyBaseState
|
import wow.doge.mygame.state.MyBaseState
|
||||||
import com.jme3.material.Material
|
|
||||||
|
|
||||||
final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
|
final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
|
||||||
final case class EnumActionEvent[T <: EnumEntry](
|
final case class EnumActionEvent[T <: EnumEntry](
|
||||||
@ -397,16 +397,68 @@ package object implicits {
|
|||||||
|
|
||||||
implicit final class InputManagerExt(private val inputManager: InputManager)
|
implicit final class InputManagerExt(private val inputManager: InputManager)
|
||||||
extends AnyVal {
|
extends AnyVal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new mapping to the given triggers.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The given mapping will be assigned to the given triggers, when
|
||||||
|
* any of the triggers given raise an event, the listeners
|
||||||
|
* registered to the mappings will receive appropriate events.
|
||||||
|
*
|
||||||
|
* @param mappingName The mapping name to assign.
|
||||||
|
* @param triggers The triggers to which the mapping is to be registered.
|
||||||
|
*/
|
||||||
def withMapping(mapping: String, triggers: Trigger*): InputManager = {
|
def withMapping(mapping: String, triggers: Trigger*): InputManager = {
|
||||||
inputManager.addMapping(mapping, triggers: _*)
|
inputManager.addMapping(mapping, triggers: _*)
|
||||||
inputManager
|
inputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def withMapping[T <: EnumEntry](
|
||||||
|
mapping: T,
|
||||||
|
triggers: Trigger*
|
||||||
|
): InputManager = {
|
||||||
|
inputManager.addMapping(mapping.entryName, triggers: _*)
|
||||||
|
inputManager
|
||||||
|
}
|
||||||
|
|
||||||
def withListener(listener: InputListener, mappings: String*) = {
|
def withListener(listener: InputListener, mappings: String*) = {
|
||||||
inputManager.addListener(listener, mappings: _*)
|
inputManager.addListener(listener, mappings: _*)
|
||||||
inputManager
|
inputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new mappings from the values of the given Enum
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The given mapping will be assigned to the given triggers, when
|
||||||
|
* any of the triggers given raise an event, the listeners
|
||||||
|
* registered to the mappings will receive appropriate events.
|
||||||
|
*
|
||||||
|
* @param mappingName The mapping name to assign.
|
||||||
|
* @param mappingFn Function from enum values to the sequence of trigers.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* {{{
|
||||||
|
*
|
||||||
|
* sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
|
||||||
|
* object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
||||||
|
* val values = findValues
|
||||||
|
* case object TurnRight extends PlayerAnalogMovementInput
|
||||||
|
* case object TurnLeft extends PlayerAnalogMovementInput
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* inputManager.withEnumMappings(PlayerAnalogMovementInput) {
|
||||||
|
* case PlayerAnalogMovementInput.TurnRight =>
|
||||||
|
* Seq(new KeyTrigger(KeyInput.KEY_RIGHT))
|
||||||
|
* case PlayerAnalogMovementInput.TurnLeft =>
|
||||||
|
* Seq(new KeyTrigger(KeyInput.KEY_LEFT))
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }}}
|
||||||
|
*/
|
||||||
def withEnumMappings[T <: EnumEntry](
|
def withEnumMappings[T <: EnumEntry](
|
||||||
mappingEnum: Enum[T]
|
mappingEnum: Enum[T]
|
||||||
)(mappingFn: T => Seq[Trigger]): InputManager = {
|
)(mappingFn: T => Seq[Trigger]): InputManager = {
|
||||||
@ -417,6 +469,15 @@ package object implicits {
|
|||||||
inputManager
|
inputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an observable which emits the given mappings as elements of an observable
|
||||||
|
*
|
||||||
|
* @param mappingNames
|
||||||
|
* @return Observable of action events
|
||||||
|
*
|
||||||
|
* @see [[ActionEvent]]
|
||||||
|
* @see [[enumObservableAction]]
|
||||||
|
*/
|
||||||
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
|
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
|
||||||
|
|
||||||
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
|
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
|
||||||
@ -443,6 +504,27 @@ package object implicits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Create an observable which emits the values of the given
|
||||||
|
* enum as elements of an observable
|
||||||
|
*
|
||||||
|
* @param mappingNames
|
||||||
|
* @return Observable of enum values
|
||||||
|
*
|
||||||
|
* @example {{{
|
||||||
|
* inputManager
|
||||||
|
* .enumObservableAction(PlayerMovementInput)
|
||||||
|
* .doOnNext { action =>
|
||||||
|
* action.binding match {
|
||||||
|
* case PlayerMovementInput.WalkLeft => Task {/* your actions */}
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }}}
|
||||||
|
*
|
||||||
|
* @see [[EnumActionEvent]]
|
||||||
|
* @see [[enumAnalogObservable]]
|
||||||
|
*/
|
||||||
def enumObservableAction[T <: EnumEntry](
|
def enumObservableAction[T <: EnumEntry](
|
||||||
mappingEnum: Enum[T]
|
mappingEnum: Enum[T]
|
||||||
): Observable[EnumActionEvent[T]] = {
|
): Observable[EnumActionEvent[T]] = {
|
||||||
@ -472,6 +554,36 @@ package object implicits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def enumEntryObservableAction[T <: EnumEntry](
|
||||||
|
mappingEnumEntry: T
|
||||||
|
): Observable[EnumActionEvent[T]] = {
|
||||||
|
|
||||||
|
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
|
||||||
|
val c = SingleAssignCancelable()
|
||||||
|
val al = new ActionListener {
|
||||||
|
override def onAction(
|
||||||
|
binding: String,
|
||||||
|
value: Boolean,
|
||||||
|
tpf: Float
|
||||||
|
): Unit = {
|
||||||
|
if (
|
||||||
|
sub.onNext(
|
||||||
|
EnumActionEvent(mappingEnumEntry, value, tpf)
|
||||||
|
) == Ack.Stop
|
||||||
|
) {
|
||||||
|
sub.onComplete()
|
||||||
|
c.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputManager.addListener(al, mappingEnumEntry.entryName)
|
||||||
|
|
||||||
|
c := Cancelable(() => inputManager.removeListener(al))
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
|
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
|
||||||
|
|
||||||
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
|
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
|
||||||
@ -666,6 +778,11 @@ package object implicits {
|
|||||||
def /(f: Float): ImVector3f = v.copy(v.x / f, v.y / f, v.z / f)
|
def /(f: Float): ImVector3f = v.copy(v.x / f, v.y / f, v.z / f)
|
||||||
def unary_- = v.copy(-v.x, -v.y, -v.z)
|
def unary_- = v.copy(-v.x, -v.y, -v.z)
|
||||||
def mutable = new Vector3f(v.x, v.y, v.z)
|
def mutable = new Vector3f(v.x, v.y, v.z)
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * alias for [[cross]] product
|
||||||
|
// */
|
||||||
|
// def |*|() = ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// val TasktoUIO = new FunctionK[Task, UIO] {
|
// val TasktoUIO = new FunctionK[Task, UIO] {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package wow.doge.mygame.math;
|
package wow.doge.mygame.math;
|
||||||
|
|
||||||
import Math.{abs, sqrt, pow}
|
import Math.{sqrt, pow}
|
||||||
|
|
||||||
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
|
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package wow.doge.mygame.subsystems.events
|
package wow.doge.mygame.subsystems.events
|
||||||
|
|
||||||
sealed trait PlayerMovementEvent
|
sealed trait PlayerEvent
|
||||||
object PlayerMovementEvent {
|
|
||||||
|
sealed trait PlayerMovementEvent extends PlayerEvent
|
||||||
|
final object PlayerMovementEvent {
|
||||||
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
|
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
|
||||||
final case class PlayerMovedRight(pressed: Boolean)
|
final case class PlayerMovedRight(pressed: Boolean)
|
||||||
extends PlayerMovementEvent
|
extends PlayerMovementEvent
|
||||||
@ -10,15 +12,15 @@ object PlayerMovementEvent {
|
|||||||
final case class PlayerMovedBackward(pressed: Boolean)
|
final case class PlayerMovedBackward(pressed: Boolean)
|
||||||
extends PlayerMovementEvent
|
extends PlayerMovementEvent
|
||||||
final case object PlayerJumped extends PlayerMovementEvent
|
final case object PlayerJumped extends PlayerMovementEvent
|
||||||
final case object PlayerRotatedRight extends PlayerMovementEvent
|
// final case object PlayerTurnedRight extends PlayerMovementEvent
|
||||||
final case object PlayerRotatedLeft extends PlayerMovementEvent
|
// final case object PlayerTurnedLeft extends PlayerMovementEvent
|
||||||
final case object PlayerCameraUp extends PlayerMovementEvent
|
|
||||||
final case object PlayerCameraDown extends PlayerMovementEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait PlayerCameraEvent
|
sealed trait PlayerCameraEvent extends PlayerEvent
|
||||||
|
|
||||||
object PlayerCameraEvent {
|
final object PlayerCameraEvent {
|
||||||
|
final case object CameraLeft extends PlayerCameraEvent
|
||||||
|
final case object CameraRight extends PlayerCameraEvent
|
||||||
final case object CameraMovedUp extends PlayerCameraEvent
|
final case object CameraMovedUp extends PlayerCameraEvent
|
||||||
final case object CameraMovedDown extends PlayerCameraEvent
|
final case object CameraMovedDown extends PlayerCameraEvent
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user