Many changes

Enhanced NPC state machine
Made player movement direction depend on camera direction
This commit is contained in:
Rohan Sircar 2020-12-03 19:04:53 +05:30
parent 73d657952f
commit 88293cebde
17 changed files with 717 additions and 322 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ project/plugins/project/
metals.sbt
.metals
.bloop
.ammonite
# Scala-IDE specific
.scala_dependencies

View File

@ -10,21 +10,28 @@ 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.NpcMovementActor2
import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.PlayerControllerTags
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.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils
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(
logger: Logger[Task],
@ -189,9 +193,23 @@ class MainAppDelegate(
_ <- createPlayerController(appScheduler)
.absorbWith(e => DummyException("boom"))
.onErrorRestart(3)
johnActor <- createNpc(appScheduler, "John").executeOn(appScheduler)
_ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
_ <- 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 ()
def createPlayerController(
@ -199,28 +217,49 @@ class MainAppDelegate(
): IO[PlayerController.Error, Unit] = {
val playerPos = ImVector3f.ZERO
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
lazy val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
val camNode =
PlayerController.Defaults
.defaultCamerNode(camera, playerPos)
.taggedWith[PlayerControllerTags.PlayerCameraNode]
val mbPlayerNode = PlayerController.Defaults
// lazy val camNode =
// PlayerController.Defaults
// .defaultCamerNode(camera, playerPos)
// .taggedWith[PlayerControllerTags.PlayerCameraNode]
lazy val mbPlayerNode = PlayerController.Defaults
.defaultPlayerNode(
assetManager,
modelPath,
playerPos,
camNode,
// camNode
playerPhysicsControl
)
lazy 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 createNpc(
def createTestNpc(
appScheduler: monix.execution.Scheduler,
npcName: String
) =
@ -243,7 +282,7 @@ class MainAppDelegate(
assetManager,
initialPos,
npcPhysicsControl,
"John"
npcName
)
val npcActorTask = AkkaUtils.spawnActorL2(
NpcActorSupervisor
@ -251,14 +290,14 @@ class MainAppDelegate(
new NpcMovementActor2.Props(
enqueueR,
initialPos,
tickEventBus,
// tickEventBus,
npcPhysicsControl
).create,
npcName,
initialPos
)
.create,
s"${npcName}-npcMovementActorSupervisor"
s"${npcName}-npcActorSupervisor"
)
// .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 = {}
}

View File

@ -67,10 +67,7 @@ class SimpleAppExt(
object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable): Unit =
enqueueScala(() => command.run())
// enqueue(command)
// new SingleThreadEventExecutor()
// sys.addShutdownHook(JMEExecutorService.shutdown())
enqueue(command)
}
lazy val scheduler = Scheduler(JMEExecutorService)

View File

@ -0,0 +1,6 @@
package wow.doge.mygame.game.entities
class EntityId(val value: String) extends AnyVal
object EntityIds {
val CameraPivot = new EntityId("CameraPivot")
}

View File

@ -1,35 +1,43 @@
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.Behavior
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
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.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
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 {
sealed trait Command
case class Move(pos: ImVector3f) extends Command
private case class UpdatePosition(pos: ImVector3f) extends Command
final case class Move(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
case object MovementTick extends Command
private case object NoOp extends Command
final case class Props(
npcMovementActorBehavior: Behavior[NpcMovementActor2.Command],
@ -43,115 +51,186 @@ object NpcActorSupervisor {
s"npc-${npcName}-NpcMovementActor"
)
new NpcActorSupervisor(ctx, this)
.idle(State(npcMovementActor, initialPos))
new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
.idle(State())
}
}
final case class State(
npcMovementActor: ActorRef[NpcMovementActor2.Command],
currentPos: ImVector3f
)
final case class Children(
npcMovementActor: ActorRef[NpcMovementActor2.Command]
)
}
class NpcActorSupervisor(
ctx: ActorContext[NpcActorSupervisor.Command],
props: NpcActorSupervisor.Props
props: NpcActorSupervisor.Props,
children: NpcActorSupervisor.Children
) {
import NpcActorSupervisor._
implicit val timeout = Timeout(1.second)
private val movementTimer = ctx.spawn(
GenericTimerActor
.Props(
children.npcMovementActor,
NpcMovementActor2.MovementTick,
100.millis
)
.create,
s"npc-John-NpcActorTimer"
)
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
Behaviors.receiveMessage[Command] {
case Move(pos) => {
state.npcMovementActor ! NpcMovementActor2.Move(pos)
val movementTimer = ctx.spawn(
GenericTimerActor.Props(ctx.self, MovementTick, 100.millis).create,
s"npc-${props.npcName}-NpcActorTimer"
)
movementTimer ! GenericTimerActor.Start
moving(state, pos, movementTimer)
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) =>
ctx.log.warn(err.getMessage())
Behaviors.same
case _ => Behaviors.unhandled
}
case LogError(err) =>
ctx.log.warn(err.getMessage())
Behaviors.same
case _ => Behaviors.unhandled
}
def moving(
state: State,
targetPos: ImVector3f,
movementTimer: ActorRef[GenericTimerActor.Command]
signal: CancelableFuture[NpcMovementActor2.DoneMoving.type]
): Behavior[NpcActorSupervisor.Command] =
Behaviors.receiveMessagePartial[Command] {
case LogError(err) =>
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())
Behaviors.setup { _ =>
movementTimer ! GenericTimerActor.Start
ctx.ask(state.npcMovementActor, NpcMovementActor2.AskPosition(_)) {
case Success(value) =>
UpdatePosition(value)
// ctx
// .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
// _.fold(LogError(_), MovementResponse(_))
// )
ctx.pipeToSelf(signal) {
case Success(value) => DoneMoving
case Failure(exception) => LogError(exception)
}
Behaviors.receiveMessagePartial[Command] {
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
moving(state, targetPos, movementTimer)
}
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 {
case object DoneMoving
sealed trait Command
case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
case object MovementTick 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](
val enqueueR: Function1[() => Unit, Unit],
val initialPos: ImVector3f,
val tickEventBus: GameEventBus[TickEvent],
// val tickEventBus: GameEventBus[TickEvent],
val movable: T
) {
def create =
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](
ctx: ActorContext[NpcMovementActor2.Command],
props: NpcMovementActor2.Props[T]
) {
)(implicit cm: CanMove[T]) {
import NpcMovementActor2._
def location = cm.location(props.movable)
def receive(
state: State
)(implicit cm: CanMove[T]): Behavior[NpcMovementActor2.Command] =
Behaviors.receiveMessage[Command] {
): Behavior[NpcMovementActor2.Command] =
Behaviors.receiveMessagePartial {
case AskPosition(replyTo) =>
replyTo ! cm.location(props.movable)
replyTo ! location
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 =>
ctx.log.debug(
"Position at Stop = " + cm.location(props.movable).toString
"Position at Stop = " + location.toString
)
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
}
}
}

View File

@ -30,7 +30,7 @@ object PlayerActorSupervisor {
playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]],
tickEventBus: GameEventBus[TickEvent],
imMovementActorBehavior: Behavior[ImMovementActor.Command],
playerCamELBehavior: Behavior[PlayerCameraEvent]
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
) {
def create[T: CanMove](movable: T) =
Behaviors.logMessages(
@ -50,23 +50,29 @@ object PlayerActorSupervisor {
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementActorChild"
)
val playerCameraActor =
ctx.spawn(playerCameraActorBehavior, "playerCameraActor")
val playerCameraEl = ctx.spawn(
PlayerCameraEventListener(playerCameraActor),
"playerCameraActorEl"
)
ctx.spawn(
PlayerMovementActor
.Props(movementActor, playerMovementEventBus, tickEventBus)
.Props(
movementActor,
playerCameraActor,
playerMovementEventBus,
tickEventBus
)
.create,
"playerMovementAcor"
)
lazy val playerCameraHandler = {
ctx.spawn(
Behaviors
.supervise(playerCamELBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"playerCameraHandler"
)
}
//init actors
playerCameraEventBus ! EventBus.Subscribe(playerCameraHandler)
playerCameraEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor(
ctx,
@ -100,6 +106,7 @@ object PlayerMovementActor {
sealed trait Command
final case class Props(
movementActor: ActorRef[ImMovementActor.Command],
playerCameraActor: ActorRef[PlayerCameraActor.Command],
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
],
@ -118,14 +125,13 @@ object PlayerMovementActor {
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
val renderTickEl =
ctx.spawn(renderTickElBehavior, "playerMovementTickListener")
playerMovementEventBus ! EventBus.Subscribe(
playerMovementEl
)
playerMovementEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
Behaviors.receiveMessage { msg => Behaviors.same }
}

View File

@ -1,16 +1,40 @@
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.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 {
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 =
Behaviors.setup[Command] { ctx =>
new PlayerCameraActor(ctx, this).receive(State.empty)
}
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerCameraActor].underlying
),
Behaviors.setup[Command] { ctx =>
new PlayerCameraActor(ctx, this).receive()
}
)
}
case class State()
@ -23,8 +47,36 @@ class PlayerCameraActor(
props: PlayerCameraActor.Props
) {
import PlayerCameraActor._
def receive(state: State) =
Behaviors.receiveMessage[Command] {
case _ => Behaviors.same
def receive(
rotationBuf: Quaternion = new Quaternion(),
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
}
}

View File

@ -1,7 +1,6 @@
package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef
import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
@ -15,9 +14,7 @@ import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
import com.jme3.scene.Node
import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.scene.shape.Box
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.IO
@ -38,6 +35,7 @@ import wow.doge.mygame.utils.AkkaUtils
object PlayerControllerTags {
sealed trait PlayerTag
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
object PlayerController {
@ -60,7 +58,9 @@ object PlayerController {
appScheduler: monix.execution.Scheduler,
playerNode: Node @@ PlayerControllerTags.PlayerTag,
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
tickEventBus: GameEventBus[TickEvent]
cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
tickEventBus: GameEventBus[TickEvent],
camera: Camera
)(implicit timeout: Timeout, scheduler: Scheduler) {
val create: IO[Error, Unit] =
(for {
@ -71,17 +71,31 @@ object PlayerController {
playerMovementEventBus,
playerCameraEventBus,
tickEventBus,
ImMovementActor
.Props(enqueueR, playerPhysicsControl)
.create,
wireWith(PlayerCameraEventListener.apply _)
new ImMovementActor.Props(
enqueueR,
playerPhysicsControl,
camera
).create,
// wireWith(PlayerCameraEventListener.apply _)
// PlayerCameraEventListener()
new PlayerCameraActor.Props(
cameraPivotNode,
enqueueR,
playerNode.getWorldTranslation _
).create
).create(playerPhysicsControl)
)
_ <- Task(rootNode += playerNode)
_ <- IO {
physicsSpace += playerNode
physicsSpace += playerPhysicsControl
// rootNode += cameraNode
cameraPivotNode += cameraNode
// playerNode += cameraPivotNode
rootNode += cameraPivotNode
}
_ <- Task(rootNode += playerNode)
} yield ())
.onErrorHandleWith(e => IO.raiseError(GenericError(e.getMessage())))
.executeOn(appScheduler)
@ -129,9 +143,27 @@ object PlayerController {
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
def defaultCamerNode(cam: Camera, playerPos: ImVector3f) =
new CameraNode("CameraNode", cam)
.withControlDir(ControlDirection.SpatialToCamera)
// new CameraControl(cam) {
// override def controlUpdate(tpf: Float) = {
// 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))
.withLookAt(playerPos, ImVector3f.UNIT_Y)
@ -139,14 +171,14 @@ object PlayerController {
assetManager: AssetManager,
modelPath: os.RelPath,
playerPos: ImVector3f,
camNode: CameraNode,
// camNode: CameraNode,
playerPhysicsControl: BetterCharacterControl
) =
Either
.catchNonFatal(
Node("PlayerNode")
.withChildren(
camNode,
// camNode,
assetManager
.loadModel(modelPath)
.asInstanceOf[Node]
@ -187,45 +219,7 @@ object PlayerController {
}
}
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,
// _
// )
// )
}
object Methods {}
// camNode <- IO(
// _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))

View File

@ -3,18 +3,17 @@ package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef
import akka.actor.typed.LogOptions
import akka.actor.typed.scaladsl.Behaviors
import com.jme3.scene.CameraNode
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
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.movement.ImMovementActor
object PlayerMovementEventListener {
import PlayerMovementEvent._
def apply(movementActor: ActorRef[ImMovementActor.Command]) =
def apply(
movementActor: ActorRef[ImMovementActor.Command]
) =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
@ -22,7 +21,7 @@ object PlayerMovementEventListener {
Logger[PlayerMovementEventListener.type].underlying
),
Behaviors.setup[PlayerMovementEvent](ctx =>
Behaviors.receiveMessagePartial {
Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) =>
movementActor ! ImMovementActor.MovedLeft(pressed)
Behaviors.same
@ -38,27 +37,21 @@ object PlayerMovementEventListener {
case PlayerJumped =>
movementActor ! ImMovementActor.Jump
Behaviors.same
case PlayerRotatedRight =>
movementActor ! ImMovementActor.RotateRight
Behaviors.same
case PlayerRotatedLeft =>
movementActor ! ImMovementActor.RotateLeft
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
// case PlayerTurnedRight =>
// movementActor ! ImMovementActor.RotateRight
// Behaviors.same
// case PlayerTurnedLeft =>
// movementActor ! ImMovementActor.RotateLeft
// Behaviors.same
}
)
)
}
object PlayerCameraEventListener {
import PlayerCameraEvent._
def apply(
camNode: CameraNode,
enqueueR: Function1[() => Unit, Unit]
playerCameraActor: ActorRef[PlayerCameraActor.Command]
) =
Behaviors.logMessages(
LogOptions()
@ -69,16 +62,17 @@ object PlayerCameraEventListener {
Behaviors.setup[PlayerCameraEvent](ctx =>
Behaviors.receiveMessagePartial {
case CameraMovedUp =>
enqueueR(() => {
playerCameraActor ! PlayerCameraActor.RotateUp
camNode.move(0, 1, 0)
})
Behaviors.same
case CameraMovedDown =>
enqueueR(() => {
camNode.move(0, -1, 0)
})
playerCameraActor ! PlayerCameraActor.RotateDown
Behaviors.same
case CameraLeft =>
playerCameraActor ! PlayerCameraActor.RotateLeft
Behaviors.same
case CameraRight =>
playerCameraActor ! PlayerCameraActor.RotateRight
Behaviors.same
}
)

View File

@ -3,11 +3,11 @@ package wow.doge.mygame.game.subsystems.ai
import scala.collection.immutable.ArraySeq
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.math.Vector3
import com.badlogic.gdx.ai.utils.Location
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 scala.jdk.javaapi.CollectionConverters._

View File

@ -3,12 +3,14 @@ package wow.doge.mygame.game.subsystems.input
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import cats.effect.concurrent.Ref
import com.jme3.input.InputManager
import com.jme3.input.KeyInput
import com.jme3.input.MouseInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.input.controls.MouseAxisTrigger
import monix.bio.UIO
import monix.{eval => me}
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -18,7 +20,7 @@ import wow.doge.mygame.utils.IOUtils._
object GameInputHandler {
final case class Props(
final class Props(
inputManager: InputManager,
playerMovementEventBus: GameEventBus[PlayerMovementEvent],
playerCameraEventBus: GameEventBus[PlayerCameraEvent]
@ -27,7 +29,8 @@ object GameInputHandler {
def begin =
for {
_ <- UIO(setupMovementKeys(inputManager))
_ <- UIO(setupKeys(inputManager))
// _ <- UIO(setupAnalogMovementKeys)
_ <- UIO(setupCameraKeys())
_ <- toIO(
generateMovementInputEvents(
inputManager,
@ -35,7 +38,7 @@ object GameInputHandler {
).completedL.startAndForget
)
_ <- toIO(
generateRotateEvents(
generateAnalogMovementEvents(
inputManager,
playerMovementEventBus
).completedL.startAndForget
@ -46,46 +49,85 @@ object GameInputHandler {
playerCameraEventBus
).completedL.startAndForget
)
_ <- toIO(
Ref.of[me.Task, Boolean](false).flatMap(value => cursorToggle(value))
)
} yield ()
def setupMovementKeys(inputManager: InputManager) =
inputManager.withEnumMappings(PlayerMovementInput) {
case PlayerMovementInput.WalkRight =>
Seq(new KeyTrigger(KeyInput.KEY_D))
case PlayerMovementInput.WalkLeft =>
Seq(new KeyTrigger(KeyInput.KEY_A))
case PlayerMovementInput.WalkForward =>
Seq(new KeyTrigger(KeyInput.KEY_W))
case PlayerMovementInput.WalkBackward =>
Seq(new KeyTrigger(KeyInput.KEY_S))
case PlayerMovementInput.Jump =>
Seq(new KeyTrigger(KeyInput.KEY_SPACE))
}
def setupAnalogMovementKeys() =
inputManager.withEnumMappings(PlayerAnalogMovementInput) {
case PlayerAnalogMovementInput.TurnRight =>
Seq(new KeyTrigger(KeyInput.KEY_D))
case PlayerAnalogMovementInput.TurnLeft =>
Seq(new KeyTrigger(KeyInput.KEY_A))
}
def setupCameraKeys() =
inputManager.withEnumMappings(PlayerCameraInput) {
case PlayerCameraInput.CameraRotateLeft =>
Seq(
new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_X, false)
)
case PlayerCameraInput.CameraRotateRight =>
Seq(
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)
)
case PlayerCameraInput.CameraRotateDown =>
Seq(
new KeyTrigger(KeyInput.KEY_DOWN),
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 setupMovementKeys(inputManager: InputManager) =
inputManager.withEnumMappings(PlayerMovementInput) {
case PlayerMovementInput.WalkRight =>
Seq(new KeyTrigger(KeyInput.KEY_D))
case PlayerMovementInput.WalkLeft =>
Seq(new KeyTrigger(KeyInput.KEY_A))
case PlayerMovementInput.WalkForward =>
Seq(new KeyTrigger(KeyInput.KEY_W))
case PlayerMovementInput.WalkBackward =>
Seq(new KeyTrigger(KeyInput.KEY_S))
case PlayerMovementInput.Jump =>
Seq(new KeyTrigger(KeyInput.KEY_SPACE))
}
def setupKeys(inputManager: InputManager) =
inputManager
.withMapping(
PlayerAnalogMovementInput.TurnRight.entryName,
new KeyTrigger(KeyInput.KEY_RIGHT),
new MouseAxisTrigger(MouseInput.AXIS_X, true)
)
.withMapping(
PlayerAnalogMovementInput.TurnLeft.entryName,
new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_X, false)
)
.withMapping(
"CAMERA_UP",
// new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_Y, false)
)
.withMapping(
"CAMERA_DOWN",
// new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_Y, true)
)
def generateMovementInputEvents(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
@ -99,74 +141,74 @@ object GameInputHandler {
.doOnNext { action =>
action.binding match {
case PlayerMovementInput.WalkLeft =>
toTask(
playerMovementEventBus !! EventBus.Publish(
me.Task(
playerMovementEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
name
)
)
case PlayerMovementInput.WalkRight =>
toTask(
playerMovementEventBus !! EventBus.Publish(
me.Task(
playerMovementEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
name
)
)
case PlayerMovementInput.WalkForward =>
toTask(
playerMovementEventBus !! EventBus.Publish(
me.Task(
playerMovementEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
name
)
)
case PlayerMovementInput.WalkBackward =>
toTask(
playerMovementEventBus !! EventBus.Publish(
me.Task(
playerMovementEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
name
)
)
case PlayerMovementInput.Jump =>
if (action.value) {
toTask(
playerMovementEventBus !! EventBus.Publish(
me.Task(
playerMovementEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerJumped,
name
)
)
} else monix.eval.Task.unit
} else me.Task.unit
}
}
}
def generateRotateEvents(
def generateAnalogMovementEvents(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) = {
val name = "rotateMovementEventsGenerator"
// val name = "analogMovementEventsGenerator"
inputManager
.enumAnalogObservable(PlayerAnalogMovementInput)
.sample(1.millis)
.doOnNext(analogEvent =>
analogEvent.binding match {
case PlayerAnalogMovementInput.TurnRight =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerRotatedRight,
name
)
)
case PlayerAnalogMovementInput.TurnLeft =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerRotatedLeft,
name
)
)
}
)
// .doOnNext(analogEvent =>
// analogEvent.binding match {
// case PlayerAnalogMovementInput.TurnRight =>
// me.Task(
// playerMovementEventBus ! EventBus.Publish(
// PlayerMovementEvent.PlayerTurnedRight,
// name
// )
// )
// case PlayerAnalogMovementInput.TurnLeft =>
// me.Task(
// playerMovementEventBus ! EventBus.Publish(
// PlayerMovementEvent.PlayerTurnedLeft,
// name
// )
// )
// }
// )
}
def generateCameraEvents(
@ -175,25 +217,38 @@ object GameInputHandler {
) = {
val name = "cameraMovementEventsGenerator"
inputManager
.analogObservable("CAMERA_UP", "CAMERA_DOWN")
.enumAnalogObservable(PlayerCameraInput)
.sample(1.millis)
.doOnNext(analogEvent =>
analogEvent.binding.name match {
case "CAMERA_UP" =>
toTask(
playerCameraEventBus !! EventBus.Publish(
analogEvent.binding match {
case PlayerCameraInput.CameraRotateLeft =>
me.Task(
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,
name
)
)
case "CAMERA_DOWN" =>
toTask(
playerCameraEventBus !! EventBus.Publish(
case PlayerCameraInput.CameraRotateDown =>
me.Task(
playerCameraEventBus ! EventBus.Publish(
PlayerCameraEvent.CameraMovedDown,
name
)
)
case _ => monix.eval.Task.unit
}
)
}

View File

@ -3,18 +3,33 @@ import enumeratum.EnumEntry._
import enumeratum._
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
object PlayerMovementInput extends Enum[PlayerMovementInput] {
final object PlayerMovementInput extends Enum[PlayerMovementInput] {
val values = findValues
case object WalkForward extends PlayerMovementInput
case object WalkRight extends PlayerMovementInput
case object WalkLeft extends PlayerMovementInput
case object WalkBackward extends PlayerMovementInput
case object Jump extends PlayerMovementInput
final case object WalkForward extends PlayerMovementInput
final case object WalkRight extends PlayerMovementInput
final case object WalkLeft extends PlayerMovementInput
final case object WalkBackward extends PlayerMovementInput
final case object Jump extends PlayerMovementInput
}
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
val values = findValues
case object TurnRight extends PlayerAnalogMovementInput
case object TurnLeft extends PlayerAnalogMovementInput
final case object TurnRight 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
}

View File

@ -12,7 +12,7 @@ import wow.doge.mygame.subsystems.movement.RotateDir
trait CanMove[-A] {
// 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 jump(inst: A): Unit
def stop(inst: A): Unit
@ -24,12 +24,12 @@ object CanMove {
new CanMove[BetterCharacterControl] {
override def move(
inst: BetterCharacterControl,
direction: ImVector3f
direction: ImVector3f,
speedFactor: Float = 20f
): Unit = {
// val dir = direction.mutable
// inst.setViewDirection(dir)
// inst.setViewDirection(direction.mutable)
inst.setWalkDirection(direction.mutable.multLocal(50f))
val dir = direction.mutable.normalizeLocal()
inst.setViewDirection(dir.negate())
inst.setWalkDirection(dir.mult(speedFactor))
}
override def location(inst: BetterCharacterControl) =
inst.getSpatial().getLocalTranslation().immutable
@ -42,10 +42,10 @@ object CanMove {
rotateDir match {
case RotateDir.Left =>
new Quaternion()
.fromAngleAxis(-5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
.fromAngleNormalAxis(5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
case RotateDir.Right =>
new Quaternion()
.fromAngleAxis(5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
.fromAngleAxis(-5 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
}
val tmp = new Vector3f()
@ -57,8 +57,12 @@ object CanMove {
}
implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
override def move(inst: Spatial, direction: ImVector3f): Unit = {
inst.move(direction.mutable)
override def move(
inst: Spatial,
direction: ImVector3f,
speedFactor: Float = 1f
): Unit = {
inst.move(direction.mutable multLocal speedFactor)
}
override def location(inst: Spatial) =
inst.getLocalTranslation().immutable

View File

@ -4,6 +4,7 @@ import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.jme3.math.Vector3f
import com.jme3.renderer.Camera
import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
@ -27,15 +28,16 @@ object ImMovementActor {
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case object Jump extends Movement
final case object RotateRight extends Movement
final case object RotateLeft extends Movement
// final case object RotateRight extends Movement
// final case object RotateLeft extends Movement
final case class Props[T: CanMove](
enqueueR: Function1[() => Unit, Unit],
movable: T
final class Props[T: CanMove](
val enqueueR: Function1[() => Unit, Unit],
val movable: T,
// playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent]
// ]
val camera: Camera
) {
def create: Behavior[Command] =
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
@ -78,25 +80,47 @@ class ImMovementActor[T](
case Jump =>
props.enqueueR(() => cm.jump(props.movable))
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 =>
val walkDir =
getDirection(state.cardinalDir, ctx.log.trace)
if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * (1f / 144)
props.enqueueR { () =>
cm.move(props.movable, tmp)
}
getDirection2(state.cardinalDir, ctx.log.trace)
// if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * (1f / 144)
props.enqueueR { () =>
cm.move(props.movable, tmp)
}
// }
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 {
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {

View File

@ -26,6 +26,7 @@ import com.jme3.input.controls.AnalogListener
import com.jme3.input.controls.InputListener
import com.jme3.input.controls.Trigger
import com.jme3.light.Light
import com.jme3.material.Material
import com.jme3.math.Vector3f
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
@ -50,7 +51,6 @@ import monix.reactive.OverflowStrategy
import monix.reactive.observers.Subscriber
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyBaseState
import com.jme3.material.Material
final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
final case class EnumActionEvent[T <: EnumEntry](
@ -397,16 +397,68 @@ package object implicits {
implicit final class InputManagerExt(private val inputManager: InputManager)
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 = {
inputManager.addMapping(mapping, triggers: _*)
inputManager
}
def withMapping[T <: EnumEntry](
mapping: T,
triggers: Trigger*
): InputManager = {
inputManager.addMapping(mapping.entryName, triggers: _*)
inputManager
}
def withListener(listener: InputListener, mappings: String*) = {
inputManager.addListener(listener, mappings: _*)
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](
mappingEnum: Enum[T]
)(mappingFn: T => Seq[Trigger]): InputManager = {
@ -417,6 +469,15 @@ package object implicits {
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] = {
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](
mappingEnum: Enum[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] = {
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 unary_- = v.copy(-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] {

View File

@ -1,6 +1,6 @@
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)

View File

@ -1,7 +1,9 @@
package wow.doge.mygame.subsystems.events
sealed trait PlayerMovementEvent
object PlayerMovementEvent {
sealed trait PlayerEvent
sealed trait PlayerMovementEvent extends PlayerEvent
final object PlayerMovementEvent {
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
final case class PlayerMovedRight(pressed: Boolean)
extends PlayerMovementEvent
@ -10,15 +12,15 @@ object PlayerMovementEvent {
final case class PlayerMovedBackward(pressed: Boolean)
extends PlayerMovementEvent
final case object PlayerJumped extends PlayerMovementEvent
final case object PlayerRotatedRight extends PlayerMovementEvent
final case object PlayerRotatedLeft extends PlayerMovementEvent
final case object PlayerCameraUp extends PlayerMovementEvent
final case object PlayerCameraDown extends PlayerMovementEvent
// final case object PlayerTurnedRight extends PlayerMovementEvent
// final case object PlayerTurnedLeft 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 CameraMovedDown extends PlayerCameraEvent
}