diff --git a/.gitignore b/.gitignore index 3dcaadb..3acbc8f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ project/plugins/project/ metals.sbt .metals .bloop +.ammonite # Scala-IDE specific .scala_dependencies diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index 133a0d1..5b4a37b 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -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 = {} +} diff --git a/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala b/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala index ce541b1..7a0e85d 100644 --- a/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala +++ b/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/game/entities/EntityIds.scala b/src/main/scala/wow/doge/mygame/game/entities/EntityIds.scala new file mode 100644 index 0000000..dfd0db5 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/entities/EntityIds.scala @@ -0,0 +1,6 @@ +package wow.doge.mygame.game.entities + +class EntityId(val value: String) extends AnyVal +object EntityIds { + val CameraPivot = new EntityId("CameraPivot") +} diff --git a/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala index dd97885..b7870b9 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala @@ -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) + case StopMoving => + ctx.log.debug( + "Position at Stop = " + location.toString ) - receive(state = state.copy(currentPos = cm.location(props.movable))) + props.enqueueR(() => cm.stop(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 = " + 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 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 + } } } diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala index 0863763..e2c6eeb 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala @@ -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 } } diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala index 0b3b966..1d15510 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala @@ -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 } } diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala index e22ca0e..2f02ab9 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala @@ -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)) diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala index 9389198..310e7e5 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala @@ -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 } ) diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala b/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala index a5765df..3b097f0 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala @@ -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._ diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala index 82379ec..1f1999b 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala @@ -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,45 +49,84 @@ 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 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 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 generateMovementInputEvents( inputManager: InputManager, @@ -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 } ) } diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/InputEnums.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/InputEnums.scala index 3292220..843eef5 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/input/InputEnums.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/InputEnums.scala @@ -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 } diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala index f4459c6..27e7422 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala index cd2c338..4f00ac1 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala @@ -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) = { diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index f08761b..41f6c20 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/src/main/scala/wow/doge/mygame/implicits/package.scala @@ -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. + * + *

+ * 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 + * + *

+ * 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 { } } + /** + *

+ * 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] { diff --git a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala index b3dfe3d..cdd0a61 100644 --- a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala +++ b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala b/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala index 6486c91..97ef1e5 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala @@ -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 }