- [ ] Move out anything that is not a core system or component into examples
- [ ] Provide tutorial example with graphics (devcards)
- [ ] Component state validation? Not sure if this should be the responsibility of the game engine or not sinc ethere are many ways to handle validation and assertions
- [X] Inspector integration? This can be a library, but could be useful for beginners
- [ ] Lein template Everything needed with a basic setup of a game
- [-] Better example game
- [X] Nicer tile map
- [X] Nicer sprites
- [ ] Obligatory Flappy Bird?
Maybe add a closure around stage and loader?
- [X] Draw tiles first
- [X] Save z-index to component state
- FSEvents bug on OSX where the figwheel watcher was not working
- Move directory out of Dropbox fixed the issue
- [X] Add tiles to the broad/narrow collision detection? Decided not to so they could be used independently of each other
- [X] Add metadata to tiles in tiled to denote whether they can be traversed
- [X] Updated loading tilemap to include properties from tiled in attributes
- There should be a position component instead of overloading the move component
- Position system should come after collision and movement
- What can change an entities position?
- Movement
- Camera
- Something else?
- Render system should cull sprites that have a position that is not on the screen
- [ ] Load a new tilemap into the game state
- [ ] Update tilemap collision system
- Controls where the camera is and the other systems should take that into account
- If an entity is on a different z level then they should not interact
- Collisions can take into account z level
- Rendering should render things according to their z-level
- Example: The user is hit while walking which should interupt the current walking animation and show the damage animation
- Currently animations only change if an event is fired that is different than the current animation
- Update animation actions to handle an argument in the message to play the last animation once it’s done
- Tries to render the entire tile map a a texture which is 10,000 tiles for a 1600x1600 map
- Based on the map location, slice the tilemap array
- Render just the tiles in the bounds of map coordinance
Allow the user to modify and write new code in the browser while the game is running
Modified snippet based on #clojurescript slack channel user escherize
(ns cljsfiddle.app
(:require [cljs.js :refer [eval-str empty-state js-eval]]))
(defn my-eval [cljs-string]
(eval-str (empty-state)
(str "(ns cljs-user)"
cljs-string)
'dummy-symbol
{:ns 'cljs.user
:eval js-eval
:def-emits-var true
:load (fn [& _] {:lang :clj :source "."})
:context :statement
;; Safari support
:static-fns true}
(fn [{:keys [error value] :as x}]
(if error
(do
(def *er x)
(js/console.log (str error)))
value))))
- Repl inside the game environment
- Instead of event handlers, eval code
- Source code in the game state so it can be viewed in the inspector
You never know where you’ll be when you want to make that one change to see what happens
- Parse the source code into an AST
- Put the AST data in the inspector
- Wrap it in a type and show a codemirror widget for the source
- Edit the code, on submit, eval it and swap it into the state
- Realtime collaboration of source code running in the browser
- On changes, send it over a socket
- Receive changes on a connected session
- Add a system for multiplayer syncing
- Server should be authoritative
- Clients synchronize with the server
- Latency based on the latency of the round trip to the server not to another player
- To avoid latency, replay state (without rendering) up to the present by keeping a buffer of snapshots of game state
- If source code is stored as state then it should be able to be sync’d
- Networking for game programming http://gafferongames.com/networking-for-game-programmers/what-every-programmer-needs-to-know-about-game-networking/
- Inspect state
- Alter running game state
- Pause/rewind/fastforward
- Code editor
- Real time collaboration
- There are many places that use dynamic lookups to get things like the list of entities, components, etc that could be cached
- If you cache, need a way to invalidate the cache
- Shouldn’t v8 be doing this already?
Anonymous functions are slower than def’d functions in js
- [ ] Game loop creates a new anonymous function every time the game loop recurs
- [ ] Component functions are anonymous functions
- Also loose ability to have the repl rebind it dynamically once it’s in the game loop
- [ ] System functions are anonymous
https://github.com/emezeske/lein-cljsbuild/blob/1.1.2/doc/TESTING.md
Can we get the whole library and examples bootstrapped using self compiled cljs?
- Replumb is a library for creating your own
- Here’s a nice implementation of a repl https://github.com/jaredly/reepl
- [ ] Delete hitpoints namespace as it is not being used
- [ ] Don’t overload collision events with data about damage
- [ ] Query the damage amount from the component state of the entity that is being collided with
- Global effects such as grayscale, waves, pixelate, color tint
- Entity effects limited in scope to the entity
- Example: (meta :doc “yo” {:a 1})
- This doesn’t work with the evaluation model of praline because the parent would hold the information and need to pass it to the child
- CHECK FIRST: pixi reuses textures or frames
- In animateable component:
- It’s getting stuck on :hit-up and won’t revert back to :stand-down (when (= next-action :hit-up) (println “HIT UP” next-action current-animation-name))
- Stack should have :stand-down in it, but only has :hit-up, this means that pushing the current action down the stack is not working
i.e animateable -> animation
- Pixi doesn’t cull sprites outside of the viewport according to http://www.html5gamedevs.com/topic/6691-how-does-pixi-deal-with-items-positioned-off-the-screen/
- Similar to React, instead of events to pass data, directly subscribe to other components
- Can infer which components are coupled similar to how reagent does it
- Can make component subscriptions explicit in the game wiring Instead of subscriptions, provide collection of component labels
- Currently all subscriptions are of components for the same entity, but in theory it doesn’t have to be i.e global events
- Having an event bus means component state can change, but downstream component state does not
- Down stream would have to have logic for interpreting the state of the upstream component and they would thus be strongly coupled
- Currently all subscriptions are only for the entity which means another entity can not subscribe to the same messages
- Would be useful to create say an entity with at text and move component that could follow around the player entity
- Per component override
- Derive the component-state schema
- Explicitely or implicitely? Implicitely could use records and mk-state functions would have to return a record
- When fields are updated in dev, perform a schema assertion (maybe using prismatic schema?)
When dev-ing it’s nice to not have to deal with nil errors i.e calling nil as a function, nil values, etc
- [ ] Systems can check that they are getting state that isn’t empty
- [ ] Components can check that the component state matches expected
- [ ] Events can validate event messages
- [X] Core framework
- [ ] Components
- [ ] Systems
multi-component-entities has to put the collection of component ids for each entity into a set before calling subset? on it
- Instead of manually specifying all of the attributes of a system/component/entity use meta data
- Example:
- Component function has a component name of :foo: (defn cf {:component-name :foo} [] …) (defn component-name [f] (:component-name (meta (var component-fn)))) (component-name component-fn) => :foo
- You can include functions in metadata too so we could use that to introspect the component’s name instead of hardcoding it, you would only need to require the component-fn which means the compiler will throw errors earlier
- Specify dependencies of components for the purpose of catching errors earlier such as depending on a component state that does not exist
- mk-component-fn can read the meta data and intelligently figure out what args to call the function with
- Selected state ends up in the third argument to the component function (a hashmap)
- This prevents having to write a function every time you want to read some other component-state, instead you could list it in the meta data
- {:require-component-states [:moveable :collideable]}
- {:subscriptions [:move :collision]}
- Or with more sugar, a dsl for selecting state of the game/components etc {:require-state [[:game :stage] [:component :move] [:component :collision]]}
- This resulted in really big gains when doing collision detection where each entity can create more than one event
- Batching events for the ai system brought much less improvement so there may be something inherent about the collision events that were more severe
- Would be nice to only deal with events at the component fn level
According to profiler it’s really slow
- Provide a nice abstraction for declaring pipeline of functions for loading assets asynchronously so that it doesn’t look like spaghetti
- Integrate that into the mk-game-state function to keep the whole thing declarative
- Read this somewhere that referencing a ton of functions all over the place is not good for performance or garbage collection
- Write a macro that explodes all code into one massive function
The output of the interaction hashmap is non-deterministic because it is iterating through a hashmap where ordering is not guaranteed. Need to iterate through only the accepted keycodes and check if the input-state shows the key is “on”. That way order is controlled by the caller
According to the compiler, the move component requires multiple get-component-state calls
- Controllable should give the intended action based on user input i.e. :walk/run/attack :left/right etc
- Another component should interpret that into a new screen position
- Moveable needs to know if there is a collision before moving and intended position
- Collideable needs to know the intended position of the character
Could work well for systems when iterating over them
- [ ] Add tests
- [ ] Split up monster loops
Implement a tile map that checks for locations of entities that are collidable and sends an event if they are going to collide
- [ ] Create a spatial grid based on the map location (offset based on the view port of the screen)
- [ ] Put all tile collidable entities into their coordinates
- [ ] Iterate over all occupied tiles
- [ ] If they will be on a non-traversable tile, emit a tile collision event
- Systems iterate over all entities that have the component and then each component function
- Try to batch all the changes to the game-state in one shot
- Try using the reducers library for zero allocation collection operations
- Update component state and emit events takes up a significant amount of time number of hashmap ops = number of systems * number of entities with component * number of functions * number of events
- Lots of analysis on clojurescript performance http://wagjo.github.io/benchmark-cljs/
- [-] Use custom types using (.-a my-map) instead of keywords should be 3x faster <2014-11-30 Sun>
- What about a macro that replaces get-in, assoc-in, update-in? Would need to always use our version of it which is dumb
- Implement protocols for the custom type so that all the clojure map functions work with it
- Underlying data structure will be a js array
- [ ] Remove usage of assoc-in ./chocolatier/engine/ces.cljs:29: (assoc-in state [:scenes uid] system-ids)) ./chocolatier/engine/ces.cljs:68: (assoc-in state [:entities uid] component-ids)) ./chocolatier/engine/ces.cljs:86: (assoc-in state [:state component-id entity-id] val)) ./chocolatier/engine/ces.cljs:173: (assoc-in state [:components uid] {:fns wrapped-fns}))) ./chocolatier/engine/ces.cljs:211: (assoc-in state [:systems uid] system-fn))) ./chocolatier/engine/systems/collision.cljs:101: (assoc-in state [:state :spatial-grid] grid)))) ./chocolatier/engine/systems/events.cljs:71: (assoc-in state [:state :events :queue] {})) ./chocolatier/engine/systems/events.cljs:76: (assoc-in state [:state :events] {:queue {} :subscriptions {}})) ./chocolatier/engine/systems/input.cljs:48: (assoc-in state [:game :input] @KEYBOARD-INPUT)) ./chocolatier/engine/systems/tiles.cljs:42: (assoc-in state [:state :tiles] ./chocolatier/engine/systems/tiles.cljs:53: (assoc-in state [:state :tiles] tiles))) ./chocolatier/entities/enemy.cljs:28: (assoc-in [:state :renderable uid] init-render-state) ./chocolatier/entities/player.cljs:27: (assoc-in [:state :renderable uid] init-render-state)
- [ ] Remove usage of get-in ./chocolatier/engine/ces.cljs:81: (or (get-in state [:state component-id entity-id]) {})) ./chocolatier/engine/systems/events.cljs:36: (let [subscriptions (get-in state [:state :events :subscriptions entity-id]) ./chocolatier/engine/systems/events.cljs:37: events (get-in state [:state :events :queue])] ./chocolatier/engine/systems/events.cljs:38: (mapcat #(get-in events (if (seqable? %) % [%])) subscriptions)))
- [ ] Remove usage of update-in ./chocolatier/engine/systems/events.cljs:31: (update-in state [:state :events :subscriptions entity-id] conj selectors)) ./chocolatier/engine/systems/events.cljs:61: (update-in state (concat [:state :events :queue] selectors) conj event)))
- This did not end up working because of the semantics of property access “.-” makes it impossible to construct at compile time without evaling symbols which means they can not be dynamically evalualted by putting thename of the key in a var for instance.
- [ ] Batch game state changes
- After every system take all of the changes from component entities and events and make the update in one shot
- Uses many assoc-in
- Should components operate on all entities at the same time? That would allow a single assoc-in to the game state from the accumulated component state that could be reduced in
- This should speed up the rendering of lots of sprites
- Example code
function onLoad() { // init stats var stats = new Stats(); stats.getDomElement().style.position = 'absolute'; stats.getDomElement().style.left = '0px'; stats.getDomElement().style.top = '0px'; document.body.appendChild( stats.getDomElement() ); setInterval( function () { stats.update(); }, 1000 / 60 ); // cache dom elements canvas = document.getElementById('my_canvas'); context = canvas.getContext('2d'); width = canvas.width; height = canvas.height; shipImage = document.getElementById('ship'); // create canvas buffer canvasBuffer = document.createElement('canvas'); contextBuffer = canvasBuffer.getContext('2d'); canvasBuffer.width = 100; canvasBuffer.height = 100; contextBuffer.translate(50, 50); // so we can rotate about the center point // create lookup table for trig functions angleIncrement = Math.PI / 12; lookupTable = []; for (var i = 0; i < 5000; i++) { lookupTable[i] = { x: Math.cos(i) * width - 150, y: Math.sin(i) * height - 150 }; } // kick off the loop window.setInterval(update, 16); } // this is called using a 16 ms interval function update() { // draw transformed ship image to a canvas buffer contextBuffer.clearRect(0, 0, 100, 100); contextBuffer.rotate(angleIncrement); contextBuffer.drawImage(shipImage, 0, 0, 50, 50); // draw 5,000 ships for (var i = 0; i < 5000; i++) { var lookup = lookupTable[i]; context.drawImage(canvasBuffer, lookup.x, lookup.y); } }
Currently it updates it’s component state but that’s it. SHould send an event to avoid other components querying it directly
http://codeincomplete.com/posts/2013/12/4/javascript_game_foundations_the_game_loop/
Branch: map-position Keep track of entities based on their map coordinates. Translate map coordinates into screen coordinates on render. This should help with the collision issues so that movement is decoupled from the :player entity
- [ ] Add map-x and map-y to entities
- [ ] Add offset x and y to background layer
- [ ] On render apply offsets to the map and translate to screen changes
- [ ] Tiles
- [ ] Player
- [ ] Monster
- [ ] Boundary collisions (is a tile passable)
- Check the players map position and find the nearest tile in the tile map
- If the tile is passable then do nothing
- If not then reset offset-x and offset-y to 0
- Entities should have body parts (multiple hit boxes)
- Body parts have a hitbox and are checked during collision detection
Makes a series of state changes to the game and returns the end state once all steps are completed Can be used for testing behavior visually and with real results
- [ ] Record game state
- [ ] Playback game state
- System that takes sound events, debounces, and plays sounds
- Use howlerjs to manage playing clips