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,27 @@
|
|
1
|
+
Camera.Shake = (I, self) ->
|
2
|
+
Object.reverseMerge I,
|
3
|
+
shakeIntensity: 20
|
4
|
+
shakeCooldown: 0
|
5
|
+
|
6
|
+
defaultParams =
|
7
|
+
duration: 10
|
8
|
+
intensity: 20
|
9
|
+
|
10
|
+
self.bind "afterUpdate", ->
|
11
|
+
I.shakeCooldown = I.shakeCooldown.approach(0, 1)
|
12
|
+
|
13
|
+
self.transformFilterChain (transform) ->
|
14
|
+
if I.shakeCooldown > 0
|
15
|
+
transform.tx += signedRand(I.shakeIntensity)
|
16
|
+
transform.ty += signedRand(I.shakeIntensity)
|
17
|
+
|
18
|
+
return transform
|
19
|
+
|
20
|
+
shake: (options={}) ->
|
21
|
+
{duration, intensity} = Object.reverseMerge(options, defaultParams)
|
22
|
+
|
23
|
+
I.shakeCooldown = duration * I.zoom
|
24
|
+
I.shakeIntensity = intensity * I.zoom
|
25
|
+
|
26
|
+
self
|
27
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Camera.Zoom = (I, self) ->
|
2
|
+
Object.reverseMerge I,
|
3
|
+
maxZoom: 10
|
4
|
+
minZoom: 0.1
|
5
|
+
zoom: 1
|
6
|
+
|
7
|
+
self.transformFilterChain (transform) ->
|
8
|
+
transform.scale(I.zoom, I.zoom, self.position())
|
9
|
+
|
10
|
+
clampZoom = (value) ->
|
11
|
+
value.clamp(I.minZoom, I.maxZoom)
|
12
|
+
|
13
|
+
zoomIn: (percentage) ->
|
14
|
+
self.zoom clampZoom(I.zoom * (1 + percentage))
|
15
|
+
|
16
|
+
zoomOut: (percentage) ->
|
17
|
+
self.zoom clampZoom(I.zoom * (1 - percentage))
|
18
|
+
|
19
|
+
zoom: (value) ->
|
20
|
+
if value?
|
21
|
+
I.zoom = clampZoom(value)
|
22
|
+
|
23
|
+
return self
|
24
|
+
else
|
25
|
+
return I.zoom
|
@@ -0,0 +1,61 @@
|
|
1
|
+
###*
|
2
|
+
The `Clampable` module provides helper methods to clamp object properties. This module is included by default in `GameObject`
|
3
|
+
|
4
|
+
player = GameObject
|
5
|
+
x: 40
|
6
|
+
y: 30
|
7
|
+
|
8
|
+
player.include Clampable
|
9
|
+
|
10
|
+
@name Clampable
|
11
|
+
@module
|
12
|
+
@constructor
|
13
|
+
@param {Object} I Instance variables
|
14
|
+
@param {Core} self Reference to including object
|
15
|
+
###
|
16
|
+
Clampable = (I={}, self) ->
|
17
|
+
Object.reverseMerge I,
|
18
|
+
clampData: {}
|
19
|
+
|
20
|
+
self.bind "afterUpdate", ->
|
21
|
+
for property, data of I.clampData
|
22
|
+
I[property] = I[property].clamp(data.min, data.max)
|
23
|
+
|
24
|
+
###*
|
25
|
+
Keep an objects attributes within a given range.
|
26
|
+
|
27
|
+
# Player's health will be within [0, 100] at the end of every update
|
28
|
+
player.clamp
|
29
|
+
health:
|
30
|
+
min: 0
|
31
|
+
max: 100
|
32
|
+
|
33
|
+
# Score can only be positive
|
34
|
+
player.clamp
|
35
|
+
score:
|
36
|
+
min: 0
|
37
|
+
|
38
|
+
@name clamp
|
39
|
+
@methodOf Clampable#
|
40
|
+
@param {Object} data
|
41
|
+
###
|
42
|
+
clamp: (data) ->
|
43
|
+
Object.extend(I.clampData, data)
|
44
|
+
|
45
|
+
###*
|
46
|
+
Helper to clamp the `x` and `y` properties of the object to be within a given bounds.
|
47
|
+
|
48
|
+
@name clampToBounds
|
49
|
+
@methodOf Clampable#
|
50
|
+
@param {Rectangle} [bounds] The bounds to clamp the object's position within. Defaults to the app size if none given.
|
51
|
+
###
|
52
|
+
clampToBounds: (bounds) ->
|
53
|
+
bounds ||= Rectangle x: 0, y: 0, width: App.width, height: App.height
|
54
|
+
|
55
|
+
self.clamp
|
56
|
+
x:
|
57
|
+
min: bounds.x + I.width/2
|
58
|
+
max: bounds.width - I.width/2
|
59
|
+
y:
|
60
|
+
min: bounds.y + I.height/2
|
61
|
+
max: bounds.height - I.height/2
|
@@ -0,0 +1,126 @@
|
|
1
|
+
( ->
|
2
|
+
Collidable = (I, self) ->
|
3
|
+
# Set some default properties
|
4
|
+
Object.reverseMerge I,
|
5
|
+
allowCollisions: ANY
|
6
|
+
immovable: false
|
7
|
+
touching: NONE
|
8
|
+
velocity: Point(0, 0)
|
9
|
+
mass: 1
|
10
|
+
elasticity: 0
|
11
|
+
|
12
|
+
self.attrAccessor(
|
13
|
+
"immovable",
|
14
|
+
"velocity",
|
15
|
+
"mass",
|
16
|
+
"elasticity",
|
17
|
+
)
|
18
|
+
|
19
|
+
solid: (newSolid) ->
|
20
|
+
if newSolid?
|
21
|
+
if newSolid
|
22
|
+
I.allowCollisions = ANY
|
23
|
+
else
|
24
|
+
I.allowCollisions = NONE
|
25
|
+
else
|
26
|
+
I.allowCollisions
|
27
|
+
|
28
|
+
(exports ? this)["Collidable"] = Collidable
|
29
|
+
|
30
|
+
{NONE, LEFT, RIGHT, UP, DOWN} = Object.extend Collidable,
|
31
|
+
NONE: 0x0000
|
32
|
+
LEFT: 0x0001
|
33
|
+
RIGHT: 0x0010
|
34
|
+
UP: 0x0100
|
35
|
+
DOWN: 0x1000
|
36
|
+
|
37
|
+
{ANY, FLOOR, WALL, CEILING} = Object.extend Collidable,
|
38
|
+
FLOOR: DOWN
|
39
|
+
WALL: LEFT | RIGHT
|
40
|
+
CEILING: UP
|
41
|
+
ANY: LEFT | RIGHT | UP | DOWN
|
42
|
+
|
43
|
+
Object.extend Collidable,
|
44
|
+
separate: (a, b) ->
|
45
|
+
return if a.immovable() && b.immovable()
|
46
|
+
|
47
|
+
aBounds = a.bounds()
|
48
|
+
bBounds = b.bounds()
|
49
|
+
|
50
|
+
aVelocity = a.velocity()
|
51
|
+
bVelocity = b.velocity()
|
52
|
+
|
53
|
+
deltaVelocity = aVelocity.subtract(bVelocity)
|
54
|
+
|
55
|
+
overlap = Point(0, 0)
|
56
|
+
|
57
|
+
if Collision.rectangular(aBounds, bBounds)
|
58
|
+
if deltaVelocity.x > 0
|
59
|
+
overlap.x = aBounds.x + aBounds.width - bBounds.x
|
60
|
+
if !(a.I.allowCollisions & RIGHT) || !(b.I.allowCollisions & LEFT)
|
61
|
+
overlap.x = 0
|
62
|
+
else
|
63
|
+
a.I.touching |= RIGHT
|
64
|
+
b.I.touching |= LEFT
|
65
|
+
|
66
|
+
else if deltaVelocity.x < 0
|
67
|
+
overlap.x = aBounds.x - bBounds.width - bBounds.x
|
68
|
+
if !(a.I.allowCollisions & LEFT) || !(b.I.allowCollisions & RIGHT)
|
69
|
+
overlap.x = 0
|
70
|
+
else
|
71
|
+
a.I.touching |= LEFT
|
72
|
+
b.I.touching |= RIGHT
|
73
|
+
|
74
|
+
if deltaVelocity.y > 0
|
75
|
+
overlap.y = aBounds.y + aBounds.height - bBounds.y
|
76
|
+
if !(a.I.allowCollisions & DOWN) || !(b.I.allowCollisions & UP)
|
77
|
+
overlap.y = 0
|
78
|
+
else
|
79
|
+
a.I.touching |= DOWN
|
80
|
+
b.I.touching |= UP
|
81
|
+
|
82
|
+
else if deltaVelocity.y < 0
|
83
|
+
overlap.y = aBounds.y - bBounds.height - bBounds.y
|
84
|
+
if !(a.I.allowCollisions & UP) || !(b.I.allowCollisions & DOWN)
|
85
|
+
overlap.y = 0
|
86
|
+
else
|
87
|
+
a.I.touching |= UP
|
88
|
+
b.I.touching |= DOWN
|
89
|
+
|
90
|
+
unless overlap.equal(Point.ZERO)
|
91
|
+
if !a.immovable() and !b.immovable()
|
92
|
+
a.changePosition(overlap.scale(-0.5))
|
93
|
+
b.changePosition(overlap.scale(+0.5))
|
94
|
+
|
95
|
+
# Elastic collision
|
96
|
+
relativeVelocity = aVelocity.subtract(bVelocity)
|
97
|
+
|
98
|
+
aMass = a.mass()
|
99
|
+
bMass = b.mass()
|
100
|
+
totalMass = bMass + aMass
|
101
|
+
|
102
|
+
normal = overlap.norm()
|
103
|
+
|
104
|
+
pushA = normal.scale(-2 * (relativeVelocity.dot(normal) * (bMass / totalMass)))
|
105
|
+
pushB = normal.scale(+2 * (relativeVelocity.dot(normal) * (aMass / totalMass)))
|
106
|
+
average = pushA.add(pushB).scale(0.5)
|
107
|
+
|
108
|
+
pushA.subtract$(average).scale(a.elasticity())
|
109
|
+
pushB.subtract$(average).scale(b.elasticity())
|
110
|
+
|
111
|
+
a.I.velocity = average.add(pushA)
|
112
|
+
b.I.velocity = average.add(pushB)
|
113
|
+
|
114
|
+
else if !a.immovable()
|
115
|
+
a.changePosition(overlap.scale(-1))
|
116
|
+
|
117
|
+
a.I.velocity = bVelocity.subtract(aVelocity.scale(a.elasticity()))
|
118
|
+
|
119
|
+
else if !b.immovable()
|
120
|
+
b.changePosition(overlap)
|
121
|
+
|
122
|
+
b.I.velocity = aVelocity.subtract(bVelocity.scale(b.elasticity()))
|
123
|
+
|
124
|
+
return true
|
125
|
+
)()
|
126
|
+
|
@@ -0,0 +1,272 @@
|
|
1
|
+
( ->
|
2
|
+
# Assume game objects
|
3
|
+
collides = (a, b) ->
|
4
|
+
# TODO: Be smart about auto-detecting collision types
|
5
|
+
Collision.rectangular(a.bounds(), b.bounds())
|
6
|
+
|
7
|
+
###*
|
8
|
+
Collision holds many useful class methods for checking geometric overlap of various objects.
|
9
|
+
|
10
|
+
@name Collision
|
11
|
+
@namespace
|
12
|
+
###
|
13
|
+
Collision =
|
14
|
+
###*
|
15
|
+
Collision holds many useful class methods for checking geometric overlap of various objects.
|
16
|
+
|
17
|
+
player = engine.add
|
18
|
+
class: "Player"
|
19
|
+
x: 0
|
20
|
+
y: 0
|
21
|
+
width: 10
|
22
|
+
height: 10
|
23
|
+
|
24
|
+
enemy = engine.add
|
25
|
+
class: "Enemy"
|
26
|
+
x: 5
|
27
|
+
y: 5
|
28
|
+
width: 10
|
29
|
+
height: 10
|
30
|
+
|
31
|
+
enemy2 = engine.add
|
32
|
+
class: "Enemy"
|
33
|
+
x: -5
|
34
|
+
y: -5
|
35
|
+
width: 10
|
36
|
+
height: 10
|
37
|
+
|
38
|
+
Collision.collide(player, enemy, (p, e) -> ...)
|
39
|
+
# => callback is called once
|
40
|
+
|
41
|
+
Collision.collide(player, [enemy, enemy2], (p, e) -> ...)
|
42
|
+
# => callback is called twice
|
43
|
+
|
44
|
+
Collision.collide("Player", "Enemy", (p, e) -> ...)
|
45
|
+
# => callback is also called twice
|
46
|
+
|
47
|
+
@name collide
|
48
|
+
@methodOf Collision
|
49
|
+
@param {Object|Array|String} groupA An object or set of objects to check collisions with
|
50
|
+
@param {Object|Array|String} groupB An object or set of objects to check collisions with
|
51
|
+
@param {Function} callback The callback to call when an object of groupA collides
|
52
|
+
with an object of groupB: (a, b) ->
|
53
|
+
@param {Function} [detectionMethod] An optional detection method to determine when two
|
54
|
+
objects are colliding.
|
55
|
+
###
|
56
|
+
collide: (groupA, groupB, callback, detectionMethod=collides) ->
|
57
|
+
if Object.isString(groupA)
|
58
|
+
groupA = engine.find(groupA)
|
59
|
+
else
|
60
|
+
groupA = [].concat(groupA)
|
61
|
+
|
62
|
+
if Object.isString(groupB)
|
63
|
+
groupB = engine.find(groupB)
|
64
|
+
else
|
65
|
+
groupB = [].concat(groupB)
|
66
|
+
|
67
|
+
groupA.each (a) ->
|
68
|
+
groupB.each (b) ->
|
69
|
+
callback(a, b) if detectionMethod(a, b)
|
70
|
+
|
71
|
+
###*
|
72
|
+
Takes two bounds objects and returns true if they collide (overlap), false otherwise.
|
73
|
+
Bounds objects have x, y, width and height properties.
|
74
|
+
|
75
|
+
player = GameObject
|
76
|
+
x: 0
|
77
|
+
y: 0
|
78
|
+
width: 10
|
79
|
+
height: 10
|
80
|
+
|
81
|
+
enemy = GameObject
|
82
|
+
x: 5
|
83
|
+
y: 5
|
84
|
+
width: 10
|
85
|
+
height: 10
|
86
|
+
|
87
|
+
Collision.rectangular(player, enemy)
|
88
|
+
# => true
|
89
|
+
|
90
|
+
Collision.rectangular(player, {x: 50, y: 40, width: 30, height: 30})
|
91
|
+
# => false
|
92
|
+
|
93
|
+
@name rectangular
|
94
|
+
@methodOf Collision
|
95
|
+
@param {Object} a The first rectangle
|
96
|
+
@param {Object} b The second rectangle
|
97
|
+
@returns {Boolean} true if the rectangles overlap, false otherwise
|
98
|
+
###
|
99
|
+
rectangular: (a, b) ->
|
100
|
+
a.x < b.x + b.width &&
|
101
|
+
a.x + a.width > b.x &&
|
102
|
+
a.y < b.y + b.height &&
|
103
|
+
a.y + a.height > b.y
|
104
|
+
|
105
|
+
###*
|
106
|
+
Takes two circle objects and returns true if they collide (overlap), false otherwise.
|
107
|
+
Circle objects have x, y, and radius.
|
108
|
+
|
109
|
+
player = GameObject
|
110
|
+
x: 5
|
111
|
+
y: 5
|
112
|
+
radius: 10
|
113
|
+
|
114
|
+
enemy = GameObject
|
115
|
+
x: 10
|
116
|
+
y: 10
|
117
|
+
radius: 10
|
118
|
+
|
119
|
+
farEnemy = GameObject
|
120
|
+
x: 500
|
121
|
+
y: 500
|
122
|
+
radius: 30
|
123
|
+
|
124
|
+
Collision.circular(player, enemy)
|
125
|
+
# => true
|
126
|
+
|
127
|
+
Collision.circular(player, farEnemy)
|
128
|
+
# => false
|
129
|
+
|
130
|
+
@name circular
|
131
|
+
@methodOf Collision
|
132
|
+
@param {Object} a The first circle
|
133
|
+
@param {Object} b The second circle
|
134
|
+
@returns {Boolean} true is the circles overlap, false otherwise
|
135
|
+
###
|
136
|
+
circular: (a, b) ->
|
137
|
+
r = a.radius + b.radius
|
138
|
+
dx = b.x - a.x
|
139
|
+
dy = b.y - a.y
|
140
|
+
|
141
|
+
r * r >= dx * dx + dy * dy
|
142
|
+
|
143
|
+
###*
|
144
|
+
Detects whether a line intersects a circle.
|
145
|
+
|
146
|
+
circle = engine.add
|
147
|
+
class: "circle"
|
148
|
+
x: 50
|
149
|
+
y: 50
|
150
|
+
radius: 10
|
151
|
+
|
152
|
+
Collision.rayCircle(Point(0, 0), Point(1, 0), circle)
|
153
|
+
# => true
|
154
|
+
|
155
|
+
@name rayCircle
|
156
|
+
@methodOf Collision
|
157
|
+
@param {Point} source The starting position
|
158
|
+
@param {Point} direction A vector from the point
|
159
|
+
@param {Object} target The circle
|
160
|
+
@returns {Boolean} true if the line intersects the circle, false otherwise
|
161
|
+
###
|
162
|
+
rayCircle: (source, direction, target) ->
|
163
|
+
radius = target.radius()
|
164
|
+
target = target.position()
|
165
|
+
|
166
|
+
laserToTarget = target.subtract(source)
|
167
|
+
|
168
|
+
projectionLength = direction.dot(laserToTarget)
|
169
|
+
|
170
|
+
if projectionLength < 0
|
171
|
+
return false # object is behind
|
172
|
+
|
173
|
+
projection = direction.scale(projectionLength)
|
174
|
+
|
175
|
+
intersection = source.add(projection)
|
176
|
+
intersectionToTarget = target.subtract(intersection)
|
177
|
+
intersectionToTargetLength = intersectionToTarget.length()
|
178
|
+
|
179
|
+
if intersectionToTargetLength < radius
|
180
|
+
hit = true
|
181
|
+
|
182
|
+
if hit
|
183
|
+
dt = Math.sqrt(radius * radius - intersectionToTargetLength * intersectionToTargetLength)
|
184
|
+
|
185
|
+
hit = direction.scale(projectionLength - dt).add(source)
|
186
|
+
|
187
|
+
###*
|
188
|
+
Detects whether a line intersects a rectangle.
|
189
|
+
|
190
|
+
rect = engine.add
|
191
|
+
class: "circle"
|
192
|
+
x: 50
|
193
|
+
y: 50
|
194
|
+
width: 20
|
195
|
+
height: 20
|
196
|
+
|
197
|
+
Collision.rayRectangle(Point(0, 0), Point(1, 0), rect)
|
198
|
+
# => true
|
199
|
+
|
200
|
+
@name rayRectangle
|
201
|
+
@methodOf Collision
|
202
|
+
@param {Point} source The starting position
|
203
|
+
@param {Point} direction A vector from the point
|
204
|
+
@param {Object} target The rectangle
|
205
|
+
@returns {Boolean} true if the line intersects the rectangle, false otherwise
|
206
|
+
###
|
207
|
+
rayRectangle: (source, direction, target) ->
|
208
|
+
unless target.xw? and target.yw?
|
209
|
+
if target.width? and target.height?
|
210
|
+
xw = target.width/2
|
211
|
+
yw = target.height/2
|
212
|
+
|
213
|
+
# Convert from bounds rect to centeredBounds rect
|
214
|
+
return Collision.rayRectangle source, direction,
|
215
|
+
x: target.x + xw
|
216
|
+
y: target.y + yw
|
217
|
+
xw: xw
|
218
|
+
yw: yw
|
219
|
+
else
|
220
|
+
error "Bounds object isn't a rectangle"
|
221
|
+
|
222
|
+
return
|
223
|
+
|
224
|
+
xw = target.xw
|
225
|
+
yw = target.yw
|
226
|
+
|
227
|
+
if source.x < target.x
|
228
|
+
xval = target.x - xw
|
229
|
+
else
|
230
|
+
xval = target.x + xw
|
231
|
+
|
232
|
+
if source.y < target.y
|
233
|
+
yval = target.y - yw
|
234
|
+
else
|
235
|
+
yval = target.y + yw
|
236
|
+
|
237
|
+
if direction.x == 0
|
238
|
+
p0 = Point(target.x - xw, yval)
|
239
|
+
p1 = Point(target.x + xw, yval)
|
240
|
+
|
241
|
+
t = (yval - source.y) / direction.y
|
242
|
+
else if direction.y == 0
|
243
|
+
p0 = Point(xval, target.y - yw)
|
244
|
+
p1 = Point(xval, target.y + yw)
|
245
|
+
|
246
|
+
t = (xval - source.x) / direction.x
|
247
|
+
else
|
248
|
+
tX = (xval - source.x) / direction.x
|
249
|
+
tY = (yval - source.y) / direction.y
|
250
|
+
|
251
|
+
# TODO: These special cases are gross!
|
252
|
+
if (tX < tY || (-xw < source.x - target.x < xw)) && !(-yw < source.y - target.y < yw)
|
253
|
+
p0 = Point(target.x - xw, yval)
|
254
|
+
p1 = Point(target.x + xw, yval)
|
255
|
+
|
256
|
+
t = tY
|
257
|
+
else
|
258
|
+
p0 = Point(xval, target.y - yw)
|
259
|
+
p1 = Point(xval, target.y + yw)
|
260
|
+
|
261
|
+
t = tX
|
262
|
+
|
263
|
+
if t > 0
|
264
|
+
areaPQ0 = direction.cross(p0.subtract(source))
|
265
|
+
areaPQ1 = direction.cross(p1.subtract(source))
|
266
|
+
|
267
|
+
if areaPQ0 * areaPQ1 < 0
|
268
|
+
hit = direction.scale(t).add(source)
|
269
|
+
|
270
|
+
(exports ? this)["Collision"] = Collision
|
271
|
+
)()
|
272
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
CollisionResponse = (I={}, self) ->
|
2
|
+
# Handle multi-include
|
3
|
+
self.unbind ".Movable"
|
4
|
+
|
5
|
+
self.bind 'update.Movable', (elapsedTime) ->
|
6
|
+
t = (elapsedTime * I.velocity.x).abs()
|
7
|
+
unit = I.velocity.x.sign()
|
8
|
+
|
9
|
+
# x dimension
|
10
|
+
t.times ->
|
11
|
+
if self.collide(unit, 0, ".solid")
|
12
|
+
I.velocity.x = 0
|
13
|
+
else
|
14
|
+
I.x += unit
|
15
|
+
|
16
|
+
t = (elapsedTime * I.velocity.y).abs()
|
17
|
+
unit = I.velocity.y.sign()
|
18
|
+
# y dimension
|
19
|
+
t.times ->
|
20
|
+
if self.collide(0, unit, ".solid")
|
21
|
+
I.velocity.y = 0
|
22
|
+
else
|
23
|
+
I.y += unit
|
24
|
+
|
25
|
+
self.extend
|
26
|
+
collide: (xOffset, yOffset, className) ->
|
27
|
+
engine.find(className).inject false, (hit, block) ->
|
28
|
+
hit || Collision.rectangular(self.bounds(xOffset, yOffset), block.bounds())
|