Created
April 16, 2014 07:24
-
-
Save Janiczek/10823557 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (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