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.sbt
.metals .metals
.bloop .bloop
.ammonite
# Scala-IDE specific # Scala-IDE specific
.scala_dependencies .scala_dependencies

View File

@ -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 = {}
}

View File

@ -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)

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 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)
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] = def idle(state: State): Behavior[NpcActorSupervisor.Command] =
Behaviors.receiveMessage[Command] { Behaviors.setup { _ =>
case Move(pos) => { ctx.log.info("Inside Idle State")
state.npcMovementActor ! NpcMovementActor2.Move(pos) Behaviors.receiveMessage[Command] {
val movementTimer = ctx.spawn( case m @ Move(pos) =>
GenericTimerActor.Props(ctx.self, MovementTick, 100.millis).create, ctx.ask(
s"npc-${props.npcName}-NpcActorTimer" children.npcMovementActor,
) NpcMovementActor2.MoveTo(pos, _)
movementTimer ! GenericTimerActor.Start ) {
moving(state, pos, movementTimer) 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( 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)
}
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) case Failure(exception) => LogError(exception)
} }
// Behaviors.same Behaviors.same
moving(state, targetPos, movementTimer) 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
}
} }
} }

View File

@ -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 }
} }

View File

@ -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.setup[Command] { ctx => Behaviors.logMessages(
new PlayerCameraActor(ctx, this).receive(State.empty) LogOptions()
} .withLevel(Level.TRACE)
.withLogger(
Logger[PlayerCameraActor].underlying
),
Behaviors.setup[Command] { ctx =>
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
} }
} }

View File

@ -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))

View File

@ -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
} }
) )

View File

@ -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._

View File

@ -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,46 +49,85 @@ 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) =
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( def generateMovementInputEvents(
inputManager: InputManager, inputManager: InputManager,
playerMovementEventBus: ActorRef[ playerMovementEventBus: ActorRef[
@ -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
} }
) )
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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) = {

View File

@ -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] {

View File

@ -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)

View File

@ -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
} }