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.

297 lines
8.8 KiB

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