Skip to content

Instantly share code, notes, and snippets.

@Janiczek
Created April 16, 2014 07:24
Show Gist options
  • Save Janiczek/10823557 to your computer and use it in GitHub Desktop.
Save Janiczek/10823557 to your computer and use it in GitHub Desktop.
(ns ces.example
(:require [ces.core :refer [defcomponent
defsystem
defloop
defentity
run-loop
?
!?]]
[ces.debug :refer [reset-all
print-all]]))
; -----------------------------------------------------------------------------
; --------------------------------------------------------------- EXAMPLE -----
; -----------------------------------------------------------------------------
; in this example we'll start simple:
; we will want some objects (entities) to move on 2D plane.
; if you made some kind of mistake and want to begin, there's a reset fn:
(reset-all)
; there is also a sort-of debug fn:
(print-all)
; but let's hope you won't need them ;)
; first, let's define some components.
; these can be thought of as features or properties of your objects (entities).
; for example, "the NPC has a position and AI controls it, not the player."
; imagine we had the behaviour ("what that means") coupled with the component -
; you could easily make the player control some NPC instead of his avatar,
; maybe during some kind of cutscene! this can lead to new ways to think about
; your possibilities and you can be more creative than, say, if you didn't see
; how you could do that in an OOP settings.
; we'll do a really small example today. our objects will have a position
; on a 2D plane and optionally will move (they'll have a speed).
; for the sake of simplicity we won't draw them, they'll tell us using print.
; so, let's start!
; we already said the objects will have a position:
(defcomponent :position {:x 0 :y 0})
; and if they are about to move, they have a speed.
; look at the [:position] - that's our dependency - speed makes no sense
; if we don't move from somewhere.
(defcomponent :speed [:position] {:vx 0 :vy 0})
; so, the component has:
; - a name (:speed)
; - a vector of dependencies ([:position])
; - a map of attributes ({:vx 0 :vy 0})
; actually, the only required thing is a name.
; so you can have a component that says for example :dead and that's it -
; handy if you don't need any additional information.
; that's it for now - we've described the properties of our objects (entities).
; let's move on to another aspect: the systems and the loop.
; the system is what actually does something.
; the components just describe the entities, and systems look which entities
; have components that are of interest to them.
; so, for example render system could want all the entities that are visible.
; AI system could operate on the enemies, and so on.
; so, let's define a system for rendering the entities.
; we'll render them by saying "this entity is there and there."
; we can only render those that have a position. without it we wouldn't know
; where they are.
; the speed isn't important for us (it might be for somebody who draws them
; with a motion blur or something, but let's keep things simple).
; so, without further ado:
(defsystem :render [:position]
(println "RENDER: entity" entity "at" (? x) (? y)))
; there are few things to explain here:
; :render is the name of the system. it doesn't have to have any connection
; to the components it uses.
; [:position] is the vector of components our system is interested in.
; it works as AND, not as OR, so it needs the entity to have all the components.
; think of it as a lock with more keyholes - it needs all of the keys to unlock.
; the rest of the defsystem body is kind of a function body. that function will
; run at every "tick" of the world clock - during every loop.
; there can be more forms than just the println you see above.
; it's still weird though - where did the entity symbol come from? and what are
; these ? functions?
; the defmacro is actually anaphoric. what that means is it binds some values
; to symbols automatically. in our case it's the entity symbol - everytime
; the function is called, the entity will contain the name of the entity.
; (it's guaranteed to be unique, so it's kind of an ID.)
; the mildly dangerous part is that if you shadow 'entity symbol with something
; else, the helpers we're going to talk about in a minute could stop working.
; so, just don't do something like (let [entity 3] ...) and you should be fine.
; so, the 'entity symbol contains the name of the entity, got it. what about
; those weird ? functions? what do they do?
; our entity is supposed to have some attributes, as given by the components.
; and this is a way to access them. so, if our entity has component :position
; with :x 3 and :y 5, then (? x) gives us 3.
; can you see how the render system gets the current coordinates now?
; the ? helper is not the only one. there's ! helper that sets a value:
; (! x 4) sets the :x attribute of the current entity to 4.
; and there's a !? helper that we're gonna use in our "move" system.
; it updates the value: think of (!? x inc) like (! x (inc (? x))) -
; it's very similar to update-in (actually, it uses it):
; (!? x -) -> (! x (- (? x)))
; (!? x - 1) -> (! x (- (? x) 1))
; (!? x - 1 2) -> (! x (- (? x) 1 2))
; let's see the move system.
; it will add the current speed to the current position - and that will be
; the new position. for that it will need both position and speed components.
(defsystem :move [:position :speed]
(!? x + (? vx))
(!? y + (? vy))
(println "MOVE: entity" entity "moved to" (? x) (? y)))
; the systems are in place, we can specify the order in which they will be
; called (in fact, we MUST do it, otherwise no system will run).
; of course, in this simple example it's not very important, but still!
(defloop [:render :move])
; this makes the systems run in a given order. we can switch it around
; if it's important for us. it could be for some interconnected systems.
; let's try to run the loop.
(run-loop)
;;=> nil
; nothing happened. why? the systems have no entities to work on!
; let's add an entity then! to illustrate that the systems don't run if there's
; no entities to work on, we'll add an entity that doesn't have :speed.
; therefore, only :render system should do something with it.
(defentity :static [:position] {:x 1 :y 2})
; the anatomy of the call is also straightforward:
; - name (keyword). not required (a generic name is given as default - :entity).
; when not unique, an unique suffix is added.
; so you can create :bullet entities in a loop and the names
; will be something like :bullet1023, :bullet1027 etc.
; - vector of components (list of component names). also not required.
; - values to the attributes. not required, the default values from definition
; of the component can be given.
; let's test it:
(run-loop)
;;=> RENDER: entity :static at 1 2
;; nil
; great! we've got some text on the screen! now let's add a moving entity.
(defentity :moving [:position :speed] {:x 4 :y 5 :vx -1 :vy 1})
; what about now? the :move system should do something too this time.
(run-loop)
;;=> RENDER: entity :moving at 4 5
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to 3 6
;; nil
; amazing. let's let the loop run a few more times and see the movement:
(run-loop 5)
;;=> RENDER: entity :moving at 3 6
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to 2 7
;; RENDER: entity :moving at 2 7
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to 1 8
;; RENDER: entity :moving at 1 8
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to 0 9
;; RENDER: entity :moving at 0 9
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to -1 10
;; RENDER: entity :moving at -1 10
;; RENDER: entity :static at 1 2
;; MOVE: entity :moving moved to -2 11
;; nil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment