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.

268 lines
9.0 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package wow.doge.mygame.game.entities
  2. import scala.concurrent.duration._
  3. import akka.actor.typed.ActorRef
  4. import akka.actor.typed.Behavior
  5. import akka.actor.typed.LogOptions
  6. import akka.actor.typed.PostStop
  7. import akka.actor.typed.SupervisorStrategy
  8. import akka.actor.typed.scaladsl.ActorContext
  9. import akka.actor.typed.scaladsl.Behaviors
  10. import com.typesafe.scalalogging.Logger
  11. import org.slf4j.event.Level
  12. import wow.doge.mygame.Dispatchers
  13. import wow.doge.mygame.implicits._
  14. import wow.doge.mygame.subsystems.events.EventBus
  15. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  16. import wow.doge.mygame.subsystems.events.PlayerEvent
  17. import wow.doge.mygame.subsystems.events.TickEvent
  18. import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
  19. import wow.doge.mygame.subsystems.movement.ImMovementActor
  20. import scala.util.Success
  21. import scala.util.Failure
  22. import akka.util.Timeout
  23. import monix.reactive.Observable
  24. import monix.reactive.subjects.ConcurrentSubject
  25. import monix.execution.Scheduler
  26. import monix.reactive.OverflowStrategy
  27. object PlayerActorSupervisor {
  28. type Ref = ActorRef[PlayerActorSupervisor.Command]
  29. sealed trait Status
  30. object Status {
  31. case object Alive extends Status
  32. case object Dead extends Status
  33. }
  34. sealed trait Command
  35. case class TakeDamage(value: Int) extends Command
  36. case class Heal(value: Int) extends Command
  37. case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command
  38. case class GetStatus(replyTo: ActorRef[Status]) extends Command
  39. case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]])
  40. extends Command
  41. private case object Die extends Command
  42. private case class DamageResponse(response: (Boolean, StatsActor.State))
  43. extends Command
  44. // private case class InternalTakeDamage(old: Int, value: Int) extends Command
  45. private case class LogError(ex: Throwable) extends Command
  46. class Props(
  47. val playerEventBus: GameEventBus[PlayerEvent],
  48. val tickEventBus: GameEventBus[TickEvent],
  49. val imMovementActorBehavior: Behavior[ImMovementActor.Command],
  50. val scheduler: Scheduler
  51. ) {
  52. def behavior =
  53. Behaviors.logMessages(
  54. LogOptions()
  55. .withLevel(Level.DEBUG)
  56. .withLogger(
  57. Logger[PlayerActorSupervisor].underlying
  58. ),
  59. Behaviors
  60. .setup[Command] { ctx =>
  61. ctx.log.infoP("Starting PlayerActor")
  62. // spawn children actors
  63. val playerMovementActor =
  64. ctx.spawnN(
  65. Behaviors
  66. .supervise(imMovementActorBehavior)
  67. .onFailure[Exception](
  68. SupervisorStrategy.restart.withLimit(2, 100.millis)
  69. ),
  70. Dispatchers.jmeDispatcher
  71. )
  72. val playerStatsActor =
  73. ctx.spawnN(new StatsActor.Props(100, 100).behavior)
  74. val playerMovementEl = ctx.spawnN(
  75. Behaviors
  76. .supervise(PlayerMovementEventListener(playerMovementActor))
  77. .onFailure[Exception](
  78. SupervisorStrategy.restart.withLimit(2, 100.millis)
  79. )
  80. )
  81. val renderTickEl = {
  82. val behavior: Behavior[RenderTick.type] =
  83. Behaviors.setup(ctx =>
  84. Behaviors
  85. .receiveMessage[RenderTick.type] {
  86. case RenderTick =>
  87. playerMovementActor ! ImMovementActor.Tick
  88. // playerCameraActor ! PlayerCameraActor.Tick
  89. Behaviors.same
  90. }
  91. .receiveSignal {
  92. case (_, PostStop) =>
  93. ctx.log.infoP("stopped")
  94. Behaviors.same
  95. }
  96. )
  97. ctx.spawn(behavior, "playerMovementTickListener")
  98. }
  99. //init listeners
  100. playerEventBus ! EventBus.Subscribe(playerMovementEl)
  101. tickEventBus ! EventBus.Subscribe(renderTickEl)
  102. new PlayerActorSupervisor(
  103. ctx,
  104. this,
  105. Children(playerMovementActor, playerStatsActor),
  106. ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler)
  107. ).aliveState
  108. }
  109. )
  110. }
  111. case class Children(
  112. movementActor: ActorRef[ImMovementActor.Command],
  113. statsActor: ActorRef[StatsActor.Command]
  114. )
  115. }
  116. class PlayerActorSupervisor(
  117. ctx: ActorContext[PlayerActorSupervisor.Command],
  118. props: PlayerActorSupervisor.Props,
  119. children: PlayerActorSupervisor.Children,
  120. statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
  121. ) {
  122. import PlayerActorSupervisor._
  123. implicit val timeout = Timeout(1.second)
  124. val aliveState =
  125. Behaviors
  126. .receiveMessage[Command] {
  127. case TakeDamage(value) =>
  128. // children.movementActor ! ImMovementActor.MovedDown(true)
  129. // ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
  130. // case Success(status) => InternalTakeDamage(status.hp, value)
  131. // case Failure(ex) => LogError(ex)
  132. // }
  133. ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
  134. case Success(response) => DamageResponse(response)
  135. case Failure(ex) => LogError(ex)
  136. }
  137. Behaviors.same
  138. case CurrentStats(replyTo) =>
  139. // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  140. children.statsActor ! StatsActor.CurrentStats(replyTo)
  141. Behaviors.same
  142. case Heal(value) =>
  143. children.statsActor ! StatsActor.Heal(value)
  144. Behaviors.same
  145. case GetStatus(replyTo) =>
  146. replyTo ! Status.Alive
  147. Behaviors.same
  148. // case _ => Behaviors.unhandled
  149. // case InternalTakeDamage(hp, damage) =>
  150. // if (hp - damage <= 0) dead
  151. // else {
  152. // children.statsActor ! StatsActor.TakeDamage(damage)
  153. // Behaviors.same
  154. // }
  155. case GetStatsObservable(replyTo) =>
  156. replyTo ! statsSubject
  157. Behaviors.same
  158. case DamageResponse(response) =>
  159. response match {
  160. case (dead, state) =>
  161. if (dead) ctx.self ! Die
  162. statsSubject.onNext(state)
  163. }
  164. Behaviors.same
  165. case Die => deadState
  166. case LogError(ex) =>
  167. ctx.log.error(ex.getMessage)
  168. Behaviors.same
  169. }
  170. .receiveSignal {
  171. case (_, PostStop) =>
  172. ctx.log.infoP("stopped")
  173. statsSubject.onComplete()
  174. Behaviors.same
  175. }
  176. val deadState = Behaviors
  177. .receiveMessage[Command] {
  178. // case TakeDamage(value) =>
  179. // children.statsActor ! StatsActor.TakeDamage(value)
  180. // // children.movementActor ! ImMovementActor.MovedDown(true)
  181. // Behaviors.same
  182. // case CurrentStats(replyTo) =>
  183. // // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  184. // children.statsActor ! StatsActor.CurrentStats(replyTo)
  185. // Behaviors.same
  186. // case Heal(_) =>
  187. // Behaviors.same
  188. case CurrentStats(replyTo) =>
  189. // ctx.ask(children.statsActor, StatsActor.CurrentStats())
  190. children.statsActor ! StatsActor.CurrentStats(replyTo)
  191. Behaviors.same
  192. case GetStatus(replyTo) =>
  193. replyTo ! Status.Dead
  194. Behaviors.same
  195. case _ => Behaviors.unhandled
  196. }
  197. .receiveSignal {
  198. case (_, PostStop) =>
  199. ctx.log.infoP("stopped")
  200. statsSubject.onComplete()
  201. Behaviors.same
  202. }
  203. }
  204. object StatsActor {
  205. sealed trait Command
  206. case class TakeDamage(value: Int) extends Command
  207. case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)])
  208. extends Command
  209. case class Heal(value: Int) extends Command
  210. case class CurrentStats(replyTo: ActorRef[State]) extends Command
  211. class Props(startingHealth: Int, startingStamina: Int) {
  212. def behavior =
  213. Behaviors.setup[Command] { ctx =>
  214. new StatsActor(ctx, this)
  215. .receive(State(startingHealth, startingStamina))
  216. }
  217. }
  218. case class State(hp: Int, stamina: Int)
  219. }
  220. class StatsActor(
  221. ctx: ActorContext[StatsActor.Command],
  222. props: StatsActor.Props
  223. ) {
  224. import StatsActor._
  225. import com.softwaremill.quicklens._
  226. def receive(state: State): Behavior[Command] =
  227. Behaviors.receiveMessage[Command] {
  228. // Todo add min max values
  229. case TakeDamage(value) =>
  230. val nextState =
  231. if (state.hp - value <= 0)
  232. state.modify(_.hp).setTo(0)
  233. else
  234. state.modify(_.hp).using(_ - value)
  235. receive(nextState)
  236. case TakeDamageResult(value, replyTo) =>
  237. val nextState = if (state.hp - value <= 0) {
  238. replyTo ! true -> state
  239. state
  240. } else {
  241. replyTo ! false -> state
  242. state.modify(_.hp).using(_ - value)
  243. }
  244. receive(nextState)
  245. case Heal(value) => receive(state.modify(_.hp).using(_ + value))
  246. case CurrentStats(replyTo) =>
  247. replyTo ! state
  248. Behaviors.same
  249. }
  250. }