PageRenderTime 2673ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/org/haxel/HxlObject.hx

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