package wow.doge.mygame.state import scala.concurrent.duration.DurationInt import com.jme3.input.InputManager import com.jme3.input.KeyInput import com.jme3.input.controls.ActionListener import com.jme3.input.controls.KeyTrigger import com.jme3.math.Vector3f import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.Behavior import akka.actor.typed.ActorRef import com.jme3.scene.Geometry import akka.actor.typed.scaladsl.TimerScheduler import wow.doge.mygame.implicits._ class PlayerMovementState2( movementActor: ActorRef[MovementActor.Command], movementActorTimer: ActorRef[MovementActorTimer.Command], imMovementActor: ActorRef[ImMovementActor.Command], geom: Geometry ) extends MyBaseState with ActionListener { protected lazy val mat = MyMaterial( assetManager = assetManager, path = "Common/MatDefs/Misc/Unshaded.j3md" ) override protected[state] def onEnable(): Unit = {} override protected[state] def onDisable(): Unit = {} override protected def init(): Unit = { setupKeys(inputManager) geom.setMaterial(mat) rootNode.attachChild(geom) // movementActorTimer ! MovementActorTimer.Start(geom, cam) // movementActorTimer ! MovementActorTimer.Start } // def system = // simpleApp.getStateManager.getState(classOf[ActorSystemState]).system // def player = system.actorSelection("/user/player") //.resolveOne(1.second) var lastDir: Vector3f = Vector3f.UNIT_X override def update(tpf: Float) = { // val direction = new Vector3f() // direction.multLocal(10 * tpf) // if (direction.length() > 0f) { // // player ! PlayerMove(direction) // lastDir = direction.normalize // } // if (shoot) { // shoot = false // // player ! Shoot(lastDir) // } // movementActor ! MovementActor.Tick(tpf, geom, cam) imMovementActor ! ImMovementActor.Tick(tpf) // movementActorTimer ! MovementActorTimer.Update(tpf) } def setupKeys(inputManager: InputManager) = { inputManager .withMapping( "Left", // new KeyTrigger(KeyInput.KEY_A), new KeyTrigger(KeyInput.KEY_LEFT) ) .withMapping( "Right", // new KeyTrigger(KeyInput.KEY_D), new KeyTrigger(KeyInput.KEY_RIGHT) ) inputManager.addMapping( "Up", // new KeyTrigger(KeyInput.KEY_W), new KeyTrigger(KeyInput.KEY_UP) ) inputManager.addMapping( "Down", // new KeyTrigger(KeyInput.KEY_S), new KeyTrigger(KeyInput.KEY_DOWN) ) inputManager.addMapping( "Space", new KeyTrigger(KeyInput.KEY_SPACE), new KeyTrigger(KeyInput.KEY_H) ) inputManager.addMapping( "Reset", new KeyTrigger(KeyInput.KEY_R), new KeyTrigger(KeyInput.KEY_RETURN) ) inputManager .withListener(this, "Left") .withListener(this, "Right") inputManager.addListener(this, "Up") inputManager.addListener(this, "Down") inputManager.addListener(this, "Space") inputManager.addListener(this, "Reset") } def onAction(binding: String, value: Boolean, tpf: Float) = binding match { case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value) case "Right" => imMovementActor ! ImMovementActor.MovedRight(value) case "Up" => imMovementActor ! ImMovementActor.MovedUp(value) case "Down" => imMovementActor ! ImMovementActor.MovedDown(value) case "Space" => case _ => } } final case class CardinalDirection( left: Boolean = false, right: Boolean = false, up: Boolean = false, down: Boolean = false ) object MovementActor { sealed trait Command // final case class Tick(tpf: Float, geom: Geometry, cam: Camera) extends Command // final case class Tick(tpf: Float) extends Command final case object Tick extends Command sealed trait Movement extends Command final case class MovedLeft(pressed: Boolean) extends Movement final case class MovedUp(pressed: Boolean) extends Movement final case class MovedRight(pressed: Boolean) extends Movement final case class MovedDown(pressed: Boolean) extends Movement final case class Props(app: com.jme3.app.Application, geom: Geometry) /** * Internal state of the actor * * @param cardinalDir Immutable, can be shared as is * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor */ final case class State( cardinalDir: CardinalDirection = CardinalDirection(), walkDirection: Vector3f = Vector3f.UNIT_X ) def apply(props: Props): Behavior[Command] = Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State())) } class MovementActor( ctx: ActorContext[MovementActor.Command], props: MovementActor.Props ) { import MovementActor._ import com.softwaremill.quicklens._ def receive(state: MovementActor.State): Behavior[Command] = Behaviors.receiveMessage { msg => msg match { case m: Movement => m match { case MovedLeft(pressed) => receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) case MovedUp(pressed) => receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) case MovedRight(pressed) => receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) case MovedDown(pressed) => receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) } case Tick => val camDir = props.app.getCamera.getDirection().clone().multLocal(0.6f) val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f) val walkDir = state.walkDirection.set(0, 0, 0) // val walkDir = new Vector3f val dir = state.cardinalDir if (dir.up) { ctx.log.debug("up") // ctx.log.debug(Thread.currentThread().getName()) // walkDir.addLocal(0, 0, -1) walkDir += camDir } if (dir.left) { ctx.log.debug("left") // walkDir.addLocal(-1, 0, 0) walkDir.addLocal(camLeft) } if (dir.right) { ctx.log.debug("right") // walkDir.addLocal(1, 0, 0) walkDir.addLocal(camLeft.negateLocal()) } if (dir.down) { ctx.log.debug("down") walkDir.addLocal(camDir.negateLocal()) // walkDir.addLocal(0, 0, 1) } // (dir.up, dir.down, dir.left, dir.right) match { // case (true, false, true, false) => // case _ => // } walkDir.multLocal(2f) // walkDir.multLocal(100f) // .multLocal(tpf) // val v = props.geom.getLocalTranslation() // props.geom.setLocalTranslation( // (v += walkDir) // ) props.app.enqueue(new Runnable { override def run(): Unit = { // geom.setLocalTranslation(walkDir) val v = props.geom.getLocalTranslation() props.geom.setLocalTranslation( (v += walkDir) ) } }) Behaviors.same // receive(state = state.modify(_.walkDirection).setTo(walkDir)) } } } object MovementActorTimer { sealed trait Command final case object Start extends Command final case object Update extends Command private case object Send extends Command case object TimerKey final case class Props( timers: TimerScheduler[MovementActorTimer.Command], target: ActorRef[MovementActor.Command] ) final case class State() def apply(target: ActorRef[MovementActor.Command]) = Behaviors.withTimers[Command] { timers => new MovementActorTimer(Props(timers, target)).idle() } } class MovementActorTimer( props: MovementActorTimer.Props ) { import MovementActorTimer._ // import com.softwaremill.quicklens._ def idle(): Behavior[Command] = Behaviors.receiveMessage { msg => msg match { case Start => props.timers.startTimerWithFixedDelay( Send, 10.millis ) active() case _ => Behaviors.unhandled } } def active(): Behavior[Command] = Behaviors.receiveMessage { msg => msg match { case Update => active() case Send => props.target ! MovementActor.Tick Behaviors.same case _ => Behaviors.unhandled } } }