pixie_dust 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README +16 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/game.js +9200 -0
- data/lib/corelib.js +3331 -0
- data/lib/pixie_dust/version.rb +3 -0
- data/lib/pixie_dust.rb +14 -0
- data/pixie.json +15 -0
- data/pixie_dust.gemspec +29 -0
- data/source/active_bounds.coffee +48 -0
- data/source/ageable.coffee +23 -0
- data/source/bounded.coffee +282 -0
- data/source/camera.coffee +138 -0
- data/source/camera.fade.coffee +69 -0
- data/source/camera.flash.coffee +69 -0
- data/source/camera.rotate.coffee +11 -0
- data/source/camera.shake.coffee +27 -0
- data/source/camera.zoom.coffee +25 -0
- data/source/camera.zsort.coffee +13 -0
- data/source/clampable.coffee +61 -0
- data/source/collidable.coffee +126 -0
- data/source/collision.coffee +272 -0
- data/source/collision_response.coffee +28 -0
- data/source/color.coffee +1113 -0
- data/source/color_table.coffee +2534 -0
- data/source/controllable.coffee +66 -0
- data/source/cooldown.coffee +82 -0
- data/source/debuggable.coffee +253 -0
- data/source/drawable.coffee +167 -0
- data/source/dust_emitter.coffee +36 -0
- data/source/easing.coffee +38 -0
- data/source/emitter.coffee +7 -0
- data/source/emitterable.coffee +68 -0
- data/source/engine.coffee +274 -0
- data/source/engine.collision.coffee +77 -0
- data/source/engine.data.coffee +23 -0
- data/source/engine.delay.coffee +41 -0
- data/source/engine.fps_counter.coffee +32 -0
- data/source/engine.game_state.coffee +86 -0
- data/source/engine.joysticks.coffee +47 -0
- data/source/engine.keyboard.coffee +17 -0
- data/source/engine.levels.coffee +69 -0
- data/source/engine.mouse.coffee +16 -0
- data/source/engine.selector.coffee +166 -0
- data/source/engine.stats.coffee +16 -0
- data/source/engine.tilemap.coffee +41 -0
- data/source/engine_background.coffee +32 -0
- data/source/expirable.coffee +47 -0
- data/source/flickerable.coffee +78 -0
- data/source/follow.coffee +65 -0
- data/source/framerate.coffee +42 -0
- data/source/game_object.coffee +181 -0
- data/source/game_object.effect.coffee +33 -0
- data/source/game_object.meter.coffee +191 -0
- data/source/game_over.coffee +40 -0
- data/source/game_state.coffee +67 -0
- data/source/game_state.save_state.coffee +76 -0
- data/source/game_state.single_camera.coffee +40 -0
- data/source/game_state_cameras.coffee +33 -0
- data/source/level_state.coffee +32 -0
- data/source/movable.coffee +57 -0
- data/source/oscillator.coffee +18 -0
- data/source/pixie_dust.coffee +2 -0
- data/source/resource_loader.coffee +35 -0
- data/source/rotatable.coffee +38 -0
- data/source/sprite.coffee +181 -0
- data/source/text_effect.coffee +74 -0
- data/source/text_effect.floating.coffee +22 -0
- data/source/text_screen.coffee +38 -0
- data/source/tilemap.coffee +56 -0
- data/source/timed_events.coffee +78 -0
- data/source/title_screen.coffee +38 -0
- data/source/tween.coffee +70 -0
- data/test/active_bounds.coffee +67 -0
- data/test/bounded.coffee +98 -0
- data/test/camera.coffee +29 -0
- data/test/clampable.coffee +18 -0
- data/test/collidable.coffee +51 -0
- data/test/collision.coffee +70 -0
- data/test/color.coffee +533 -0
- data/test/controllable.coffee +108 -0
- data/test/cooldown.coffee +116 -0
- data/test/debuggable.coffee +71 -0
- data/test/drawable.coffee +31 -0
- data/test/emitter.coffee +0 -0
- data/test/emitterable.coffee +15 -0
- data/test/engine.coffee +228 -0
- data/test/engine_data.coffee +12 -0
- data/test/engine_delay.coffee +14 -0
- data/test/engine_selector.coffee +100 -0
- data/test/expirable.coffee +35 -0
- data/test/flickerable.coffee +51 -0
- data/test/follow.coffee +34 -0
- data/test/game_object.coffee +78 -0
- data/test/game_object_effect.coffee +17 -0
- data/test/metered.coffee +33 -0
- data/test/movable.coffee +46 -0
- data/test/oscillator.coffee +28 -0
- data/test/resource_loader.coffee +7 -0
- data/test/rotatable.coffee +20 -0
- data/test/sprite.coffee +21 -0
- data/test/text.coffee +25 -0
- data/test/timed_events.coffee +23 -0
- data/test/tweening.coffee +18 -0
- metadata +233 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
###*
|
2
|
+
The Controllable module adds simple movement
|
3
|
+
when up, down, left, or right are held.
|
4
|
+
|
5
|
+
# create a player and include Controllable
|
6
|
+
player = GameObject
|
7
|
+
width: 5
|
8
|
+
height: 17
|
9
|
+
x: 15
|
10
|
+
y: 30
|
11
|
+
speed: 2
|
12
|
+
|
13
|
+
player.include Controllable
|
14
|
+
|
15
|
+
# hold the left arrow key, then
|
16
|
+
# update the player
|
17
|
+
player.update()
|
18
|
+
|
19
|
+
# the player is moved left according to his speed
|
20
|
+
player.I.x
|
21
|
+
# => 13
|
22
|
+
|
23
|
+
# We keep track of the direction the object is
|
24
|
+
# facing in case you need that (eg. for attack direction)
|
25
|
+
player.I.facing
|
26
|
+
# => player.I.facing
|
27
|
+
# => Point(-1, 0)
|
28
|
+
|
29
|
+
@name Controllable
|
30
|
+
@module
|
31
|
+
@constructor
|
32
|
+
@param {Object} I Instance variables
|
33
|
+
@param {Core} self Reference to including object
|
34
|
+
###
|
35
|
+
Controllable = (I={}, self) ->
|
36
|
+
Object.reverseMerge I,
|
37
|
+
facing: Point(1, 0)
|
38
|
+
speed: 1
|
39
|
+
velocity: Point(0, 0)
|
40
|
+
|
41
|
+
self.bind "update", ->
|
42
|
+
self.movement()
|
43
|
+
|
44
|
+
movement: ->
|
45
|
+
I.velocity.x = 0
|
46
|
+
I.velocity.y = 0
|
47
|
+
|
48
|
+
if keydown.left
|
49
|
+
I.velocity.x = -1
|
50
|
+
|
51
|
+
if keydown.right
|
52
|
+
I.velocity.x = 1
|
53
|
+
|
54
|
+
if keydown.up
|
55
|
+
I.velocity.y = -1
|
56
|
+
|
57
|
+
if keydown.down
|
58
|
+
I.velocity.y = 1
|
59
|
+
|
60
|
+
I.velocity = I.velocity.norm()
|
61
|
+
|
62
|
+
unless I.velocity.equal(Point.ZERO)
|
63
|
+
I.facing = I.velocity
|
64
|
+
|
65
|
+
I.velocity = I.velocity.scale(I.speed)
|
66
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
###*
|
2
|
+
The Cooldown module provides a declarative way to manage cooldowns on
|
3
|
+
GameObject's properties.
|
4
|
+
|
5
|
+
# Health regeneration
|
6
|
+
player = GameObject
|
7
|
+
health: 50
|
8
|
+
|
9
|
+
# health will approach
|
10
|
+
# 100 by 1 every second
|
11
|
+
player.cooldown "health",
|
12
|
+
target: 100
|
13
|
+
|
14
|
+
elapsedTime = 1
|
15
|
+
player.update(elapsedTime)
|
16
|
+
|
17
|
+
player.I.health
|
18
|
+
# => 51
|
19
|
+
|
20
|
+
# Shoot Timeout
|
21
|
+
player = GameObject()
|
22
|
+
|
23
|
+
# by default the cooldown
|
24
|
+
# approaches 0 by 1 each second
|
25
|
+
player.cooldown "shootTimer"
|
26
|
+
|
27
|
+
player.I.shootTimer = 10 # => Pew! Pew!
|
28
|
+
|
29
|
+
player.update(elapsedTime)
|
30
|
+
|
31
|
+
player.I.shootTimer # => 9
|
32
|
+
|
33
|
+
# Turbo Cooldown
|
34
|
+
player = GameObject()
|
35
|
+
|
36
|
+
# turboTimer starts at 1000
|
37
|
+
# and approaches 12 by 5 each second
|
38
|
+
player.cooldown "turboTimer",
|
39
|
+
approachBy: 5
|
40
|
+
value: 1000
|
41
|
+
target: 12
|
42
|
+
|
43
|
+
player.I.turboTimer = 1000
|
44
|
+
|
45
|
+
player.update(elapsedTime)
|
46
|
+
|
47
|
+
player.I.turboTimer # => 995
|
48
|
+
|
49
|
+
@name Cooldown
|
50
|
+
@module
|
51
|
+
@constructor
|
52
|
+
@param {Object} I Instance variables
|
53
|
+
@param {Core} self Reference to including object
|
54
|
+
###
|
55
|
+
Cooldown = (I, self) ->
|
56
|
+
Object.reverseMerge I,
|
57
|
+
cooldowns: {}
|
58
|
+
|
59
|
+
self.bind "update", (dt) ->
|
60
|
+
for name, cooldownOptions of I.cooldowns
|
61
|
+
{approachBy, target} = cooldownOptions
|
62
|
+
|
63
|
+
I[name] = I[name].approach(target, approachBy * dt)
|
64
|
+
|
65
|
+
cooldown: (name, options={}) ->
|
66
|
+
{target, approachBy, value} = options
|
67
|
+
|
68
|
+
target ||= 0
|
69
|
+
approachBy = 1 unless approachBy?
|
70
|
+
|
71
|
+
# Set the cooldown data
|
72
|
+
I.cooldowns[name] = {
|
73
|
+
target
|
74
|
+
approachBy
|
75
|
+
}
|
76
|
+
|
77
|
+
if value?
|
78
|
+
# Use the value given if any
|
79
|
+
I[name] = options.value
|
80
|
+
else
|
81
|
+
# Initialize field if it doesn't exist
|
82
|
+
I[name] = 0 unless I[name]
|
@@ -0,0 +1,253 @@
|
|
1
|
+
###*
|
2
|
+
The Debuggable Module provides a simple API to easily display
|
3
|
+
an object's properties onscreen. This mixin comes with predefined
|
4
|
+
attribute filters so that you can exclude irrelevant data.
|
5
|
+
|
6
|
+
player = GameObject
|
7
|
+
x: 40
|
8
|
+
y: 14
|
9
|
+
spriteName: null
|
10
|
+
numericErrorProperty: NaN
|
11
|
+
|
12
|
+
player.include Debuggable
|
13
|
+
|
14
|
+
# sets up debug output for all player's properties
|
15
|
+
# at the starting position (0, 0)
|
16
|
+
player.debug
|
17
|
+
filter: 'all'
|
18
|
+
|
19
|
+
@name Debuggable
|
20
|
+
@module
|
21
|
+
@constructor
|
22
|
+
@param {Object} I Instance variables
|
23
|
+
@param {Core} self Reference to including object
|
24
|
+
###
|
25
|
+
Debuggable = (I={}, self) ->
|
26
|
+
COL_HEIGHT = 175
|
27
|
+
ROW_HEIGHT = 9
|
28
|
+
FONT_SIZE = 9
|
29
|
+
|
30
|
+
debugX = 0
|
31
|
+
debugY = 0
|
32
|
+
|
33
|
+
Object.reverseMerge I,
|
34
|
+
debug:
|
35
|
+
enabled: false
|
36
|
+
color: 'black'
|
37
|
+
filter: 'all'
|
38
|
+
bounds: true
|
39
|
+
velocity: true
|
40
|
+
position:
|
41
|
+
x: 0
|
42
|
+
y: 0
|
43
|
+
|
44
|
+
initialI = Object.extend({}, I)
|
45
|
+
|
46
|
+
debugBounds = (canvas) ->
|
47
|
+
canvas.drawRect
|
48
|
+
color: 'rgba(255, 0, 255, 0.4)'
|
49
|
+
bounds: self.bounds()
|
50
|
+
|
51
|
+
debugVelocity = (canvas) ->
|
52
|
+
if I.velocity?
|
53
|
+
canvas.withTransform Matrix.translation(I.x, I.y), (canvas) ->
|
54
|
+
thickness = 4
|
55
|
+
|
56
|
+
color = 'rgba(255, 0, 0, 0.5)'
|
57
|
+
|
58
|
+
canvas.drawRect
|
59
|
+
x: 0
|
60
|
+
y: -thickness / 2
|
61
|
+
width: I.velocity.x
|
62
|
+
height: thickness
|
63
|
+
color: color
|
64
|
+
|
65
|
+
canvas.drawRect
|
66
|
+
x: -thickness / 2
|
67
|
+
y: 0
|
68
|
+
width: thickness
|
69
|
+
height: I.velocity.y
|
70
|
+
color: color
|
71
|
+
|
72
|
+
filterProperties = (properties) ->
|
73
|
+
results = {}
|
74
|
+
|
75
|
+
switch I.debug.filter
|
76
|
+
when 'all'
|
77
|
+
results = properties
|
78
|
+
when 'undefined'
|
79
|
+
for key, value of properties
|
80
|
+
results[key] = value if not value? or nan(value)
|
81
|
+
when 'changed'
|
82
|
+
for key, value of properties
|
83
|
+
results[key] = value if initialI[key] isnt value
|
84
|
+
|
85
|
+
return results
|
86
|
+
|
87
|
+
sortedKeys = ->
|
88
|
+
keys = []
|
89
|
+
|
90
|
+
for key, value of filterProperties(I)
|
91
|
+
keys.push key
|
92
|
+
|
93
|
+
keys.sort()
|
94
|
+
|
95
|
+
nan = (value) ->
|
96
|
+
typeof value is 'number' and isNaN(value)
|
97
|
+
|
98
|
+
drawDebugLine = (text, canvas, x, y) ->
|
99
|
+
canvas.drawText
|
100
|
+
color: I.debug.color
|
101
|
+
x: x + I.debug.position.x
|
102
|
+
y: y + I.debug.position.y
|
103
|
+
text: text
|
104
|
+
|
105
|
+
debugY += ROW_HEIGHT
|
106
|
+
|
107
|
+
getPropertyRow = (key, value, canvas) ->
|
108
|
+
# exclude functions returned by iterating over
|
109
|
+
# objects like Color, Point, etc.
|
110
|
+
if typeof value is 'function'
|
111
|
+
return
|
112
|
+
else if Object.isObject(value)
|
113
|
+
drawDebugLine(key, canvas, debugX, debugY)
|
114
|
+
|
115
|
+
debugX += 8
|
116
|
+
|
117
|
+
for k, v of value
|
118
|
+
getPropertyRow(k, v, canvas)
|
119
|
+
|
120
|
+
debugX -= 8
|
121
|
+
else if Object.isArray(value)
|
122
|
+
toStringArray = (for v in value
|
123
|
+
if Object.isObject(v)
|
124
|
+
v.I.class || v.toString()
|
125
|
+
else
|
126
|
+
v
|
127
|
+
)
|
128
|
+
|
129
|
+
drawDebugLine("#{key}(#{value.length}): #{toStringArray}", canvas, debugX, debugY)
|
130
|
+
else
|
131
|
+
value = processValue(value)
|
132
|
+
|
133
|
+
drawDebugLine("#{key}: #{value}", canvas, debugX, debugY)
|
134
|
+
|
135
|
+
processValue = (value) ->
|
136
|
+
output = value
|
137
|
+
|
138
|
+
try
|
139
|
+
parsedNumber = parseFloat(value)
|
140
|
+
|
141
|
+
if parsedNumber
|
142
|
+
if typeof value isnt 'string' and parsedNumber isnt parseInt(value)
|
143
|
+
output = value.toFixed(3)
|
144
|
+
|
145
|
+
return output
|
146
|
+
|
147
|
+
self.bind "update", ->
|
148
|
+
if justPressed['0']
|
149
|
+
self.toggleDebug()
|
150
|
+
|
151
|
+
self.bind "overlay", (canvas) ->
|
152
|
+
if I.debug.enabled
|
153
|
+
canvas.font "#{FONT_SIZE}px Monaco"
|
154
|
+
|
155
|
+
debugX = 0
|
156
|
+
debugY = ROW_HEIGHT
|
157
|
+
|
158
|
+
for key in sortedKeys()
|
159
|
+
getPropertyRow(key, I[key], canvas)
|
160
|
+
|
161
|
+
debugX += COL_HEIGHT
|
162
|
+
debugY = ROW_HEIGHT
|
163
|
+
|
164
|
+
debugBounds(canvas) if I.debug.bounds
|
165
|
+
debugVelocity(canvas) if I.debug.velocity
|
166
|
+
|
167
|
+
###*
|
168
|
+
Enable debugging display for the calling GameObject.
|
169
|
+
|
170
|
+
player = GameObject
|
171
|
+
x: 40
|
172
|
+
y: 14
|
173
|
+
spriteName: null
|
174
|
+
numericErrorProperty: NaN
|
175
|
+
|
176
|
+
player.include Debuggable
|
177
|
+
|
178
|
+
# sets up debug output for all player's properties
|
179
|
+
# at the starting position (0, 0)
|
180
|
+
player.debug
|
181
|
+
filter: 'all'
|
182
|
+
|
183
|
+
player.I.y = 45
|
184
|
+
|
185
|
+
# sets up debug output for only properties that have
|
186
|
+
# changed since initialization. In this case only y
|
187
|
+
# would be displayed.
|
188
|
+
player.debug
|
189
|
+
filter: 'changed'
|
190
|
+
|
191
|
+
# sets up debug output for properties that are <code>undefined</code>,
|
192
|
+
# <code>null</code>, or <code>NaN</code>. In this case spriteName and
|
193
|
+
# numericErrorProperty would be displayed.
|
194
|
+
player.debug
|
195
|
+
filter: 'undefined'
|
196
|
+
|
197
|
+
# sets up debug output using all possible configuration options
|
198
|
+
player.debug
|
199
|
+
bounds: true # set this to false to disable visual debugging of the object's bounding box
|
200
|
+
color: 'red' # color of debug text
|
201
|
+
filter: 'all'
|
202
|
+
x: 30 # x position to start printing debug information
|
203
|
+
y: 50 # y position to start printing debug information
|
204
|
+
velocity: true # set this to false to disable visual debugging of the object's velocity
|
205
|
+
|
206
|
+
@name debug
|
207
|
+
@methodOf Debuggable#
|
208
|
+
@param {Object} Options to configure debug output
|
209
|
+
@param {Boolean} bounds Whether or not to visually debug the object's bounds
|
210
|
+
@param {Color|String} color The color of the debug text
|
211
|
+
@param {String} filter Choices include 'all', 'changed', and 'undefined'
|
212
|
+
@param {Number} x The x position to start drawing the debug information
|
213
|
+
@param {Number} y The y position to start drawing the debug information
|
214
|
+
@param {Boolean} velocity Whether or not to visually debug the object's velocity
|
215
|
+
###
|
216
|
+
debug: (options={}) ->
|
217
|
+
{x, y} = options
|
218
|
+
|
219
|
+
I.debug.position.x = x if x?
|
220
|
+
I.debug.position.y = y if y?
|
221
|
+
|
222
|
+
Object.extend I.debug, options
|
223
|
+
|
224
|
+
I.debug.enabled = true
|
225
|
+
|
226
|
+
###*
|
227
|
+
Toggle display of debug information.
|
228
|
+
|
229
|
+
player = GameObject()
|
230
|
+
|
231
|
+
player.include Debuggable
|
232
|
+
|
233
|
+
# enables debug display
|
234
|
+
player.debug()
|
235
|
+
|
236
|
+
# disables debug display
|
237
|
+
player.toggleDisable()
|
238
|
+
|
239
|
+
# if false is passed to toggleDisplay, then debugging is disabled.
|
240
|
+
player.toggleDisplay(false)
|
241
|
+
|
242
|
+
# if true is passed to toggleDisplay, then debugging is enabled.
|
243
|
+
player.toggleDisplay(true)
|
244
|
+
|
245
|
+
@name toggleDebug
|
246
|
+
@methodOf Debuggable#
|
247
|
+
@param {Boolean} newVal If true is passed then debugging is enabled, if false is passed then debugging is disabled, if nothing is passed, then debug state is toggled.
|
248
|
+
###
|
249
|
+
toggleDebug: (newVal) ->
|
250
|
+
if newVal?
|
251
|
+
I.debug.enabled = newVal
|
252
|
+
else
|
253
|
+
I.debug.enabled = not I.debug.enabled
|
@@ -0,0 +1,167 @@
|
|
1
|
+
###*
|
2
|
+
The Drawable module is used to provide a simple draw method to the including
|
3
|
+
object.
|
4
|
+
|
5
|
+
Binds a default draw listener to draw a rectangle or a sprite, if one exists.
|
6
|
+
|
7
|
+
Binds a step listener to update the transform of the object.
|
8
|
+
|
9
|
+
Autoloads the sprite specified in I.sprite, if any.
|
10
|
+
|
11
|
+
player = Core
|
12
|
+
x: 15
|
13
|
+
y: 30
|
14
|
+
width: 5
|
15
|
+
height: 5
|
16
|
+
sprite: "my_cool_sprite"
|
17
|
+
|
18
|
+
engine.bind 'draw', (canvas) ->
|
19
|
+
player.draw(canvas)
|
20
|
+
# => Uncaught TypeError: Object has no method 'draw'
|
21
|
+
|
22
|
+
player.include(Drawable)
|
23
|
+
|
24
|
+
engine.bind 'draw', (canvas) ->
|
25
|
+
player.draw(canvas)
|
26
|
+
# => if you have a sprite named "my_cool_sprite" in your images folder
|
27
|
+
# then it will be drawn. Otherwise, a rectangle positioned at x: 15 and
|
28
|
+
# y: 30 with width and height 5 will be drawn.
|
29
|
+
|
30
|
+
@name Drawable
|
31
|
+
@module
|
32
|
+
@constructor
|
33
|
+
@param {Object} I Instance variables
|
34
|
+
@param {Core} self Reference to including object
|
35
|
+
###
|
36
|
+
|
37
|
+
###*
|
38
|
+
Triggered every time the object should be drawn. A canvas is passed as
|
39
|
+
the first argument.
|
40
|
+
|
41
|
+
player = Core
|
42
|
+
x: 0
|
43
|
+
y: 10
|
44
|
+
width: 5
|
45
|
+
height: 5
|
46
|
+
|
47
|
+
player.bind "draw", (canvas) ->
|
48
|
+
# Text will be drawn positioned relatively to the object.
|
49
|
+
canvas.drawText
|
50
|
+
text: "Hey, drawing stuff is pretty easy."
|
51
|
+
color: "white"
|
52
|
+
x: 5
|
53
|
+
y: 5
|
54
|
+
|
55
|
+
@name draw
|
56
|
+
@methodOf Drawable#
|
57
|
+
@event
|
58
|
+
@param {PowerCanvas} canvas A reference to the canvas to draw on.
|
59
|
+
###
|
60
|
+
|
61
|
+
###*
|
62
|
+
Triggered before the object should be drawn. A canvas is passed as
|
63
|
+
the first argument. This does not apply the current transform.
|
64
|
+
|
65
|
+
@name beforeTransform
|
66
|
+
@methodOf Drawable#
|
67
|
+
@event
|
68
|
+
@param {PowerCanvas} canvas A reference to the canvas to draw on.
|
69
|
+
###
|
70
|
+
|
71
|
+
###*
|
72
|
+
Triggered after the object should be drawn. A canvas is passed as
|
73
|
+
the first argument. This applies the current transform.
|
74
|
+
|
75
|
+
@name afterTransform
|
76
|
+
@methodOf Drawable#
|
77
|
+
@event
|
78
|
+
@param {PowerCanvas} canvas A reference to the canvas to draw on.
|
79
|
+
###
|
80
|
+
|
81
|
+
Drawable = (I={}, self) ->
|
82
|
+
Object.reverseMerge I,
|
83
|
+
alpha: 1
|
84
|
+
color: "#196"
|
85
|
+
scale: 1
|
86
|
+
scaleX: 1
|
87
|
+
scaleY: 1
|
88
|
+
zIndex: 0
|
89
|
+
|
90
|
+
cachedSprite = null
|
91
|
+
|
92
|
+
self.unbind ".Drawable"
|
93
|
+
|
94
|
+
self.bind 'draw.Drawable', (canvas) ->
|
95
|
+
if I.alpha? and I.alpha != 1
|
96
|
+
previousAlpha = canvas.context().globalAlpha
|
97
|
+
canvas.context().globalAlpha = I.alpha
|
98
|
+
|
99
|
+
if sprite = self.sprite()
|
100
|
+
if sprite.draw?
|
101
|
+
sprite.draw(canvas, -sprite.width / 2, -sprite.height / 2)
|
102
|
+
else
|
103
|
+
warn?("Sprite has no draw method!")
|
104
|
+
else
|
105
|
+
if I.radius?
|
106
|
+
canvas.drawCircle
|
107
|
+
x: 0
|
108
|
+
y: 0
|
109
|
+
radius: I.radius
|
110
|
+
color: I.color
|
111
|
+
else
|
112
|
+
canvas.drawRect
|
113
|
+
x: -I.width/2
|
114
|
+
y: -I.height/2
|
115
|
+
width: I.width
|
116
|
+
height: I.height
|
117
|
+
color: I.color
|
118
|
+
|
119
|
+
if I.alpha? and I.alpha != 1
|
120
|
+
canvas.context().globalAlpha = previousAlpha
|
121
|
+
|
122
|
+
###*
|
123
|
+
Draw does not actually do any drawing itself, instead it triggers all of the draw events.
|
124
|
+
Listeners on the events do the actual drawing.
|
125
|
+
|
126
|
+
@name draw
|
127
|
+
@methodOf Drawable#
|
128
|
+
@returns self
|
129
|
+
###
|
130
|
+
draw: (canvas) ->
|
131
|
+
self.trigger 'beforeTransform', canvas
|
132
|
+
|
133
|
+
canvas.withTransform self.transform(), (canvas) ->
|
134
|
+
self.trigger 'beforeDraw', canvas
|
135
|
+
self.trigger 'draw', canvas
|
136
|
+
self.trigger 'afterDraw', canvas
|
137
|
+
|
138
|
+
self.trigger 'afterTransform', canvas
|
139
|
+
|
140
|
+
return self
|
141
|
+
|
142
|
+
sprite: (newSprite) ->
|
143
|
+
if newSprite?
|
144
|
+
cachedSprite = newSprite
|
145
|
+
else
|
146
|
+
if I.sprite
|
147
|
+
Sprite.loadByName(I.sprite)
|
148
|
+
|
149
|
+
###*
|
150
|
+
Returns the current transform, with translation, rotation, and flipping applied.
|
151
|
+
|
152
|
+
@name transform
|
153
|
+
@methodOf Drawable#
|
154
|
+
@returns {Matrix} The current transform
|
155
|
+
###
|
156
|
+
transform: ->
|
157
|
+
center = self.center()
|
158
|
+
|
159
|
+
transform = Matrix.translation(center.x.floor(), center.y.floor())
|
160
|
+
|
161
|
+
transform = transform.concat(Matrix.scale(I.scale * I.scaleX, I.scale * I.scaleY))
|
162
|
+
transform = transform.concat(Matrix.rotation(I.rotation)) if I.rotation
|
163
|
+
|
164
|
+
if I.spriteOffset
|
165
|
+
transform = transform.concat(Matrix.translation(I.spriteOffset.x, I.spriteOffset.y))
|
166
|
+
|
167
|
+
return transform
|
@@ -0,0 +1,36 @@
|
|
1
|
+
DustParticle = (I={}, self) ->
|
2
|
+
Object.extend I,
|
3
|
+
color: 'rgb(100, 100, 100)'
|
4
|
+
duration: 0.2
|
5
|
+
fadeOut: true
|
6
|
+
maxSpeed: 90
|
7
|
+
|
8
|
+
I.velocity = [
|
9
|
+
Point(-60, -30)
|
10
|
+
Point(40, -15)
|
11
|
+
Point(-20, -7)
|
12
|
+
Point(60, -30)
|
13
|
+
Point(40, -15)
|
14
|
+
Point(20, -7)
|
15
|
+
].rand()
|
16
|
+
|
17
|
+
I.acceleration = Point(0, 60)
|
18
|
+
|
19
|
+
return {}
|
20
|
+
|
21
|
+
DustEmitter = (I={}) ->
|
22
|
+
Object.reverseMerge I,
|
23
|
+
duration: 3
|
24
|
+
particleCount: 20
|
25
|
+
batchSize: 5
|
26
|
+
x: 0
|
27
|
+
y: 0
|
28
|
+
zIndex: 50
|
29
|
+
generator:
|
30
|
+
includedModules: ["DustParticle"]
|
31
|
+
radius: (n) ->
|
32
|
+
[2, 3, 1].wrap(n)
|
33
|
+
|
34
|
+
self = Emitter(I)
|
35
|
+
|
36
|
+
return self
|
@@ -0,0 +1,38 @@
|
|
1
|
+
( ->
|
2
|
+
Easing =
|
3
|
+
sinusoidal: (begin, end) ->
|
4
|
+
change = end - begin
|
5
|
+
(t) -> begin + change * (1 - Math.cos(t * Math.TAU / 4))
|
6
|
+
|
7
|
+
sinusoidalOut: (begin, end) ->
|
8
|
+
change = end - begin
|
9
|
+
(t) -> begin + change * (0 + Math.sin(t * Math.TAU / 4))
|
10
|
+
|
11
|
+
polynomialEasings = ["linear", "quadratic", "cubic", "quartic", "quintic"]
|
12
|
+
|
13
|
+
polynomialEasings.each (easing, i) ->
|
14
|
+
exponent = i + 1
|
15
|
+
sign = if exponent % 2 then 1 else -1
|
16
|
+
|
17
|
+
Easing[easing] = (begin, end) ->
|
18
|
+
change = (end - begin)
|
19
|
+
(t) -> begin + change * Math.pow(t, exponent)
|
20
|
+
|
21
|
+
Easing["#{easing}Out"] = (begin, end) ->
|
22
|
+
change = end - begin
|
23
|
+
(t) -> begin + change * (1 + sign * Math.pow(t - 1, exponent))
|
24
|
+
|
25
|
+
["sinusoidal"].concat(polynomialEasings).each (easing) ->
|
26
|
+
Easing["#{easing}InOut"] = (begin, end) ->
|
27
|
+
midpoint = (begin + end)/2
|
28
|
+
easeIn = Easing[easing](begin, midpoint)
|
29
|
+
easeOut = Easing["#{easing}Out"](midpoint, end)
|
30
|
+
|
31
|
+
(t) ->
|
32
|
+
if t < 0.5
|
33
|
+
easeIn(2 * t)
|
34
|
+
else
|
35
|
+
easeOut(2 * t - 1)
|
36
|
+
|
37
|
+
(exports ? this)["Easing"] = Easing
|
38
|
+
)()
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# TODO: LOL at name
|
2
|
+
Emitterable = (I={}, self) ->
|
3
|
+
Object.reverseMerge I,
|
4
|
+
batchSize: 1
|
5
|
+
emissionRate: 1
|
6
|
+
width: 0
|
7
|
+
height: 0
|
8
|
+
sprite: Sprite.EMPTY
|
9
|
+
generator: {}
|
10
|
+
particles: []
|
11
|
+
particleCount: Infinity
|
12
|
+
x: I.x
|
13
|
+
y: I.y
|
14
|
+
particleData:
|
15
|
+
acceleration: Point(0, 0.1)
|
16
|
+
age: 0
|
17
|
+
color: "blue"
|
18
|
+
duration: 1.5
|
19
|
+
height: 2
|
20
|
+
maxSpeed: 120
|
21
|
+
offset: Point(0, 0)
|
22
|
+
sprite: false
|
23
|
+
spriteName: false
|
24
|
+
velocity: Point(-0.25, 1)
|
25
|
+
width: 2
|
26
|
+
x: 0
|
27
|
+
y: 0
|
28
|
+
|
29
|
+
n = 0
|
30
|
+
|
31
|
+
self.bind 'draw', (canvas) ->
|
32
|
+
I.particles.invoke "draw", canvas
|
33
|
+
|
34
|
+
self.bind 'overlay', (canvas) ->
|
35
|
+
I.particles.invoke "trigger", "overlay", canvas
|
36
|
+
|
37
|
+
self.bind 'beforeUpdate', (dt) ->
|
38
|
+
I.particles.invoke "trigger", "beforeUpdate", dt
|
39
|
+
|
40
|
+
self.bind 'afterUpdate', (dt) ->
|
41
|
+
I.particles.invoke "trigger", "afterUpdate", dt
|
42
|
+
|
43
|
+
self.bind 'update', (dt) ->
|
44
|
+
I.batchSize.times ->
|
45
|
+
if n < I.particleCount && rand() < I.emissionRate
|
46
|
+
particleProperties = Object.extend {}, I.particleData
|
47
|
+
|
48
|
+
for key, value of I.generator
|
49
|
+
if I.generator[key].call
|
50
|
+
particleProperties[key] = I.generator[key](n, I)
|
51
|
+
else
|
52
|
+
particleProperties[key] = I.generator[key]
|
53
|
+
|
54
|
+
particleProperties.x += particleProperties.offset.x
|
55
|
+
particleProperties.y += particleProperties.offset.y
|
56
|
+
|
57
|
+
I.particles.push(GameObject(particleProperties))
|
58
|
+
|
59
|
+
n += 1
|
60
|
+
|
61
|
+
I.particles = I.particles.select (particle) ->
|
62
|
+
particle.update(dt)
|
63
|
+
|
64
|
+
if n == I.particleCount && !I.particles.length
|
65
|
+
I.active = false
|
66
|
+
|
67
|
+
return {}
|
68
|
+
|