Friday, May 27, 2016

Using an ECS with Scala.JS

I've created an implementation of Entitas ECS in Scala. The source is over on github: https://github.com/darkoverlordofdata/entitas-scala.

I think the big drawback to using an entity component system is that it takes a lot of boilerplate to get a project up and running. So, I also have a command line tool to help me with that, entitas-cli. Entitas-cli requires nodejs, and uses Liquid templates to generate code from a json configuration file.

To install entitas-cli: 'npm install -g entitas-cli'

I will need an existing project, so using the tutorial at https://www.scala-js.org/tutorial/basic/, I've created a basic HelloWorld application.  I only followed the first three steps, and then I add my dependencies, so now my build.sbt looks like this:

scalaJSUseRhino in Global := false
enablePlugins(ScalaJSPlugin)
name := "Scala.js Tutorial"
scalaVersion := "2.11.7" // or any other Scala version >= 2.10.2
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.0"

libraryDependencies += "com.darkoverlordofdata" %%% "entitas" % "0.0.0-SNAPSHOT"
resolvers += "Artifactory" at "https://oss.jfrog.org/artifactory/oss-snapshot-local/"
libraryDependencies += "co.technius.scalajs-pixi" %%% "core" % "0.0.1-SNAPSHOT"
jsDependencies += "org.webjars" % "pixi.js" % "3.0.7" / "pixi.js"
jsDependencies += RuntimeDOM
skip in packageJSDependencies := false
persistLauncher in Compile := true

I'll have to reload my sbt configuration. So while it's doing that, I open another terminal window in the project root, and type the following commands :

entitas init helloworld 
entitas create -c Destroy
entitas create -c Player 
entitas create -c Position x:Float y:Float
entitas create -c Velocity x:Float y:Float
entitas create -c View sprite:co.technius.scalajs.pixi.Sprite
entitas create -s Destroy IExecuteSystem
entitas create -s Physics IExecuteSystem
entitas create -s Render IInitializeSystem IExecuteSystem

Now I have an ./entitas.json file in my project root.


    {
      "namespace": "helloworld",
      "src": "lib/src",
      "output": {
        "javascript": "web/src/helloworld/generatedExtensions.js",
        "typescript": "lib/src/generatedComponents.ts",
        "declaration": "lib/ext/helloworld.d.ts"
      },
    ...}

These defaults are for a typescript project, so I make the following changes for scala:

      "namespace": "tutorial.webapp",
      "src": "src/main/scala",
      "output": {
        "generated": "components.scala"
      },

and generate the output:

entitas generate -p scala mutable=var

This creates all of my components and system classes. Components are regenerated every time I run the tool. Systems are not overwritten.

I also need some assets.  I've downloaded Kenney's SpaceShooterRedux package from http://opengameart.org/content/space-shooter-redux

The main TutorialApp class should load the assets and start the game loop:

package tutorial.webapp

import co.technius.scalajs.pixi.Container
import co.technius.scalajs.pixi.Pixi
import co.technius.scalajs.pixi.loaders.{ResourceDictionary, Loader}
import com.darkoverlordofdata.entitas.{Systems, Pool}
import tutorial.webapp.systems._
import org.scalajs.dom.document
import org.scalajs.dom.window
import scala.scalajs.js.JSApp

object TutorialApp extends JSApp {
  lazy val width = window.innerWidth
  lazy val height = window.innerHeight
  lazy val renderer = Pixi.autoDetectRenderer(width, height)
  lazy val stage = new Container

  lazy val pool: Pool = { new Pool(Component.TotalComponents.id) }
  lazy val systems: Systems = { new Systems() }

  def main(): Unit = {
    Pixi.loader
      .add("black",   "images/black.png")
      .add("ship",    "images/ship.png")
      .load(loaded)
    document.body.appendChild(renderer.view)
    window.requestAnimationFrame(render)
  }

  lazy val loaded = (loader:Loader, res:ResourceDictionary) => {
    systems
      .add(pool.createSystem(new RenderSystem(pool)))
      .add(pool.createSystem(new PhysicsSystem(pool)))
      .add(pool.createSystem(new DestroySystem(pool)))
    systems.initialize()
  }

  lazy val render: (Double) => Unit = { time =>
    systems.execute()
    window.requestAnimationFrame(render)
  }

}


Now I just need to fill in the systems and create entities. The full version is here
https://github.com/darkoverlordofdata/invaders-scala-js

Try it  live here, at https://darkoverlordofdata.com/invaders-scala-js. Be kind, it is just started.


No comments:

Post a Comment