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.
- 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())
|