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.

225 lines
7.7 KiB

  1. package wow.doge.mygame.game.entities
  2. import akka.actor.typed.ActorRef
  3. import akka.actor.typed.Behavior
  4. import akka.actor.typed.SupervisorStrategy
  5. import akka.actor.typed.scaladsl.ActorContext
  6. import akka.actor.typed.scaladsl.Behaviors
  7. import wow.doge.mygame.subsystems.events.Event
  8. import wow.doge.mygame.subsystems.events.EventBus
  9. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  10. import wow.doge.mygame.subsystems.events.TickEvent
  11. import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
  12. import wow.doge.mygame.subsystems.movement.ImMovementActor
  13. import wow.doge.mygame.subsystems.events.EntityMovementEvent
  14. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
  15. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
  16. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
  17. import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
  18. import wow.doge.mygame.math.ImVector3f
  19. import wow.doge.mygame.game.subsystems.movement.CanMove
  20. import wow.doge.mygame.implicits._
  21. import akka.util.Timeout
  22. import scala.concurrent.duration._
  23. import scala.util.Success
  24. import scala.util.Failure
  25. object NpcActorSupervisor {
  26. sealed trait Command
  27. case class Move(pos: ImVector3f) extends Command
  28. private case class UpdatePosition(pos: ImVector3f) extends Command
  29. private case class LogError(err: Throwable) extends Command
  30. case object MovementTick extends Command
  31. final case class Props(
  32. npcMovementActorBehavior: Behavior[NpcMovementActor2.Command],
  33. npcName: String,
  34. initialPos: ImVector3f
  35. ) {
  36. def create =
  37. Behaviors.setup[Command] { ctx =>
  38. val npcMovementActor = ctx.spawn(
  39. (npcMovementActorBehavior),
  40. s"npc-${npcName}-NpcMovementActor"
  41. )
  42. new NpcActorSupervisor(ctx, this)
  43. .idle(State(npcMovementActor, initialPos))
  44. }
  45. }
  46. final case class State(
  47. npcMovementActor: ActorRef[NpcMovementActor2.Command],
  48. currentPos: ImVector3f
  49. )
  50. }
  51. class NpcActorSupervisor(
  52. ctx: ActorContext[NpcActorSupervisor.Command],
  53. props: NpcActorSupervisor.Props
  54. ) {
  55. import NpcActorSupervisor._
  56. implicit val timeout = Timeout(1.second)
  57. def idle(state: State): Behavior[NpcActorSupervisor.Command] =
  58. Behaviors.receiveMessage[Command] {
  59. case Move(pos) => {
  60. state.npcMovementActor ! NpcMovementActor2.Move(pos)
  61. val movementTimer = ctx.spawn(
  62. GenericTimerActor.Props(ctx.self, MovementTick, 100.millis).create,
  63. s"npc-${props.npcName}-NpcActorTimer"
  64. )
  65. movementTimer ! GenericTimerActor.Start
  66. moving(state, pos, movementTimer)
  67. }
  68. case LogError(err) =>
  69. ctx.log.warn(err.getMessage())
  70. Behaviors.same
  71. case _ => Behaviors.unhandled
  72. }
  73. def moving(
  74. state: State,
  75. targetPos: ImVector3f,
  76. movementTimer: ActorRef[GenericTimerActor.Command]
  77. ): Behavior[NpcActorSupervisor.Command] =
  78. Behaviors.receiveMessagePartial[Command] {
  79. case LogError(err) =>
  80. ctx.log.warn(err.getMessage())
  81. Behaviors.same
  82. case Move(pos) => moving(state, pos, movementTimer)
  83. case UpdatePosition(pos) =>
  84. ctx.log.trace("Current pos = " + state.currentPos.toString())
  85. moving(state.copy(currentPos = pos), targetPos, movementTimer)
  86. case MovementTick =>
  87. val dst = ImVector3f.dst(targetPos, state.currentPos)
  88. if (dst <= 10f) {
  89. state.npcMovementActor ! NpcMovementActor2.StopMoving
  90. movementTimer ! GenericTimerActor.Stop
  91. idle(state)
  92. } else {
  93. // ctx.log.debug("Difference = " + dst.toString())
  94. // ctx.log.debug("Current pos = " + state.currentPos.toString())
  95. ctx.ask(state.npcMovementActor, NpcMovementActor2.AskPosition(_)) {
  96. case Success(value) =>
  97. UpdatePosition(value)
  98. case Failure(exception) => LogError(exception)
  99. }
  100. // Behaviors.same
  101. moving(state, targetPos, movementTimer)
  102. }
  103. }
  104. }
  105. object NpcMovementActor2 {
  106. sealed trait Command
  107. case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
  108. case object StopMoving extends Command
  109. case class Move(target: ImVector3f) extends Command
  110. final class Props[T: CanMove](
  111. val enqueueR: Function1[() => Unit, Unit],
  112. val initialPos: ImVector3f,
  113. val tickEventBus: GameEventBus[TickEvent],
  114. val movable: T
  115. ) {
  116. def create =
  117. Behaviors.setup[Command] { ctx =>
  118. new NpcMovementActor2(ctx, this).receive(State(initialPos))
  119. }
  120. }
  121. final case class State(currentPos: ImVector3f)
  122. }
  123. class NpcMovementActor2[T](
  124. ctx: ActorContext[NpcMovementActor2.Command],
  125. props: NpcMovementActor2.Props[T]
  126. ) {
  127. import NpcMovementActor2._
  128. def receive(
  129. state: State
  130. )(implicit cm: CanMove[T]): Behavior[NpcMovementActor2.Command] =
  131. Behaviors.receiveMessage[Command] {
  132. case AskPosition(replyTo) =>
  133. replyTo ! cm.location(props.movable)
  134. Behaviors.same
  135. case Move(target: ImVector3f) =>
  136. props.enqueueR(() =>
  137. cm.move(props.movable, (target - state.currentPos) * 0.005f)
  138. )
  139. receive(state = state.copy(currentPos = cm.location(props.movable)))
  140. case StopMoving =>
  141. ctx.log.debug(
  142. "Position at Stop = " + cm.location(props.movable).toString
  143. )
  144. props.enqueueR(() => cm.stop(props.movable))
  145. receive(state = state.copy(currentPos = cm.location(props.movable)))
  146. }
  147. }
  148. object NpcMovementActor {
  149. sealed trait Command
  150. final case class Props(
  151. imMovementActorBehavior: Behavior[ImMovementActor.Command],
  152. npcName: String,
  153. // movementActor: ActorRef[ImMovementActor.Command],
  154. mainEventBus: ActorRef[
  155. EventBus.Command[Event]
  156. ],
  157. tickEventBus: GameEventBus[TickEvent]
  158. ) {
  159. def create: Behavior[Command] =
  160. Behaviors.setup { ctx =>
  161. val movementActor = ctx.spawn(
  162. Behaviors
  163. .supervise(imMovementActorBehavior)
  164. .onFailure[Exception](SupervisorStrategy.restart),
  165. s"npc-${npcName}-MovementActorChild"
  166. )
  167. val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
  168. case event: EntityMovementEvent =>
  169. event match {
  170. case MovedLeft(name, pressed) =>
  171. if (name == npcName)
  172. movementActor ! ImMovementActor.MovedLeft(pressed)
  173. Behaviors.same
  174. case MovedUp(name, pressed) =>
  175. if (name == npcName)
  176. movementActor ! ImMovementActor.MovedUp(pressed)
  177. Behaviors.same
  178. case MovedRight(name, pressed) =>
  179. if (name == npcName)
  180. movementActor ! ImMovementActor.MovedRight(pressed)
  181. Behaviors.same
  182. case MovedDown(name, pressed) =>
  183. if (name == npcName)
  184. movementActor ! ImMovementActor.MovedDown(pressed)
  185. Behaviors.same
  186. }
  187. }
  188. val npcMovementEl = ctx.spawn(
  189. Behaviors
  190. .supervise(npcMovementElBehavior)
  191. .onFailure[Exception](SupervisorStrategy.restart),
  192. s"npc-${npcName}-MovementEventHandler"
  193. )
  194. val renderTickElBehavior =
  195. Behaviors.receiveMessage[RenderTick.type] {
  196. case RenderTick =>
  197. movementActor ! ImMovementActor.Tick
  198. Behaviors.same
  199. }
  200. val renderTickEl =
  201. ctx.spawn(
  202. renderTickElBehavior,
  203. s"npc-${npcName}-MovementTickListener"
  204. )
  205. mainEventBus ! EventBus.Subscribe(npcMovementEl)
  206. tickEventBus ! EventBus.Subscribe(renderTickEl)
  207. Behaviors.receiveMessage { msg => Behaviors.same }
  208. }
  209. }
  210. }