Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
8.5 KiB

4 years ago
  1. package wow.doge.mygame.state
  2. import scala.concurrent.duration.DurationInt
  3. import com.jme3.input.InputManager
  4. import com.jme3.input.KeyInput
  5. import com.jme3.input.controls.ActionListener
  6. import com.jme3.input.controls.KeyTrigger
  7. import com.jme3.math.Vector3f
  8. import akka.actor.typed.scaladsl.ActorContext
  9. import akka.actor.typed.scaladsl.Behaviors
  10. import akka.actor.typed.Behavior
  11. import akka.actor.typed.ActorRef
  12. import com.jme3.scene.Geometry
  13. import akka.actor.typed.scaladsl.TimerScheduler
  14. import wow.doge.mygame.implicits._
  15. class PlayerMovementState2(
  16. movementActor: ActorRef[MovementActor.Command],
  17. movementActorTimer: ActorRef[MovementActorTimer.Command],
  18. imMovementActor: ActorRef[ImMovementActor.Command],
  19. geom: Geometry
  20. ) extends MyBaseState
  21. with ActionListener {
  22. protected lazy val mat = MyMaterial(
  23. assetManager = assetManager,
  24. path = "Common/MatDefs/Misc/Unshaded.j3md"
  25. )
  26. override protected[state] def onEnable(): Unit = {}
  27. override protected[state] def onDisable(): Unit = {}
  28. override protected def init(): Unit = {
  29. setupKeys(inputManager)
  30. geom.setMaterial(mat)
  31. rootNode.attachChild(geom)
  32. // movementActorTimer ! MovementActorTimer.Start(geom, cam)
  33. // movementActorTimer ! MovementActorTimer.Start
  34. }
  35. // def system =
  36. // simpleApp.getStateManager.getState(classOf[ActorSystemState]).system
  37. // def player = system.actorSelection("/user/player") //.resolveOne(1.second)
  38. var lastDir: Vector3f = Vector3f.UNIT_X
  39. override def update(tpf: Float) = {
  40. // val direction = new Vector3f()
  41. // direction.multLocal(10 * tpf)
  42. // if (direction.length() > 0f) {
  43. // // player ! PlayerMove(direction)
  44. // lastDir = direction.normalize
  45. // }
  46. // if (shoot) {
  47. // shoot = false
  48. // // player ! Shoot(lastDir)
  49. // }
  50. // movementActor ! MovementActor.Tick(tpf, geom, cam)
  51. imMovementActor ! ImMovementActor.Tick(tpf)
  52. // movementActorTimer ! MovementActorTimer.Update(tpf)
  53. }
  54. def setupKeys(inputManager: InputManager) = {
  55. inputManager
  56. .withMapping(
  57. "Left",
  58. // new KeyTrigger(KeyInput.KEY_A),
  59. new KeyTrigger(KeyInput.KEY_LEFT)
  60. )
  61. .withMapping(
  62. "Right",
  63. // new KeyTrigger(KeyInput.KEY_D),
  64. new KeyTrigger(KeyInput.KEY_RIGHT)
  65. )
  66. inputManager.addMapping(
  67. "Up",
  68. // new KeyTrigger(KeyInput.KEY_W),
  69. new KeyTrigger(KeyInput.KEY_UP)
  70. )
  71. inputManager.addMapping(
  72. "Down",
  73. // new KeyTrigger(KeyInput.KEY_S),
  74. new KeyTrigger(KeyInput.KEY_DOWN)
  75. )
  76. inputManager.addMapping(
  77. "Space",
  78. new KeyTrigger(KeyInput.KEY_SPACE),
  79. new KeyTrigger(KeyInput.KEY_H)
  80. )
  81. inputManager.addMapping(
  82. "Reset",
  83. new KeyTrigger(KeyInput.KEY_R),
  84. new KeyTrigger(KeyInput.KEY_RETURN)
  85. )
  86. inputManager
  87. .withListener(this, "Left")
  88. .withListener(this, "Right")
  89. inputManager.addListener(this, "Up")
  90. inputManager.addListener(this, "Down")
  91. inputManager.addListener(this, "Space")
  92. inputManager.addListener(this, "Reset")
  93. }
  94. def onAction(binding: String, value: Boolean, tpf: Float) =
  95. binding match {
  96. case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value)
  97. case "Right" => imMovementActor ! ImMovementActor.MovedRight(value)
  98. case "Up" => imMovementActor ! ImMovementActor.MovedUp(value)
  99. case "Down" => imMovementActor ! ImMovementActor.MovedDown(value)
  100. case "Space" =>
  101. case _ =>
  102. }
  103. }
  104. final case class CardinalDirection(
  105. left: Boolean = false,
  106. right: Boolean = false,
  107. up: Boolean = false,
  108. down: Boolean = false
  109. )
  110. object MovementActor {
  111. sealed trait Command
  112. // final case class Tick(tpf: Float, geom: Geometry, cam: Camera) extends Command
  113. // final case class Tick(tpf: Float) extends Command
  114. final case object Tick extends Command
  115. sealed trait Movement extends Command
  116. final case class MovedLeft(pressed: Boolean) extends Movement
  117. final case class MovedUp(pressed: Boolean) extends Movement
  118. final case class MovedRight(pressed: Boolean) extends Movement
  119. final case class MovedDown(pressed: Boolean) extends Movement
  120. final case class Props(app: com.jme3.app.Application, geom: Geometry)
  121. /**
  122. * Internal state of the actor
  123. *
  124. * @param cardinalDir Immutable, can be shared as is
  125. * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
  126. */
  127. final case class State(
  128. cardinalDir: CardinalDirection = CardinalDirection(),
  129. walkDirection: Vector3f = Vector3f.UNIT_X
  130. )
  131. def apply(props: Props): Behavior[Command] =
  132. Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))
  133. }
  134. class MovementActor(
  135. ctx: ActorContext[MovementActor.Command],
  136. props: MovementActor.Props
  137. ) {
  138. import MovementActor._
  139. import com.softwaremill.quicklens._
  140. def receive(state: MovementActor.State): Behavior[Command] =
  141. Behaviors.receiveMessage { msg =>
  142. msg match {
  143. case m: Movement =>
  144. m match {
  145. case MovedLeft(pressed) =>
  146. receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
  147. case MovedUp(pressed) =>
  148. receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
  149. case MovedRight(pressed) =>
  150. receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
  151. case MovedDown(pressed) =>
  152. receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
  153. }
  154. case Tick =>
  155. val camDir =
  156. props.app.getCamera.getDirection().clone().multLocal(0.6f)
  157. val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
  158. val walkDir = state.walkDirection.set(0, 0, 0)
  159. // val walkDir = new Vector3f
  160. val dir = state.cardinalDir
  161. if (dir.up) {
  162. ctx.log.debug("up")
  163. // ctx.log.debug(Thread.currentThread().getName())
  164. // walkDir.addLocal(0, 0, -1)
  165. walkDir += camDir
  166. }
  167. if (dir.left) {
  168. ctx.log.debug("left")
  169. // walkDir.addLocal(-1, 0, 0)
  170. walkDir.addLocal(camLeft)
  171. }
  172. if (dir.right) {
  173. ctx.log.debug("right")
  174. // walkDir.addLocal(1, 0, 0)
  175. walkDir.addLocal(camLeft.negateLocal())
  176. }
  177. if (dir.down) {
  178. ctx.log.debug("down")
  179. walkDir.addLocal(camDir.negateLocal())
  180. // walkDir.addLocal(0, 0, 1)
  181. }
  182. // (dir.up, dir.down, dir.left, dir.right) match {
  183. // case (true, false, true, false) =>
  184. // case _ =>
  185. // }
  186. walkDir.multLocal(2f)
  187. // walkDir.multLocal(100f)
  188. // .multLocal(tpf)
  189. // val v = props.geom.getLocalTranslation()
  190. // props.geom.setLocalTranslation(
  191. // (v += walkDir)
  192. // )
  193. props.app.enqueue(new Runnable {
  194. override def run(): Unit = {
  195. // geom.setLocalTranslation(walkDir)
  196. val v = props.geom.getLocalTranslation()
  197. props.geom.setLocalTranslation(
  198. (v += walkDir)
  199. )
  200. }
  201. })
  202. Behaviors.same
  203. // receive(state = state.modify(_.walkDirection).setTo(walkDir))
  204. }
  205. }
  206. }
  207. object MovementActorTimer {
  208. sealed trait Command
  209. final case object Start extends Command
  210. final case object Update extends Command
  211. private case object Send extends Command
  212. case object TimerKey
  213. final case class Props(
  214. timers: TimerScheduler[MovementActorTimer.Command],
  215. target: ActorRef[MovementActor.Command]
  216. )
  217. final case class State()
  218. def apply(target: ActorRef[MovementActor.Command]) =
  219. Behaviors.withTimers[Command] { timers =>
  220. new MovementActorTimer(Props(timers, target)).idle()
  221. }
  222. }
  223. class MovementActorTimer(
  224. props: MovementActorTimer.Props
  225. ) {
  226. import MovementActorTimer._
  227. // import com.softwaremill.quicklens._
  228. def idle(): Behavior[Command] =
  229. Behaviors.receiveMessage { msg =>
  230. msg match {
  231. case Start =>
  232. props.timers.startTimerWithFixedDelay(
  233. Send,
  234. 10.millis
  235. )
  236. active()
  237. case _ => Behaviors.unhandled
  238. }
  239. }
  240. def active(): Behavior[Command] =
  241. Behaviors.receiveMessage { msg =>
  242. msg match {
  243. case Update => active()
  244. case Send =>
  245. props.target ! MovementActor.Tick
  246. Behaviors.same
  247. case _ => Behaviors.unhandled
  248. }
  249. }
  250. }