Advertisement
Guest User

Untitled

a guest
Sep 21st, 2019
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.61 KB | None | 0 0
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. /**
  6. * Actor is a generic class for game objects that need to move around and collide with each other.
  7. * The main reason to use this class is to use its MoveAndCollide method.
  8. * This method lets you move an Actor around and have it correctly respond to collisions with other game objects.
  9. * It also keeps the Actor's position aligned with the pixel grid, while still allowing movement to fractional positions (like "4.7 pixels").
  10. * The fractional position is tracked internally, while the pixel grid position is used for collision and rendering.
  11. * Keeping Actors locked to the pixel grid makes collision logic simpler and avoids graphical glitches and anomalies.
  12. */
  13. public class Actor : MonoBehaviour {
  14. /**
  15. * The MoveAndCollide method detects collisions by dividing movement into small steps.
  16. * This value is maximum number of pixels in a step.
  17. * The larger this value is, the fewer collision checks MoveAndCollide will perform in general (making it more efficient).
  18. * However, if the value is too large, then when Actors are moving very fast, they may pass through objects without detecting a collision.
  19. * This value should be set as large as possible but not so large that objects can be skipped over in a single step.
  20. * If your game is tile-based, a good rule of thumb is to set this value to the tile size (if you don't have objects smaller than a tile).
  21. * Additionally, if your Actors have a maximum speed, there is no need to set this value higher than the maximum speed.
  22. */
  23. public const int MAX_STEP_SIZE = 16;
  24.  
  25. /**
  26. * The position of the actor on the pixel grid.
  27. */
  28. public Vector3Int pixelPosition;
  29. /**
  30. * The offset of the actor from the pixel grid. This offset is used to allow Actors to move at fractional speed values.
  31. * The subpixel offset is in the following range: -0.5 < subpixel offset <= 0.5.
  32. */
  33. public Vector3 subpixelOffset;
  34. // Added together, the pixel position and the subpixel offset give the "true position" of the Actor.
  35. // The pixel position is always the rounded version of the true position, where numbers with a fractional part of 0.5 are rounded down.
  36. // The subpixel offset is always the true position minus the pixel position.
  37. // For example, if true horizontal position of the Actor is 4.7 pixels, the horizontal pixel position is 5 and the horizontal subpixel offset is -0.3.
  38.  
  39. /**
  40. * These variables store the boundaries of the Actor's hitbox.
  41. * They are absolute positions rather than relative.
  42. * For example, if the actor is at horizontal pixel position 32, and the left side of the hitbox is at horizontal pixel position 24,
  43. * then the variable hitboxLeft would have value 24 rather than value -8.
  44. */
  45. public int hitboxLeft, hitboxRight, hitboxTop, hitboxBottom;
  46.  
  47. // The actual collider representing the Actor's hitbox.
  48. private BoxCollider2D bc;
  49.  
  50. /**
  51. * Initialize the actor.
  52. */
  53. protected virtual void Start() {
  54. // Initialize the pixel position, hitbox boundaries, and subpixel offset of the actor.
  55. bc = GetComponent<BoxCollider2D>();
  56. pixelPosition = new Vector3Int(RoundTiesDown(transform.position.x), RoundTiesDown(transform.position.y), 0);
  57. subpixelOffset = transform.position - pixelPosition;
  58. ComputeHitbox();
  59. }
  60.  
  61. /**
  62. * Move the Actor by the given amount along the given axis, ignoring collisions.
  63. */
  64. public void Move(float amount, int axis) {
  65. // Determine the target position.
  66. float targetPosition = pixelPosition[axis] + subpixelOffset[axis] + amount;
  67. // Round it to get the target pixel position, and update the pixel position.
  68. pixelPosition[axis] = RoundTiesDown(targetPosition);
  69. // Update the subpixel offset: it's the difference between the target position and our new pixel position.
  70. subpixelOffset[axis] = targetPosition - pixelPosition[axis];
  71.  
  72. // We've moved, so recompute our hitbox boundaries and sync pixel position with transform position.
  73. ComputeHitbox();
  74. UpdateTransform();
  75. }
  76.  
  77. /**
  78. * Move the Actor by the given amount along the given axis, handling collisions appropriately.
  79. * This function depends on two other functions: CheckCollisions and HandleCollisions.
  80. * The Actor is moved in small steps, using CheckCollisions to see if a step will cause a collision will occur.
  81. * When collisions are found, they are processed using HandleCollisions.
  82. * HandleCollisions returns a boolean value that determines if the Actor should stop (e.g., if a wall was hit)
  83. * or keep moving (e.g., if a collectable object was hit).
  84. * You can control how each Actor deals with collisions and what kinds of objects they collide with by overriding
  85. * CheckCollisions and HandleCollisions.
  86. * The total amount that the Actor moved in pixels is returned.
  87. */
  88. public virtual int MoveAndCollide(float amount, int axis) {
  89. // Determine the target position.
  90. float targetPosition = pixelPosition[axis] + subpixelOffset[axis] + amount;
  91. // Round it to get the target pixel position.
  92. int targetPixelPosition = RoundTiesDown(targetPosition);
  93. // The pixel amount we want to move is the difference between the target position and current position.
  94. int pixelAmount = targetPixelPosition - pixelPosition[axis];
  95. // Update the subpixel offset: it's the difference between the target position and the target pixel position.
  96. subpixelOffset[axis] = targetPosition - targetPixelPosition;
  97.  
  98. // Set up variables related to stepping.
  99. int stepSize = MAX_STEP_SIZE;
  100. int stepAmount = System.Math.Sign(amount) * stepSize;
  101. Vector3Int stepVector = Vector3Int.zero;
  102. // Track the total amount we actually moved.
  103. int movedAmount = 0;
  104.  
  105. while (pixelAmount != 0) {
  106. // If the pixel amount left to move is actually smaller than our step size...
  107. if (System.Math.Abs(pixelAmount) < stepSize) {
  108. stepAmount = pixelAmount; // Just change our step amount to the entire pixel amount.
  109. }
  110. stepVector[axis] = stepAmount;
  111.  
  112. // Check for collisions one step ahead.
  113. List<Collider2D> colliders = CheckCollisions(stepVector);
  114. if (colliders.Count != 0) { // If we hit something...
  115. bool stop = HandleCollisions(colliders); // Handle the collisions and figure out whether we should stop
  116. if (stop) { // If we hit something that should make the actor stop...
  117. if (stepSize > 1) { // Try to decrease the step size, unless we're already taking one-pixel steps.
  118. stepSize /= 2;
  119. stepAmount = System.Math.Sign(amount) * stepSize;
  120. // Go back to the start of the loop with smaller steps to try to get closer to the collider.
  121. continue;
  122. }
  123. // If we're down to one pixel steps, that means we can't move forward at all without hitting something.
  124. // So we break out of the loop and return.
  125. break;
  126. }
  127. }
  128. // Now, we actually take the step (update the pixel position).
  129. pixelPosition += stepVector;
  130. // Decrease the pixel amount left to move, and increase the total amount moved.
  131. pixelAmount -= stepAmount;
  132. movedAmount += stepAmount;
  133.  
  134. // Because the character moved, we need to recompute the hitbox.
  135. ComputeHitbox();
  136. }
  137. // The character is done moving, so sync the transform position with the pixel position.
  138. // Why don't we call this in the loop? Well, it's because we check for colliders by comparing
  139. // the hitbox variables of this Actor with other colliders, rather than using the Actor's transform.
  140. // No Actors other than this one are moving around in the loop, so we don't need to resync transforms,
  141. // we can just keep hitbox variables updated. If you modify this method to do more complicated things,
  142. // you might need to sync transforms within the loop.
  143. UpdateTransform();
  144. return movedAmount;
  145. }
  146.  
  147. /**
  148. * Returns a list of colliders that would touch this Actor if it was moved according to the offset vector.
  149. * For example, if the offset was (0,1,0), this would return a list of colliders one pixel below the Actor.
  150. * The default implementation reports all collisions except for self-collisions.
  151. * Override this to change how the actor checks for collisions when MoveAndCollide is called.
  152. * For example, to implement jump-through platforms, you could modify this method so it does not report a collision
  153. * unless the Actor is above the jump-through platform.
  154. */
  155. protected virtual List<Collider2D> CheckCollisions(Vector3Int offset) {
  156. Collider2D[] colliders = Physics2D.OverlapAreaAll(new Vector2Int(hitboxLeft + 1, hitboxTop - 1) + ((Vector2Int)offset),
  157. new Vector2Int(hitboxRight - 1, hitboxBottom + 1) + ((Vector2Int)offset));
  158. List<Collider2D> filteredColliders = new List<Collider2D>(colliders);
  159. filteredColliders.Remove(bc);
  160. return filteredColliders;
  161. }
  162.  
  163. /**
  164. * Given a list of colliders, this method allows you to implement collision response for each collider.
  165. * For example, if you collided with a coin, you could destroy the coin and increment a coin counter in this method.
  166. * The return value determines if the Actor should stop moving when it hits this object during a MoveAndCollide call.
  167. * If true, the Actor will stop. For example, you could use this to make the Actor stop when a wall is hit.
  168. * The default implementation makes the Actor stop if it hits anything.
  169. */
  170. protected virtual bool HandleCollisions(List<Collider2D> colliders) {
  171. return colliders.Count != 0;
  172. }
  173.  
  174. /**
  175. * This is a special round function that always rounds down when the fractional part of the number is 0.5.
  176. * Usually it doesn't matter if you use this or the "balanced" rounding used by Unity's Mathf.Round.
  177. * However, this function is used in the default implementations of Move and MoveAndCollide, as it provides more consistent
  178. * behavior which is desirable for some advanced uses (like implementing Actors which push or carry other Actors).
  179. */
  180. protected int RoundTiesDown(float number) {
  181. return Mathf.CeilToInt(number - 0.5f);
  182. }
  183.  
  184. /**
  185. * This method computes the boundaries of the Actor's hitbox.
  186. * When the Actor's pixel position changes, this function should be called to keep the hitbox position in sync.
  187. */
  188. protected void ComputeHitbox() {
  189. hitboxLeft = pixelPosition.x - ((int)bc.bounds.extents.x);
  190. hitboxRight = pixelPosition.x + ((int)bc.bounds.extents.x);
  191. hitboxBottom = pixelPosition.y - ((int)bc.bounds.extents.y);
  192. hitboxTop = pixelPosition.y + ((int)bc.bounds.extents.y);
  193. }
  194.  
  195. /**
  196. * This method updates the transform of the Actor to match the Actor's pixel position.
  197. * It also updates Unity's internal tracking of transform positions, so that the new transform position will be reflected
  198. * in collision checks.
  199. */
  200. protected void UpdateTransform() {
  201. transform.position = pixelPosition;
  202. Physics2D.SyncTransforms();
  203. }
  204. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement