/org/flixel/FlxObject.as
ActionScript | 1173 lines | 682 code | 84 blank | 407 comment | 187 complexity | 1aca37ce89b9e7a5b655b3c1ece7db3d MD5 | raw file
1package org.flixel
2{
3 import flash.display.Graphics;
4 import flash.display.Sprite;
5 import flash.geom.Point;
6
7 import org.flixel.FlxBasic;
8
9 /**
10 * This is the base class for most of the display objects (<code>FlxSprite</code>, <code>FlxText</code>, etc).
11 * It includes some basic attributes about game objects, including retro-style flickering,
12 * basic state information, sizes, scrolling, and basic physics and motion.
13 *
14 * @author Adam Atomic
15 */
16 public class FlxObject extends FlxBasic
17 {
18 /**
19 * Generic value for "left" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
20 */
21 static public const LEFT:uint = 0x0001;
22 /**
23 * Generic value for "right" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
24 */
25 static public const RIGHT:uint = 0x0010;
26 /**
27 * Generic value for "up" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
28 */
29 static public const UP:uint = 0x0100;
30 /**
31 * Generic value for "down" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
32 */
33 static public const DOWN:uint = 0x1000;
34
35 /**
36 * Special-case constant meaning no collisions, used mainly by <code>allowCollisions</code> and <code>touching</code>.
37 */
38 static public const NONE:uint = 0;
39 /**
40 * Special-case constant meaning up, used mainly by <code>allowCollisions</code> and <code>touching</code>.
41 */
42 static public const CEILING:uint= UP;
43 /**
44 * Special-case constant meaning down, used mainly by <code>allowCollisions</code> and <code>touching</code>.
45 */
46 static public const FLOOR:uint = DOWN;
47 /**
48 * Special-case constant meaning only the left and right sides, used mainly by <code>allowCollisions</code> and <code>touching</code>.
49 */
50 static public const WALL:uint = LEFT | RIGHT;
51 /**
52 * Special-case constant meaning any direction, used mainly by <code>allowCollisions</code> and <code>touching</code>.
53 */
54 static public const ANY:uint = LEFT | RIGHT | UP | DOWN;
55
56 /**
57 * Handy constant used during collision resolution (see <code>separateX()</code> and <code>separateY()</code>).
58 */
59 static public const OVERLAP_BIAS:Number = 4;
60
61 /**
62 * Path behavior controls: move from the start of the path to the end then stop.
63 */
64 static public const PATH_FORWARD:uint = 0x000000;
65 /**
66 * Path behavior controls: move from the end of the path to the start then stop.
67 */
68 static public const PATH_BACKWARD:uint = 0x000001;
69 /**
70 * Path behavior controls: move from the start of the path to the end then directly back to the start, and start over.
71 */
72 static public const PATH_LOOP_FORWARD:uint = 0x000010;
73 /**
74 * Path behavior controls: move from the end of the path to the start then directly back to the end, and start over.
75 */
76 static public const PATH_LOOP_BACKWARD:uint = 0x000100;
77 /**
78 * Path behavior controls: move from the start of the path to the end then turn around and go back to the start, over and over.
79 */
80 static public const PATH_YOYO:uint = 0x001000;
81 /**
82 * Path behavior controls: ignores any vertical component to the path data, only follows side to side.
83 */
84 static public const PATH_HORIZONTAL_ONLY:uint = 0x010000;
85 /**
86 * Path behavior controls: ignores any horizontal component to the path data, only follows up and down.
87 */
88 static public const PATH_VERTICAL_ONLY:uint = 0x100000;
89
90 /**
91 * X position of the upper left corner of this object in world space.
92 */
93 public var x:Number;
94 /**
95 * Y position of the upper left corner of this object in world space.
96 */
97 public var y:Number;
98 /**
99 * The width of this object.
100 */
101 public var width:Number;
102 /**
103 * The height of this object.
104 */
105 public var height:Number;
106
107 /**
108 * Whether an object will move/alter position after a collision.
109 */
110 public var immovable:Boolean;
111
112 /**
113 * The basic speed of this object.
114 */
115 public var velocity:FlxPoint;
116 /**
117 * The virtual mass of the object. Default value is 1.
118 * Currently only used with <code>elasticity</code> during collision resolution.
119 * Change at your own risk; effects seem crazy unpredictable so far!
120 */
121 public var mass:Number;
122 /**
123 * The bounciness of this object. Only affects collisions. Default value is 0, or "not bouncy at all."
124 */
125 public var elasticity:Number;
126 /**
127 * How fast the speed of this object is changing.
128 * Useful for smooth movement and gravity.
129 */
130 public var acceleration:FlxPoint;
131 /**
132 * This isn't drag exactly, more like deceleration that is only applied
133 * when acceleration is not affecting the sprite.
134 */
135 public var drag:FlxPoint;
136 /**
137 * If you are using <code>acceleration</code>, you can use <code>maxVelocity</code> with it
138 * to cap the speed automatically (very useful!).
139 */
140 public var maxVelocity:FlxPoint;
141 /**
142 * Set the angle of a sprite to rotate it.
143 * WARNING: rotating sprites decreases rendering
144 * performance for this sprite by a factor of 10x!
145 */
146 public var angle:Number;
147 /**
148 * This is how fast you want this sprite to spin.
149 */
150 public var angularVelocity:Number;
151 /**
152 * How fast the spin speed should change.
153 */
154 public var angularAcceleration:Number;
155 /**
156 * Like <code>drag</code> but for spinning.
157 */
158 public var angularDrag:Number;
159 /**
160 * Use in conjunction with <code>angularAcceleration</code> for fluid spin speed control.
161 */
162 public var maxAngular:Number;
163 /**
164 * Should always represent (0,0) - useful for different things, for avoiding unnecessary <code>new</code> calls.
165 */
166 static protected const _pZero:FlxPoint = new FlxPoint();
167
168 /**
169 * A point that can store numbers from 0 to 1 (for X and Y independently)
170 * that governs how much this object is affected by the camera subsystem.
171 * 0 means it never moves, like a HUD element or far background graphic.
172 * 1 means it scrolls along a the same speed as the foreground layer.
173 * scrollFactor is initialized as (1,1) by default.
174 */
175 public var scrollFactor:FlxPoint;
176 /**
177 * Internal helper used for retro-style flickering.
178 */
179 protected var _flicker:Boolean;
180 /**
181 * Internal helper used for retro-style flickering.
182 */
183 protected var _flickerTimer:Number;
184 /**
185 * Handy for storing health percentage or armor points or whatever.
186 */
187 public var health:Number;
188 /**
189 * This is just a pre-allocated x-y point container to be used however you like
190 */
191 protected var _point:FlxPoint;
192 /**
193 * This is just a pre-allocated rectangle container to be used however you like
194 */
195 protected var _rect:FlxRect;
196 /**
197 * Set this to false if you want to skip the automatic motion/movement stuff (see <code>updateMotion()</code>).
198 * FlxObject and FlxSprite default to true.
199 * FlxText, FlxTileblock, FlxTilemap and FlxSound default to false.
200 */
201 public var moves:Boolean;
202 /**
203 * Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts.
204 * Use bitwise operators to check the values stored here, or use touching(), justStartedTouching(), etc.
205 * You can even use them broadly as boolean values if you're feeling saucy!
206 */
207 public var touching:uint;
208 /**
209 * Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts from the previous game loop step.
210 * Use bitwise operators to check the values stored here, or use touching(), justStartedTouching(), etc.
211 * You can even use them broadly as boolean values if you're feeling saucy!
212 */
213 public var wasTouching:uint;
214 /**
215 * Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating collision directions.
216 * Use bitwise operators to check the values stored here.
217 * Useful for things like one-way platforms (e.g. allowCollisions = UP;)
218 * The accessor "solid" just flips this variable between NONE and ANY.
219 */
220 public var allowCollisions:uint;
221
222 /**
223 * Important variable for collision processing.
224 * By default this value is set automatically during <code>preUpdate()</code>.
225 */
226 public var last:FlxPoint;
227
228 /**
229 * A reference to a path object. Null by default, assigned by <code>followPath()</code>.
230 */
231 public var path:FlxPath;
232 /**
233 * The speed at which the object is moving on the path.
234 * When an object completes a non-looping path circuit,
235 * the pathSpeed will be zeroed out, but the <code>path</code> reference
236 * will NOT be nulled out. So <code>pathSpeed</code> is a good way
237 * to check if this object is currently following a path or not.
238 */
239 public var pathSpeed:Number;
240 /**
241 * The angle in degrees between this object and the next node, where 0 is directly upward, and 90 is to the right.
242 */
243 public var pathAngle:Number;
244 /**
245 * Internal helper, tracks which node of the path this object is moving toward.
246 */
247 protected var _pathNodeIndex:int;
248 /**
249 * Internal tracker for path behavior flags (like looping, horizontal only, etc).
250 */
251 protected var _pathMode:uint;
252 /**
253 * Internal helper for node navigation, specifically yo-yo and backwards movement.
254 */
255 protected var _pathInc:int;
256 /**
257 * Internal flag for whether hte object's angle should be adjusted to the path angle during path follow behavior.
258 */
259 protected var _pathRotate:Boolean;
260
261 /**
262 * Instantiates a <code>FlxObject</code>.
263 *
264 * @param X The X-coordinate of the point in space.
265 * @param Y The Y-coordinate of the point in space.
266 * @param Width Desired width of the rectangle.
267 * @param Height Desired height of the rectangle.
268 */
269 public function FlxObject(X:Number=0,Y:Number=0,Width:Number=0,Height:Number=0)
270 {
271 x = X;
272 y = Y;
273 last = new FlxPoint(x,y);
274 width = Width;
275 height = Height;
276 mass = 1.0;
277 elasticity = 0.0;
278
279 immovable = false;
280 moves = true;
281
282 touching = NONE;
283 wasTouching = NONE;
284 allowCollisions = ANY;
285
286 velocity = new FlxPoint();
287 acceleration = new FlxPoint();
288 drag = new FlxPoint();
289 maxVelocity = new FlxPoint(10000,10000);
290
291 angle = 0;
292 angularVelocity = 0;
293 angularAcceleration = 0;
294 angularDrag = 0;
295 maxAngular = 10000;
296
297 scrollFactor = new FlxPoint(1.0,1.0);
298 _flicker = false;
299 _flickerTimer = 0;
300
301 _point = new FlxPoint();
302 _rect = new FlxRect();
303
304 path = null;
305 pathSpeed = 0;
306 pathAngle = 0;
307 }
308
309 /**
310 * Override this function to null out variables or
311 * manually call destroy() on class members if necessary.
312 * Don't forget to call super.destroy()!
313 */
314 override public function destroy():void
315 {
316 velocity = null;
317 acceleration = null;
318 drag = null;
319 maxVelocity = null;
320 scrollFactor = null;
321 _point = null;
322 _rect = null;
323 last = null;
324 cameras = null;
325 if(path != null)
326 path.destroy();
327 path = null;
328 }
329
330 /**
331 * Pre-update is called right before <code>update()</code> on each object in the game loop.
332 * In <code>FlxObject</code> it controls the flicker timer,
333 * tracking the last coordinates for collision purposes,
334 * and checking if the object is moving along a path or not.
335 */
336 override public function preUpdate():void
337 {
338 _ACTIVECOUNT++;
339
340 if(_flickerTimer != 0)
341 {
342 if(_flickerTimer > 0)
343 {
344 _flickerTimer = _flickerTimer - FlxG.elapsed;
345 if(_flickerTimer <= 0)
346 {
347 _flickerTimer = 0;
348 _flicker = false;
349 }
350 }
351 }
352
353 last.x = x;
354 last.y = y;
355
356 if((path != null) && (pathSpeed != 0) && (path.nodes[_pathNodeIndex] != null))
357 updatePathMotion();
358 }
359
360 /**
361 * Post-update is called right after <code>update()</code> on each object in the game loop.
362 * In <code>FlxObject</code> this function handles integrating the objects motion
363 * based on the velocity and acceleration settings, and tracking/clearing the <code>touching</code> flags.
364 */
365 override public function postUpdate():void
366 {
367 if(moves)
368 updateMotion();
369
370 wasTouching = touching;
371 touching = NONE;
372 }
373
374 /**
375 * Internal function for updating the position and speed of this object.
376 * Useful for cases when you need to update this but are buried down in too many supers.
377 * Does a slightly fancier-than-normal integration to help with higher fidelity framerate-independenct motion.
378 */
379 protected function updateMotion():void
380 {
381 var delta:Number;
382 var velocityDelta:Number;
383
384 velocityDelta = (FlxU.computeVelocity(angularVelocity,angularAcceleration,angularDrag,maxAngular) - angularVelocity)/2;
385 angularVelocity += velocityDelta;
386 angle += angularVelocity*FlxG.elapsed;
387 angularVelocity += velocityDelta;
388
389 velocityDelta = (FlxU.computeVelocity(velocity.x,acceleration.x,drag.x,maxVelocity.x) - velocity.x)/2;
390 velocity.x += velocityDelta;
391 delta = velocity.x*FlxG.elapsed;
392 velocity.x += velocityDelta;
393 x += delta;
394
395 velocityDelta = (FlxU.computeVelocity(velocity.y,acceleration.y,drag.y,maxVelocity.y) - velocity.y)/2;
396 velocity.y += velocityDelta;
397 delta = velocity.y*FlxG.elapsed;
398 velocity.y += velocityDelta;
399 y += delta;
400 }
401
402 /**
403 * Rarely called, and in this case just increments the visible objects count and calls <code>drawDebug()</code> if necessary.
404 */
405 override public function draw():void
406 {
407 if(cameras == null)
408 cameras = FlxG.cameras;
409 var camera:FlxCamera;
410 var i:uint = 0;
411 var l:uint = cameras.length;
412 while(i < l)
413 {
414 camera = cameras[i++];
415 if(!onScreen(camera))
416 continue;
417 _VISIBLECOUNT++;
418 if(FlxG.visualDebug && !ignoreDrawDebug)
419 drawDebug(camera);
420 }
421 }
422
423 /**
424 * Override this function to draw custom "debug mode" graphics to the
425 * specified camera while the debugger's visual mode is toggled on.
426 *
427 * @param Camera Which camera to draw the debug visuals to.
428 */
429 override public function drawDebug(Camera:FlxCamera=null):void
430 {
431 if(Camera == null)
432 Camera = FlxG.camera;
433
434 //get bounding box coordinates
435 var boundingBoxX:Number = x - int(Camera.scroll.x*scrollFactor.x); //copied from getScreenXY()
436 var boundingBoxY:Number = y - int(Camera.scroll.y*scrollFactor.y);
437 boundingBoxX = int(boundingBoxX + ((boundingBoxX > 0)?0.0000001:-0.0000001));
438 boundingBoxY = int(boundingBoxY + ((boundingBoxY > 0)?0.0000001:-0.0000001));
439 var boundingBoxWidth:int = (width != int(width))?width:width-1;
440 var boundingBoxHeight:int = (height != int(height))?height:height-1;
441
442 //fill static graphics object with square shape
443 var gfx:Graphics = FlxG.flashGfx;
444 gfx.clear();
445 gfx.moveTo(boundingBoxX,boundingBoxY);
446 var boundingBoxColor:uint;
447 if(allowCollisions)
448 {
449 if(allowCollisions != ANY)
450 boundingBoxColor = FlxG.PINK;
451 if(immovable)
452 boundingBoxColor = FlxG.GREEN;
453 else
454 boundingBoxColor = FlxG.RED;
455 }
456 else
457 boundingBoxColor = FlxG.BLUE;
458 gfx.lineStyle(1,boundingBoxColor,0.5);
459 gfx.lineTo(boundingBoxX+boundingBoxWidth,boundingBoxY);
460 gfx.lineTo(boundingBoxX+boundingBoxWidth,boundingBoxY+boundingBoxHeight);
461 gfx.lineTo(boundingBoxX,boundingBoxY+boundingBoxHeight);
462 gfx.lineTo(boundingBoxX,boundingBoxY);
463
464 //draw graphics shape to camera buffer
465 Camera.buffer.draw(FlxG.flashGfxSprite);
466 }
467
468 /**
469 * Call this function to give this object a path to follow.
470 * If the path does not have at least one node in it, this function
471 * will log a warning message and return.
472 *
473 * @param Path The <code>FlxPath</code> you want this object to follow.
474 * @param Speed How fast to travel along the path in pixels per second.
475 * @param Mode Optional, controls the behavior of the object following the path using the path behavior constants. Can use multiple flags at once, for example PATH_YOYO|PATH_HORIZONTAL_ONLY will make an object move back and forth along the X axis of the path only.
476 * @param AutoRotate Automatically point the object toward the next node. Assumes the graphic is pointing upward. Default behavior is false, or no automatic rotation.
477 */
478 public function followPath(Path:FlxPath,Speed:Number=100,Mode:uint=PATH_FORWARD,AutoRotate:Boolean=false):void
479 {
480 if(Path.nodes.length <= 0)
481 {
482 FlxG.log("WARNING: Paths need at least one node in them to be followed.");
483 return;
484 }
485
486 path = Path;
487 pathSpeed = FlxU.abs(Speed);
488 _pathMode = Mode;
489 _pathRotate = AutoRotate;
490
491 //get starting node
492 if((_pathMode == PATH_BACKWARD) || (_pathMode == PATH_LOOP_BACKWARD))
493 {
494 _pathNodeIndex = path.nodes.length-1;
495 _pathInc = -1;
496 }
497 else
498 {
499 _pathNodeIndex = 0;
500 _pathInc = 1;
501 }
502 }
503
504 /**
505 * Tells this object to stop following the path its on.
506 *
507 * @param DestroyPath Tells this function whether to call destroy on the path object. Default value is false.
508 */
509 public function stopFollowingPath(DestroyPath:Boolean=false):void
510 {
511 pathSpeed = 0;
512 if(DestroyPath && (path != null))
513 {
514 path.destroy();
515 path = null;
516 }
517 }
518
519 /**
520 * Internal function that decides what node in the path to aim for next based on the behavior flags.
521 *
522 * @return The node (a <code>FlxPoint</code> object) we are aiming for next.
523 */
524 protected function advancePath(Snap:Boolean=true):FlxPoint
525 {
526 if(Snap)
527 {
528 var oldNode:FlxPoint = path.nodes[_pathNodeIndex];
529 if(oldNode != null)
530 {
531 if((_pathMode & PATH_VERTICAL_ONLY) == 0)
532 x = oldNode.x - width*0.5;
533 if((_pathMode & PATH_HORIZONTAL_ONLY) == 0)
534 y = oldNode.y - height*0.5;
535 }
536 }
537
538 _pathNodeIndex += _pathInc;
539
540 if((_pathMode & PATH_BACKWARD) > 0)
541 {
542 if(_pathNodeIndex < 0)
543 {
544 _pathNodeIndex = 0;
545 pathSpeed = 0;
546 }
547 }
548 else if((_pathMode & PATH_LOOP_FORWARD) > 0)
549 {
550 if(_pathNodeIndex >= path.nodes.length)
551 _pathNodeIndex = 0;
552 }
553 else if((_pathMode & PATH_LOOP_BACKWARD) > 0)
554 {
555 if(_pathNodeIndex < 0)
556 {
557 _pathNodeIndex = path.nodes.length-1;
558 if(_pathNodeIndex < 0)
559 _pathNodeIndex = 0;
560 }
561 }
562 else if((_pathMode & PATH_YOYO) > 0)
563 {
564 if(_pathInc > 0)
565 {
566 if(_pathNodeIndex >= path.nodes.length)
567 {
568 _pathNodeIndex = path.nodes.length-2;
569 if(_pathNodeIndex < 0)
570 _pathNodeIndex = 0;
571 _pathInc = -_pathInc;
572 }
573 }
574 else if(_pathNodeIndex < 0)
575 {
576 _pathNodeIndex = 1;
577 if(_pathNodeIndex >= path.nodes.length)
578 _pathNodeIndex = path.nodes.length-1;
579 if(_pathNodeIndex < 0)
580 _pathNodeIndex = 0;
581 _pathInc = -_pathInc;
582 }
583 }
584 else
585 {
586 if(_pathNodeIndex >= path.nodes.length)
587 {
588 _pathNodeIndex = path.nodes.length-1;
589 pathSpeed = 0;
590 }
591 }
592
593 return path.nodes[_pathNodeIndex];
594 }
595
596 /**
597 * Internal function for moving the object along the path.
598 * Generally this function is called automatically by <code>preUpdate()</code>.
599 * The first half of the function decides if the object can advance to the next node in the path,
600 * while the second half handles actually picking a velocity toward the next node.
601 */
602 protected function updatePathMotion():void
603 {
604 //first check if we need to be pointing at the next node yet
605 _point.x = x + width*0.5;
606 _point.y = y + height*0.5;
607 var node:FlxPoint = path.nodes[_pathNodeIndex];
608 var deltaX:Number = node.x - _point.x;
609 var deltaY:Number = node.y - _point.y;
610
611 var horizontalOnly:Boolean = (_pathMode & PATH_HORIZONTAL_ONLY) > 0;
612 var verticalOnly:Boolean = (_pathMode & PATH_VERTICAL_ONLY) > 0;
613
614 if(horizontalOnly)
615 {
616 if(((deltaX>0)?deltaX:-deltaX) < pathSpeed*FlxG.elapsed)
617 node = advancePath();
618 }
619 else if(verticalOnly)
620 {
621 if(((deltaY>0)?deltaY:-deltaY) < pathSpeed*FlxG.elapsed)
622 node = advancePath();
623 }
624 else
625 {
626 if(Math.sqrt(deltaX*deltaX + deltaY*deltaY) < pathSpeed*FlxG.elapsed)
627 node = advancePath();
628 }
629
630 //then just move toward the current node at the requested speed
631 if(pathSpeed != 0)
632 {
633 //set velocity based on path mode
634 _point.x = x + width*0.5;
635 _point.y = y + height*0.5;
636 if(horizontalOnly || (_point.y == node.y))
637 {
638 velocity.x = (_point.x < node.x)?pathSpeed:-pathSpeed;
639 if(velocity.x < 0)
640 pathAngle = -90;
641 else
642 pathAngle = 90;
643 if(!horizontalOnly)
644 velocity.y = 0;
645 }
646 else if(verticalOnly || (_point.x == node.x))
647 {
648 velocity.y = (_point.y < node.y)?pathSpeed:-pathSpeed;
649 if(velocity.y < 0)
650 pathAngle = 0;
651 else
652 pathAngle = 180;
653 if(!verticalOnly)
654 velocity.x = 0;
655 }
656 else
657 {
658 pathAngle = FlxU.getAngle(_point,node);
659 FlxU.rotatePoint(0,pathSpeed,0,0,pathAngle,velocity);
660 }
661
662 //then set object rotation if necessary
663 if(_pathRotate)
664 {
665 angularVelocity = 0;
666 angularAcceleration = 0;
667 angle = pathAngle;
668 }
669 }
670 }
671
672 /**
673 * Checks to see if some <code>FlxObject</code> overlaps this <code>FlxObject</code> or <code>FlxGroup</code>.
674 * If the group has a LOT of things in it, it might be faster to use <code>FlxG.overlaps()</code>.
675 * WARNING: Currently tilemaps do NOT support screen space overlap checks!
676 *
677 * @param ObjectOrGroup The object or group being tested.
678 * @param InScreenSpace Whether to take scroll factors into account when checking for overlap. Default is false, or "only compare in world space."
679 * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
680 *
681 * @return Whether or not the two objects overlap.
682 */
683 public function overlaps(ObjectOrGroup:FlxBasic,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
684 {
685 if(ObjectOrGroup is FlxGroup)
686 {
687 var results:Boolean = false;
688 var i:uint = 0;
689 var members:Array = (ObjectOrGroup as FlxGroup).members;
690 while(i < length)
691 {
692 if(overlaps(members[i++],InScreenSpace,Camera))
693 results = true;
694 }
695 return results;
696 }
697
698 if(ObjectOrGroup is FlxTilemap)
699 {
700 //Since tilemap's have to be the caller, not the target, to do proper tile-based collisions,
701 // we redirect the call to the tilemap overlap here.
702 return (ObjectOrGroup as FlxTilemap).overlaps(this,InScreenSpace,Camera);
703 }
704
705 var object:FlxObject = ObjectOrGroup as FlxObject;
706 if(!InScreenSpace)
707 {
708 return (object.x + object.width > x) && (object.x < x + width) &&
709 (object.y + object.height > y) && (object.y < y + height);
710 }
711
712 if(Camera == null)
713 Camera = FlxG.camera;
714 var objectScreenPos:FlxPoint = object.getScreenXY(null,Camera);
715 getScreenXY(_point,Camera);
716 return (objectScreenPos.x + object.width > _point.x) && (objectScreenPos.x < _point.x + width) &&
717 (objectScreenPos.y + object.height > _point.y) && (objectScreenPos.y < _point.y + height);
718 }
719
720 /**
721 * Checks to see if this <code>FlxObject</code> were located at the given position, would it overlap the <code>FlxObject</code> or <code>FlxGroup</code>?
722 * This is distinct from overlapsPoint(), which just checks that point, rather than taking the object's size into account.
723 * WARNING: Currently tilemaps do NOT support screen space overlap checks!
724 *
725 * @param X The X position you want to check. Pretends this object (the caller, not the parameter) is located here.
726 * @param Y The Y position you want to check. Pretends this object (the caller, not the parameter) is located here.
727 * @param ObjectOrGroup The object or group being tested.
728 * @param InScreenSpace Whether to take scroll factors into account when checking for overlap. Default is false, or "only compare in world space."
729 * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
730 *
731 * @return Whether or not the two objects overlap.
732 */
733 public function overlapsAt(X:Number,Y:Number,ObjectOrGroup:FlxBasic,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
734 {
735 if(ObjectOrGroup is FlxGroup)
736 {
737 var results:Boolean = false;
738 var basic:FlxBasic;
739 var i:uint = 0;
740 var members:Array = (ObjectOrGroup as FlxGroup).members;
741 while(i < length)
742 {
743 if(overlapsAt(X,Y,members[i++],InScreenSpace,Camera))
744 results = true;
745 }
746 return results;
747 }
748
749 if(ObjectOrGroup is FlxTilemap)
750 {
751 //Since tilemap's have to be the caller, not the target, to do proper tile-based collisions,
752 // we redirect the call to the tilemap overlap here.
753 //However, since this is overlapsAt(), we also have to invent the appropriate position for the tilemap.
754 //So we calculate the offset between the player and the requested position, and subtract that from the tilemap.
755 var tilemap:FlxTilemap = ObjectOrGroup as FlxTilemap;
756 return tilemap.overlapsAt(tilemap.x - (X - x),tilemap.y - (Y - y),this,InScreenSpace,Camera);
757 }
758
759 var object:FlxObject = ObjectOrGroup as FlxObject;
760 if(!InScreenSpace)
761 {
762 return (object.x + object.width > X) && (object.x < X + width) &&
763 (object.y + object.height > Y) && (object.y < Y + height);
764 }
765
766 if(Camera == null)
767 Camera = FlxG.camera;
768 var objectScreenPos:FlxPoint = object.getScreenXY(null,Camera);
769 _point.x = X - int(Camera.scroll.x*scrollFactor.x); //copied from getScreenXY()
770 _point.y = Y - int(Camera.scroll.y*scrollFactor.y);
771 _point.x += (_point.x > 0)?0.0000001:-0.0000001;
772 _point.y += (_point.y > 0)?0.0000001:-0.0000001;
773 return (objectScreenPos.x + object.width > _point.x) && (objectScreenPos.x < _point.x + width) &&
774 (objectScreenPos.y + object.height > _point.y) && (objectScreenPos.y < _point.y + height);
775 }
776
777 /**
778 * Checks to see if a point in 2D world space overlaps this <code>FlxObject</code> object.
779 *
780 * @param Point The point in world space you want to check.
781 * @param InScreenSpace Whether to take scroll factors into account when checking for overlap.
782 * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
783 *
784 * @return Whether or not the point overlaps this object.
785 */
786 public function overlapsPoint(Point:FlxPoint,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
787 {
788 if(!InScreenSpace)
789 return (Point.x > x) && (Point.x < x + width) && (Point.y > y) && (Point.y < y + height);
790
791 if(Camera == null)
792 Camera = FlxG.camera;
793 var X:Number = Point.x - Camera.scroll.x;
794 var Y:Number = Point.y - Camera.scroll.y;
795 getScreenXY(_point,Camera);
796 return (X > _point.x) && (X < _point.x+width) && (Y > _point.y) && (Y < _point.y+height);
797 }
798
799 /**
800 * Check and see if this object is currently on screen.
801 *
802 * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
803 *
804 * @return Whether the object is on screen or not.
805 */
806 public function onScreen(Camera:FlxCamera=null):Boolean
807 {
808 if(Camera == null)
809 Camera = FlxG.camera;
810 getScreenXY(_point,Camera);
811 return (_point.x + width > 0) && (_point.x < Camera.width) && (_point.y + height > 0) && (_point.y < Camera.height);
812 }
813
814 /**
815 * Call this function to figure out the on-screen position of the object.
816 *
817 * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
818 * @param Point Takes a <code>FlxPoint</code> object and assigns the post-scrolled X and Y values of this object to it.
819 *
820 * @return The <code>Point</code> you passed in, or a new <code>Point</code> if you didn't pass one, containing the screen X and Y position of this object.
821 */
822 public function getScreenXY(Point:FlxPoint=null,Camera:FlxCamera=null):FlxPoint
823 {
824 if(Point == null)
825 Point = new FlxPoint();
826 if(Camera == null)
827 Camera = FlxG.camera;
828 Point.x = x - int(Camera.scroll.x*scrollFactor.x);
829 Point.y = y - int(Camera.scroll.y*scrollFactor.y);
830 Point.x += (Point.x > 0)?0.0000001:-0.0000001;
831 Point.y += (Point.y > 0)?0.0000001:-0.0000001;
832 return Point;
833 }
834
835 /**
836 * Tells this object to flicker, retro-style.
837 * Pass a negative value to flicker forever.
838 *
839 * @param Duration How many seconds to flicker for.
840 */
841 public function flicker(Duration:Number=1):void
842 {
843 _flickerTimer = Duration;
844 if(_flickerTimer == 0)
845 _flicker = false;
846 }
847
848 /**
849 * Check to see if the object is still flickering.
850 *
851 * @return Whether the object is flickering or not.
852 */
853 public function get flickering():Boolean
854 {
855 return _flickerTimer != 0;
856 }
857
858 /**
859 * Whether the object collides or not. For more control over what directions
860 * the object will collide from, use collision constants (like LEFT, FLOOR, etc)
861 * to set the value of allowCollisions directly.
862 */
863 public function get solid():Boolean
864 {
865 return (allowCollisions & ANY) > NONE;
866 }
867
868 /**
869 * @private
870 */
871 public function set solid(Solid:Boolean):void
872 {
873 if(Solid)
874 allowCollisions = ANY;
875 else
876 allowCollisions = NONE;
877 }
878
879 /**
880 * Retrieve the midpoint of this object in world coordinates.
881 *
882 * @Point Allows you to pass in an existing <code>FlxPoint</code> object if you're so inclined. Otherwise a new one is created.
883 *
884 * @return A <code>FlxPoint</code> object containing the midpoint of this object in world coordinates.
885 */
886 public function getMidpoint(Point:FlxPoint=null):FlxPoint
887 {
888 if(Point == null)
889 Point = new FlxPoint();
890 Point.x = x + width*0.5;
891 Point.y = y + height*0.5;
892 return Point;
893 }
894
895 /**
896 * Handy function for reviving game objects.
897 * Resets their existence flags and position.
898 *
899 * @param X The new X position of this object.
900 * @param Y The new Y position of this object.
901 */
902 public function reset(X:Number,Y:Number):void
903 {
904 revive();
905 touching = NONE;
906 wasTouching = NONE;
907 x = X;
908 y = Y;
909 last.x = x;
910 last.y = y;
911 velocity.x = 0;
912 velocity.y = 0;
913 }
914
915 /**
916 * Handy function for checking if this object is touching a particular surface.
917 * For slightly better performance you can just & the value directly into <code>touching</code>.
918 * However, this method is good for readability and accessibility.
919 *
920 * @param Direction Any of the collision flags (e.g. LEFT, FLOOR, etc).
921 *
922 * @return Whether the object is touching an object in (any of) the specified direction(s) this frame.
923 */
924 public function isTouching(Direction:uint):Boolean
925 {
926 return (touching & Direction) > NONE;
927 }
928
929 /**
930 * Handy function for checking if this object is just landed on a particular surface.
931 *
932 * @param Direction Any of the collision flags (e.g. LEFT, FLOOR, etc).
933 *
934 * @return Whether the object just landed on (any of) the specified surface(s) this frame.
935 */
936 public function justTouched(Direction:uint):Boolean
937 {
938 return ((touching & Direction) > NONE) && ((wasTouching & Direction) <= NONE);
939 }
940
941 /**
942 * Reduces the "health" variable of this sprite by the amount specified in Damage.
943 * Calls kill() if health drops to or below zero.
944 *
945 * @param Damage How much health to take away (use a negative number to give a health bonus).
946 */
947 public function hurt(Damage:Number):void
948 {
949 health = health - Damage;
950 if(health <= 0)
951 kill();
952 }
953
954 /**
955 * The main collision resolution function in flixel.
956 *
957 * @param Object1 Any <code>FlxObject</code>.
958 * @param Object2 Any other <code>FlxObject</code>.
959 *
960 * @return Whether the objects in fact touched and were separated.
961 */
962 static public function separate(Object1:FlxObject, Object2:FlxObject):Boolean
963 {
964 var separatedX:Boolean = separateX(Object1,Object2);
965 var separatedY:Boolean = separateY(Object1,Object2);
966 return separatedX || separatedY;
967 }
968
969 /**
970 * The X-axis component of the object separation process.
971 *
972 * @param Object1 Any <code>FlxObject</code>.
973 * @param Object2 Any other <code>FlxObject</code>.
974 *
975 * @return Whether the objects in fact touched and were separated along the X axis.
976 */
977 static public function separateX(Object1:FlxObject, Object2:FlxObject):Boolean
978 {
979 //can't separate two immovable objects
980 var obj1immovable:Boolean = Object1.immovable;
981 var obj2immovable:Boolean = Object2.immovable;
982 if(obj1immovable && obj2immovable)
983 return false;
984
985 //If one of the objects is a tilemap, just pass it off.
986 if(Object1 is FlxTilemap)
987 return (Object1 as FlxTilemap).overlapsWithCallback(Object2,separateX);
988 if(Object2 is FlxTilemap)
989 return (Object2 as FlxTilemap).overlapsWithCallback(Object1,separateX,true);
990
991 //First, get the two object deltas
992 var overlap:Number = 0;
993 var obj1delta:Number = Object1.x - Object1.last.x;
994 var obj2delta:Number = Object2.x - Object2.last.x;
995 if(obj1delta != obj2delta)
996 {
997 //Check if the X hulls actually overlap
998 var obj1deltaAbs:Number = (obj1delta > 0)?obj1delta:-obj1delta;
999 var obj2deltaAbs:Number = (obj2delta > 0)?obj2delta:-obj2delta;
1000 var obj1rect:FlxRect = new FlxRect(Object1.x-((obj1delta > 0)?obj1delta:0),Object1.last.y,Object1.width+((obj1delta > 0)?obj1delta:-obj1delta),Object1.height);
1001 var obj2rect:FlxRect = new FlxRect(Object2.x-((obj2delta > 0)?obj2delta:0),Object2.last.y,Object2.width+((obj2delta > 0)?obj2delta:-obj2delta),Object2.height);
1002 if((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height))
1003 {
1004 var maxOverlap:Number = obj1deltaAbs + obj2deltaAbs + OVERLAP_BIAS;
1005
1006 //If they did overlap (and can), figure out by how much and flip the corresponding flags
1007 if(obj1delta > obj2delta)
1008 {
1009 overlap = Object1.x + Object1.width - Object2.x;
1010 if((overlap > maxOverlap) || !(Object1.allowCollisions & RIGHT) || !(Object2.allowCollisions & LEFT))
1011 overlap = 0;
1012 else
1013 {
1014 Object1.touching |= RIGHT;
1015 Object2.touching |= LEFT;
1016 }
1017 }
1018 else if(obj1delta < obj2delta)
1019 {
1020 overlap = Object1.x - Object2.width - Object2.x;
1021 if((-overlap > maxOverlap) || !(Object1.allowCollisions & LEFT) || !(Object2.allowCollisions & RIGHT))
1022 overlap = 0;
1023 else
1024 {
1025 Object1.touching |= LEFT;
1026 Object2.touching |= RIGHT;
1027 }
1028 }
1029 }
1030 }
1031
1032 //Then adjust their positions and velocities accordingly (if there was any overlap)
1033 if(overlap != 0)
1034 {
1035 var obj1v:Number = Object1.velocity.x;
1036 var obj2v:Number = Object2.velocity.x;
1037
1038 if(!obj1immovable && !obj2immovable)
1039 {
1040 overlap *= 0.5;
1041 Object1.x = Object1.x - overlap;
1042 Object2.x += overlap;
1043
1044 var obj1velocity:Number = Math.sqrt((obj2v * obj2v * Object2.mass)/Object1.mass) * ((obj2v > 0)?1:-1);
1045 var obj2velocity:Number = Math.sqrt((obj1v * obj1v * Object1.mass)/Object2.mass) * ((obj1v > 0)?1:-1);
1046 var average:Number = (obj1velocity + obj2velocity)*0.5;
1047 obj1velocity -= average;
1048 obj2velocity -= average;
1049 Object1.velocity.x = average + obj1velocity * Object1.elasticity;
1050 Object2.velocity.x = average + obj2velocity * Object2.elasticity;
1051 }
1052 else if(!obj1immovable)
1053 {
1054 Object1.x = Object1.x - overlap;
1055 Object1.velocity.x = obj2v - obj1v*Object1.elasticity;
1056 }
1057 else if(!obj2immovable)
1058 {
1059 Object2.x += overlap;
1060 Object2.velocity.x = obj1v - obj2v*Object2.elasticity;
1061 }
1062 return true;
1063 }
1064 else
1065 return false;
1066 }
1067
1068 /**
1069 * The Y-axis component of the object separation process.
1070 *
1071 * @param Object1 Any <code>FlxObject</code>.
1072 * @param Object2 Any other <code>FlxObject</code>.
1073 *
1074 * @return Whether the objects in fact touched and were separated along the Y axis.
1075 */
1076 static public function separateY(Object1:FlxObject, Object2:FlxObject):Boolean
1077 {
1078 //can't separate two immovable objects
1079 var obj1immovable:Boolean = Object1.immovable;
1080 var obj2immovable:Boolean = Object2.immovable;
1081 if(obj1immovable && obj2immovable)
1082 return false;
1083
1084 //If one of the objects is a tilemap, just pass it off.
1085 if(Object1 is FlxTilemap)
1086 return (Object1 as FlxTilemap).overlapsWithCallback(Object2,separateY);
1087 if(Object2 is FlxTilemap)
1088 return (Object2 as FlxTilemap).overlapsWithCallback(Object1,separateY,true);
1089
1090 //First, get the two object deltas
1091 var overlap:Number = 0;
1092 var obj1delta:Number = Object1.y - Object1.last.y;
1093 var obj2delta:Number = Object2.y - Object2.last.y;
1094 if(obj1delta != obj2delta)
1095 {
1096 //Check if the Y hulls actually overlap
1097 var obj1deltaAbs:Number = (obj1delta > 0)?obj1delta:-obj1delta;
1098 var obj2deltaAbs:Number = (obj2delta > 0)?obj2delta:-obj2delta;
1099 var obj1rect:FlxRect = new FlxRect(Object1.x,Object1.y-((obj1delta > 0)?obj1delta:0),Object1.width,Object1.height+obj1deltaAbs);
1100 var obj2rect:FlxRect = new FlxRect(Object2.x,Object2.y-((obj2delta > 0)?obj2delta:0),Object2.width,Object2.height+obj2deltaAbs);
1101 if((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height))
1102 {
1103 var maxOverlap:Number = obj1deltaAbs + obj2deltaAbs + OVERLAP_BIAS;
1104
1105 //If they did overlap (and can), figure out by how much and flip the corresponding flags
1106 if(obj1delta > obj2delta)
1107 {
1108 overlap = Object1.y + Object1.height - Object2.y;
1109 if((overlap > maxOverlap) || !(Object1.allowCollisions & DOWN) || !(Object2.allowCollisions & UP))
1110 overlap = 0;
1111 else
1112 {
1113 Object1.touching |= DOWN;
1114 Object2.touching |= UP;
1115 }
1116 }
1117 else if(obj1delta < obj2delta)
1118 {
1119 overlap = Object1.y - Object2.height - Object2.y;
1120 if((-overlap > maxOverlap) || !(Object1.allowCollisions & UP) || !(Object2.allowCollisions & DOWN))
1121 overlap = 0;
1122 else
1123 {
1124 Object1.touching |= UP;
1125 Object2.touching |= DOWN;
1126 }
1127 }
1128 }
1129 }
1130
1131 //Then adjust their positions and velocities accordingly (if there was any overlap)
1132 if(overlap != 0)
1133 {
1134 var obj1v:Number = Object1.velocity.y;
1135 var obj2v:Number = Object2.velocity.y;
1136
1137 if(!obj1immovable && !obj2immovable)
1138 {
1139 overlap *= 0.5;
1140 Object1.y = Object1.y - overlap;
1141 Object2.y += overlap;
1142
1143 var obj1velocity:Number = Math.sqrt((obj2v * obj2v * Object2.mass)/Object1.mass) * ((obj2v > 0)?1:-1);
1144 var obj2velocity:Number = Math.sqrt((obj1v * obj1v * Object1.mass)/Object2.mass) * ((obj1v > 0)?1:-1);
1145 var average:Number = (obj1velocity + obj2velocity)*0.5;
1146 obj1velocity -= average;
1147 obj2velocity -= average;
1148 Object1.velocity.y = average + obj1velocity * Object1.elasticity;
1149 Object2.velocity.y = average + obj2velocity * Object2.elasticity;
1150 }
1151 else if(!obj1immovable)
1152 {
1153 Object1.y = Object1.y - overlap;
1154 Object1.velocity.y = obj2v - obj1v*Object1.elasticity;
1155 //This is special case code that handles cases like horizontal moving platforms you can ride
1156 if(Object2.active && Object2.moves && (obj1delta > obj2delta))
1157 Object1.x += Object2.x - Object2.last.x;
1158 }
1159 else if(!obj2immovable)
1160 {
1161 Object2.y += overlap;
1162 Object2.velocity.y = obj1v - obj2v*Object2.elasticity;
1163 //This is special case code that handles cases like horizontal moving platforms you can ride
1164 if(Object1.active && Object1.moves && (obj1delta < obj2delta))
1165 Object2.x += Object1.x - Object1.last.x;
1166 }
1167 return true;
1168 }
1169 else
1170 return false;
1171 }
1172 }
1173}