pixie_dust 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README +16 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/game.js +9200 -0
  8. data/lib/corelib.js +3331 -0
  9. data/lib/pixie_dust/version.rb +3 -0
  10. data/lib/pixie_dust.rb +14 -0
  11. data/pixie.json +15 -0
  12. data/pixie_dust.gemspec +29 -0
  13. data/source/active_bounds.coffee +48 -0
  14. data/source/ageable.coffee +23 -0
  15. data/source/bounded.coffee +282 -0
  16. data/source/camera.coffee +138 -0
  17. data/source/camera.fade.coffee +69 -0
  18. data/source/camera.flash.coffee +69 -0
  19. data/source/camera.rotate.coffee +11 -0
  20. data/source/camera.shake.coffee +27 -0
  21. data/source/camera.zoom.coffee +25 -0
  22. data/source/camera.zsort.coffee +13 -0
  23. data/source/clampable.coffee +61 -0
  24. data/source/collidable.coffee +126 -0
  25. data/source/collision.coffee +272 -0
  26. data/source/collision_response.coffee +28 -0
  27. data/source/color.coffee +1113 -0
  28. data/source/color_table.coffee +2534 -0
  29. data/source/controllable.coffee +66 -0
  30. data/source/cooldown.coffee +82 -0
  31. data/source/debuggable.coffee +253 -0
  32. data/source/drawable.coffee +167 -0
  33. data/source/dust_emitter.coffee +36 -0
  34. data/source/easing.coffee +38 -0
  35. data/source/emitter.coffee +7 -0
  36. data/source/emitterable.coffee +68 -0
  37. data/source/engine.coffee +274 -0
  38. data/source/engine.collision.coffee +77 -0
  39. data/source/engine.data.coffee +23 -0
  40. data/source/engine.delay.coffee +41 -0
  41. data/source/engine.fps_counter.coffee +32 -0
  42. data/source/engine.game_state.coffee +86 -0
  43. data/source/engine.joysticks.coffee +47 -0
  44. data/source/engine.keyboard.coffee +17 -0
  45. data/source/engine.levels.coffee +69 -0
  46. data/source/engine.mouse.coffee +16 -0
  47. data/source/engine.selector.coffee +166 -0
  48. data/source/engine.stats.coffee +16 -0
  49. data/source/engine.tilemap.coffee +41 -0
  50. data/source/engine_background.coffee +32 -0
  51. data/source/expirable.coffee +47 -0
  52. data/source/flickerable.coffee +78 -0
  53. data/source/follow.coffee +65 -0
  54. data/source/framerate.coffee +42 -0
  55. data/source/game_object.coffee +181 -0
  56. data/source/game_object.effect.coffee +33 -0
  57. data/source/game_object.meter.coffee +191 -0
  58. data/source/game_over.coffee +40 -0
  59. data/source/game_state.coffee +67 -0
  60. data/source/game_state.save_state.coffee +76 -0
  61. data/source/game_state.single_camera.coffee +40 -0
  62. data/source/game_state_cameras.coffee +33 -0
  63. data/source/level_state.coffee +32 -0
  64. data/source/movable.coffee +57 -0
  65. data/source/oscillator.coffee +18 -0
  66. data/source/pixie_dust.coffee +2 -0
  67. data/source/resource_loader.coffee +35 -0
  68. data/source/rotatable.coffee +38 -0
  69. data/source/sprite.coffee +181 -0
  70. data/source/text_effect.coffee +74 -0
  71. data/source/text_effect.floating.coffee +22 -0
  72. data/source/text_screen.coffee +38 -0
  73. data/source/tilemap.coffee +56 -0
  74. data/source/timed_events.coffee +78 -0
  75. data/source/title_screen.coffee +38 -0
  76. data/source/tween.coffee +70 -0
  77. data/test/active_bounds.coffee +67 -0
  78. data/test/bounded.coffee +98 -0
  79. data/test/camera.coffee +29 -0
  80. data/test/clampable.coffee +18 -0
  81. data/test/collidable.coffee +51 -0
  82. data/test/collision.coffee +70 -0
  83. data/test/color.coffee +533 -0
  84. data/test/controllable.coffee +108 -0
  85. data/test/cooldown.coffee +116 -0
  86. data/test/debuggable.coffee +71 -0
  87. data/test/drawable.coffee +31 -0
  88. data/test/emitter.coffee +0 -0
  89. data/test/emitterable.coffee +15 -0
  90. data/test/engine.coffee +228 -0
  91. data/test/engine_data.coffee +12 -0
  92. data/test/engine_delay.coffee +14 -0
  93. data/test/engine_selector.coffee +100 -0
  94. data/test/expirable.coffee +35 -0
  95. data/test/flickerable.coffee +51 -0
  96. data/test/follow.coffee +34 -0
  97. data/test/game_object.coffee +78 -0
  98. data/test/game_object_effect.coffee +17 -0
  99. data/test/metered.coffee +33 -0
  100. data/test/movable.coffee +46 -0
  101. data/test/oscillator.coffee +28 -0
  102. data/test/resource_loader.coffee +7 -0
  103. data/test/rotatable.coffee +20 -0
  104. data/test/sprite.coffee +21 -0
  105. data/test/text.coffee +25 -0
  106. data/test/timed_events.coffee +23 -0
  107. data/test/tweening.coffee +18 -0
  108. metadata +233 -0
@@ -0,0 +1,191 @@
1
+ ###*
2
+ The Metered module provides a simple drop-in
3
+ meter ui to track arbitrary numeric attributes.
4
+
5
+ player = GameObject
6
+ health: 100
7
+ heathMax: 100
8
+
9
+ enemy = GameObject
10
+ health: 500
11
+
12
+ someOtherObject = GameObject
13
+
14
+ player.meter 'health'
15
+ # => Sets up a health meter that will be drawn during the player overlay event
16
+
17
+ enemy.meter 'health'
18
+ # => Sets up a health meter that will be drawn during the enemy overlay event.
19
+ # Since healthMax wasn't provided, it is set to the value of I.health (500)
20
+
21
+ someOtherObject.meter 'turbo'
22
+ # => Sets up a turbo meter that will be drawn during the someOtherObject overlay event.
23
+ # Since neither turbo or turboMax were provided, they are both set to 100.
24
+
25
+ Metered module
26
+ @name Metered
27
+ @module
28
+ @constructor
29
+ @param {Object} I Instance variables
30
+ @param {GameObject} self Reference to including object
31
+ ###
32
+
33
+ GameObject.Meter = (I={}, self) ->
34
+ Object.reverseMerge I,
35
+ meters: {}
36
+
37
+ self.bind 'overlay', (canvas) ->
38
+ for name, meterData of I.meters
39
+ {
40
+ backgroundColor,
41
+ border: {color:borderColor, radius:borderRadius, width:borderWidth}
42
+ color,
43
+ height,
44
+ show,
45
+ width,
46
+ x,
47
+ y
48
+ } = meterData
49
+
50
+ {x, y} = meterData.position if meterData.position?
51
+
52
+ return unless show
53
+
54
+ ratio = (I[name] / I["#{name}Max"]).clamp(0, 1)
55
+
56
+ canvas.drawRoundRect
57
+ color: backgroundColor
58
+ radius: borderRadius
59
+ x: x
60
+ y: y
61
+ width: width
62
+ height: height
63
+
64
+ canvas.drawRoundRect
65
+ color: color
66
+ x: x
67
+ y: y
68
+ radius: borderRadius
69
+ width: width * ratio
70
+ height: height
71
+
72
+ canvas.drawRoundRect
73
+ x: x
74
+ y: y
75
+ width: width
76
+ height: height
77
+ radius: borderRadius
78
+ stroke:
79
+ color: borderColor
80
+ width: borderWidth
81
+ ###*
82
+ Configures a meter to be drawn each overlay event.
83
+
84
+ player = GameObject
85
+
86
+ player.meter 'health',
87
+ border
88
+ color: 'brown'
89
+ radius: 3
90
+ color: 'pink'
91
+ height: 20
92
+ x: 5
93
+ y: 5
94
+ show: true
95
+ width: 150
96
+
97
+ # => Sets up a health meter, using all the configuration options
98
+
99
+ @name meter
100
+ @methodOf Metered#
101
+ @param {String} name The name of the property to meter
102
+ @param {Object} options The meter configuration options
103
+ @param {String} border: color Color of the meter's border
104
+ @param {Number} border: width Width of the meter's border
105
+ @param {String} color Color of the meter's inner rectangle
106
+ @param {Number} height Height of the meter
107
+ @param {Object} position An x, y object representing the position of the meter
108
+ @param {Number} x x position of the meter
109
+ @param {Number} y y position of the meter
110
+ @param {Number} border: radius Border radius of the meter
111
+ @param {Boolean} show Boolean to toggle whether of not to display the meter
112
+ @param {Number} width How wide the meter is
113
+ ###
114
+ meter: (name, options={}) ->
115
+ Object.reverseMerge options,
116
+ backgroundColor: 'black'
117
+ border:
118
+ color: 'white'
119
+ radius: 2
120
+ width: 1.5
121
+ color: 'green'
122
+ height: 10
123
+ x: 0
124
+ y: 0
125
+ show: true
126
+ width: 100
127
+
128
+ I[name] ?= 100
129
+
130
+ if not I["#{name}Max"]
131
+ if I[name]
132
+ I["#{name}Max"] = I[name]
133
+ else
134
+ I["#{name}Max"] = 100
135
+
136
+ I.meters[name] = options
137
+
138
+ ###*
139
+ Shows the named meter
140
+
141
+ player = GameObject
142
+
143
+ # creates a health meter but disables visibility
144
+ player.meter 'health'
145
+ show: false
146
+
147
+ # enables visibility for the meter named 'health'
148
+ player.showMeter 'health'
149
+
150
+ @name showMeter
151
+ @methodOf Metered#
152
+ @param {String} name The name of the meter to show
153
+ ###
154
+ showMeter: (name) ->
155
+ I.meters[name].show = true
156
+
157
+ ###*
158
+ Hides the named meter
159
+
160
+ player = GameObject
161
+
162
+ # creates a health meter
163
+ player.meter 'health'
164
+
165
+ # disables visibility for the meter named 'health'
166
+ player.hideMeter 'health'
167
+
168
+ @name hideMeter
169
+ @methodOf Metered#
170
+ @param {String} name The name of the meter to hide
171
+ ###
172
+ hideMeter: (name) ->
173
+ I.meters[name].show = false
174
+
175
+ ###*
176
+ Toggles visibility of the named meter
177
+
178
+ player = GameObject
179
+
180
+ # creates a health meter
181
+ player.meter 'health'
182
+
183
+ # toggles visibility for the meter named 'health'
184
+ player.toggleMeter 'health'
185
+
186
+ @name toggleMeter
187
+ @methodOf Metered#
188
+ @param {String} name The name of the meter to toggle
189
+ ###
190
+ toggleMeter: (name) ->
191
+ I.meters[name].show = not I.meters[name].show
@@ -0,0 +1,40 @@
1
+ ###*
2
+ The Game Over class sets up a simple game state with restart instructions.
3
+
4
+ @see TextScreen
5
+ @name GameOver
6
+ @constructor
7
+ ###
8
+
9
+ ###*
10
+ Transitions to the title state on user input.
11
+
12
+ @name update
13
+ @methodOf GameOver#
14
+ @event
15
+ ###
16
+
17
+ ###*
18
+ Draws Game Over screen and reset instructions.
19
+
20
+ @name overlay
21
+ @methodOf GameOver#
22
+ @param {PixieCanvas} canvas
23
+ @event
24
+ ###
25
+ GameOver = (I={}) ->
26
+ self = TextScreen(I)
27
+
28
+ self.bind 'update', ->
29
+ if justPressed.any
30
+ engine.delay 15, ->
31
+ engine.setState TitleScreen()
32
+
33
+ self.bind "overlay", (canvas) ->
34
+ self.centerText canvas, "Game Over"
35
+
36
+ self.centerText canvas, "Press any key to restart",
37
+ size: 12
38
+ y: App.height / 2 + 30
39
+
40
+ return self
@@ -0,0 +1,67 @@
1
+ GameState = (I={}) ->
2
+ Object.reverseMerge I,
3
+ objects: []
4
+
5
+ queuedObjects = []
6
+
7
+ self = Core(I).extend {
8
+ ###*
9
+ The add method creates and adds an object to the game world. Two
10
+ other events are triggered around this one: beforeAdd and afterAdd.
11
+
12
+ # you can add arbitrary entityData and
13
+ # the engine will make it into a GameObject
14
+ engine.add
15
+ x: 50
16
+ y: 30
17
+ color: "red"
18
+
19
+ player = engine.add
20
+ class: "Player"
21
+
22
+ @name add
23
+ @methodOf Engine#
24
+ @param {Object} entityData The data used to create the game object.
25
+ @returns {GameObject}
26
+ ###
27
+ add: (entityData) ->
28
+ self.trigger "beforeAdd", entityData
29
+
30
+ object = GameObject.construct entityData
31
+ object.create()
32
+
33
+ self.trigger "afterAdd", object
34
+
35
+ if I.updating
36
+ queuedObjects.push object
37
+ else
38
+ I.objects.push object
39
+
40
+ return object
41
+
42
+ objects: ->
43
+ I.objects.copy()
44
+ }
45
+
46
+ # Add events and methods here
47
+ self.bind "update", (elapsedTime) ->
48
+ I.updating = true
49
+
50
+ I.objects.invoke "trigger", "beforeUpdate", elapsedTime
51
+
52
+ [toKeep, toRemove] = I.objects.partition (object) ->
53
+ object.update(elapsedTime)
54
+
55
+ I.objects.invoke "trigger", "afterUpdate", elapsedTime
56
+
57
+ toRemove.invoke "trigger", "remove"
58
+
59
+ I.objects = toKeep.concat(queuedObjects)
60
+ queuedObjects = []
61
+
62
+ I.updating = false
63
+
64
+ self.include "GameState.Cameras"
65
+ self.include "GameState.SaveState"
66
+
67
+ return self
@@ -0,0 +1,76 @@
1
+ ###*
2
+ The <code>SaveState</code> module provides methods to save and restore the current game state.
3
+
4
+ @name SaveState
5
+ @fieldOf GameState
6
+ @module
7
+ @param {Object} I Instance variables
8
+ @param {Object} self Reference to the game state
9
+ ###
10
+ GameState.SaveState = (I, self) ->
11
+ savedState = null
12
+
13
+ ###*
14
+ Save the current game state and returns a JSON object representing that state.
15
+
16
+ engine.bind 'update', ->
17
+ if justPressed.s
18
+ engine.saveState()
19
+
20
+ @name saveState
21
+ @methodOf GameState#
22
+ @returns {Array} An array of the instance data of all objects in the game state
23
+ ###
24
+ saveState: ->
25
+ savedState = I.objects.map (object) ->
26
+ Object.extend({}, object.I)
27
+
28
+ ###*
29
+ Loads the game state passed in, or the last saved state, if any.
30
+
31
+ engine.bind 'update', ->
32
+ if justPressed.l
33
+ # loads the last saved state
34
+ engine.loadState()
35
+
36
+ if justPressed.o
37
+ # removes all game objects, then reinstantiates
38
+ # them with the entityData passed in
39
+ engine.loadState([{x: 40, y: 50, class: "Player"}, {x: 0, y: 0, class: "Enemy"}, {x: 500, y: 400, class: "Boss"}])
40
+
41
+ @name loadState
42
+ @methodOf GameState#
43
+ @param [newState] An arraf of object instance data to load.
44
+ ###
45
+ loadState: (newState) ->
46
+ if newState ||= savedState
47
+ I.objects.invoke "trigger", "remove"
48
+ I.objects = []
49
+
50
+ newState.each (objectData) ->
51
+ self.add Object.extend({}, objectData)
52
+
53
+ ###*
54
+ Reloads the current game state, useful for hotswapping code.
55
+
56
+ engine.I.objects.each (object) ->
57
+ # bring all objects to (0, 0) for some reason
58
+ object.I.x = 0
59
+ object.I.y = 0
60
+
61
+ # reload all objects to make sure
62
+ # they are at (0, 0)
63
+ engine.reload()
64
+
65
+ @name reload
66
+ @methodOf GameState#
67
+ ###
68
+ reload: ->
69
+ oldObjects = I.objects
70
+ I.objects = []
71
+
72
+ oldObjects.each (object) ->
73
+ object.trigger "remove"
74
+
75
+ self.add object.I
76
+
@@ -0,0 +1,40 @@
1
+ ###*
2
+ The <code>SingleCamera</code> module provides provides a single camera view of the game.
3
+ Its transform can be adjusted to view different areas and provide various camera effects.
4
+
5
+ @name SingleCamera
6
+ @fieldOf GameState
7
+ @module
8
+ @param {Object} I Instance variables
9
+ @param {Object} self Reference to the game state
10
+ ###
11
+ GameState.SingleCamera = (I, self) ->
12
+ # Set some default properties
13
+ Object.reverseMerge I,
14
+ cameraTransform: Matrix.IDENTITY
15
+ zSort: true
16
+
17
+ self.attrAccessor "cameraTransform"
18
+
19
+ # Add events and methods here
20
+ self.bind "draw", (canvas) ->
21
+ canvas.withTransform I.cameraTransform, (canvas) ->
22
+ drawObjects = self.objects()
23
+
24
+ # TODO Turn this zSort into a per camera object stream filter
25
+ # This will also enable filters like clipping region tests
26
+ if I.zSort
27
+ drawObjects.sort (a, b) ->
28
+ a.I.zIndex - b.I.zIndex
29
+
30
+ drawObjects.invoke("draw", canvas)
31
+
32
+ # TODO This should be triggering a camera draw
33
+ # camera.trigger "draw", I.canvas
34
+ # possibly even with before and after.
35
+ # This is where per camera effects like shake,
36
+ # flash and fade will come in.
37
+
38
+ # No public methods
39
+ return {}
40
+
@@ -0,0 +1,33 @@
1
+ GameState.Cameras = (I, self) ->
2
+ cameras = [Camera()]
3
+
4
+ self.bind 'update', (elapsedTime) ->
5
+ self.cameras().invoke 'trigger', 'update', elapsedTime
6
+
7
+ self.bind 'afterUpdate', (elapsedTime) ->
8
+ self.cameras().invoke 'trigger', 'afterUpdate', elapsedTime
9
+
10
+ self.bind 'draw', (canvas) ->
11
+ self.cameras().invoke 'trigger', 'draw', canvas, self.objects()
12
+
13
+ self.bind 'overlay', (canvas) ->
14
+ self.cameras().invoke 'trigger', 'overlay', canvas, self.objects()
15
+
16
+ return {
17
+ addCamera: (data) ->
18
+ cameras.push(Camera(data))
19
+ ###*
20
+ Returns the array of camera objects.
21
+
22
+ @name cameras
23
+ @methodOf Engine#
24
+ @returns {Array}
25
+ ###
26
+ cameras: (newCameras) ->
27
+ if newCameras
28
+ cameras = newCameras
29
+
30
+ return self
31
+ else
32
+ return cameras
33
+ }
@@ -0,0 +1,32 @@
1
+ ###*
2
+ A Game State that loads the map for a given level and transitions into the level.
3
+
4
+ @see GameState
5
+ @name LevelState
6
+ @constructor
7
+ @param {Number} duration Amount of time in frames it takes to fade into the level
8
+ @param {String} level name of the map to load
9
+ ###
10
+
11
+ ###*
12
+ Fades in the current level and loads the map.
13
+
14
+ @name enter
15
+ @methodOf LevelState#
16
+ @event
17
+ ###
18
+ LevelState = (I={}) ->
19
+ Object.reverseMerge I,
20
+ duration: 10
21
+ level: 'level1'
22
+
23
+ self = GameState(I)
24
+
25
+ self.bind "enter", ->
26
+ engine.fadeIn
27
+ duration: I.duration
28
+
29
+ engine.loadMap I.level, ->
30
+ engine.I.transitioning = false
31
+
32
+ return self
@@ -0,0 +1,57 @@
1
+ ###*
2
+ The Movable module automatically updates the position and velocity of
3
+ GameObjects based on the velocity and acceleration. It does not check
4
+ collisions so is probably best suited to particle effect like things.
5
+
6
+ player = GameObject
7
+ x: 0
8
+ y: 0
9
+ velocity: Point(0, 0)
10
+ acceleration: Point(1, 0)
11
+ maxSpeed: 2
12
+
13
+ player.include(Movable)
14
+
15
+ # => `velocity is {x: 0, y: 0} and position is {x: 0, y: 0}`
16
+
17
+ player.update(1)
18
+ # => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`
19
+
20
+ player.update(1)
21
+ # => `velocity is {x: 2, y: 0} and position is {x: 3, y: 0}`
22
+
23
+ # we've hit our maxSpeed so our velocity won't increase
24
+ player.update(1)
25
+ # => `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`
26
+
27
+ @name Movable
28
+ @module
29
+ @constructor
30
+ @param {Object} I Instance variables
31
+ @param {Core} self Reference to including object
32
+ ###
33
+ Movable = (I={}, self) ->
34
+ Object.reverseMerge I,
35
+ acceleration: Point(0, 0)
36
+ velocity: Point(0, 0)
37
+
38
+ # Force acceleration and velocity to be Points
39
+ # Useful when reloading data from JSON
40
+ I.acceleration = Point(I.acceleration.x, I.acceleration.y)
41
+ I.velocity = Point(I.velocity.x, I.velocity.y)
42
+
43
+ self.attrReader "velocity", "acceleration"
44
+
45
+ # Handle multi-include
46
+ self.unbind ".Movable"
47
+
48
+ self.bind 'update.Movable', (dt) ->
49
+ I.velocity = I.velocity.add(I.acceleration.scale(dt))
50
+
51
+ if I.maxSpeed?
52
+ currentSpeed = I.velocity.magnitude()
53
+ if currentSpeed > I.maxSpeed
54
+ I.velocity = I.velocity.scale(I.maxSpeed / currentSpeed)
55
+
56
+ I.x += I.velocity.x * dt
57
+ I.y += I.velocity.y * dt
@@ -0,0 +1,18 @@
1
+ ###*
2
+ Creates an oscillator function with the given parameters.
3
+
4
+ @name Oscillator
5
+ @constructor
6
+ @param {Number} amplitude How much to scale the oscillator function value
7
+ @param {Number} period How fast the osciallator function repeats
8
+ @param {Number} offset How much to offset the created oscillator function. Useful for translating between sin and cosine functions.
9
+ ###
10
+ Oscillator = (options={}) ->
11
+ {amplitude, period, offset} = options
12
+
13
+ amplitude = 1 unless amplitude?
14
+ period = 1 unless period?
15
+ offset = 0 unless offset?
16
+
17
+ return (t) ->
18
+ amplitude * Math.cos(Math.TAU * t / period + offset)
@@ -0,0 +1,2 @@
1
+ #= require cornerstone
2
+ #= require_tree .
@@ -0,0 +1,35 @@
1
+ ###*
2
+ @name ResourceLoader
3
+ @namespace
4
+
5
+ Helps access the assets in your game.
6
+ ###
7
+ (->
8
+ typeTable =
9
+ images: "png"
10
+ data: "json"
11
+ tilemaps: "tilemap"
12
+
13
+ ResourceLoader =
14
+ ###*
15
+ Return the url for a particular asset.
16
+
17
+ ResourceLoader.urlFor("images", "player")
18
+ # => This returns the url for the file "player.png" in your images directory.
19
+
20
+ @name urlFor
21
+ @methodOf ResourceLoader#
22
+ @param {String} directory The directory your file is in.
23
+ @param {String} name The name of the file.
24
+ @returns {String} The full url of your asset
25
+
26
+ ###
27
+ urlFor: (directory, name) ->
28
+ directory = App?.directories?[directory] || directory
29
+
30
+ type = typeTable[directory]
31
+
32
+ "#{BASE_URL}/#{directory}/#{name}.#{type}?#{MTIME}"
33
+
34
+ (exports ? this)["ResourceLoader"] = ResourceLoader
35
+ )()
@@ -0,0 +1,38 @@
1
+ ###*
2
+ The Rotatable module rotates the object
3
+ based on its rotational velocity.
4
+
5
+ player = GameObject
6
+ x: 0
7
+ y: 0
8
+ rotationalVelocity: Math.PI / 64
9
+
10
+ player.I.rotation
11
+ # => 0
12
+
13
+ player.update(1)
14
+
15
+ player.I.rotation
16
+ # => 0.04908738521234052 # Math.PI / 64
17
+
18
+ player.update(1)
19
+
20
+ player.I.rotation
21
+ # => 0.09817477042468103 # 2 * (Math.PI / 64)
22
+
23
+ @name Rotatable
24
+ @module
25
+ @constructor
26
+ @param {Object} I Instance variables
27
+ @param {Core} self Reference to including object
28
+ ###
29
+ Rotatable = (I={}, self) ->
30
+ Object.reverseMerge I,
31
+ rotation: 0
32
+ rotationalVelocity: 0
33
+
34
+ self.bind 'update', (dt) ->
35
+ I.rotation += I.rotationalVelocity * dt
36
+
37
+ return {}
38
+