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.

294 lines
8.8 KiB

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