Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- /**
- * Actor is a generic class for game objects that need to move around and collide with each other.
- * The main reason to use this class is to use its MoveAndCollide method.
- * This method lets you move an Actor around and have it correctly respond to collisions with other game objects.
- * It also keeps the Actor's position aligned with the pixel grid, while still allowing movement to fractional positions (like "4.7 pixels").
- * The fractional position is tracked internally, while the pixel grid position is used for collision and rendering.
- * Keeping Actors locked to the pixel grid makes collision logic simpler and avoids graphical glitches and anomalies.
- */
- public class Actor : MonoBehaviour {
- /**
- * The MoveAndCollide method detects collisions by dividing movement into small steps.
- * This value is maximum number of pixels in a step.
- * The larger this value is, the fewer collision checks MoveAndCollide will perform in general (making it more efficient).
- * However, if the value is too large, then when Actors are moving very fast, they may pass through objects without detecting a collision.
- * This value should be set as large as possible but not so large that objects can be skipped over in a single step.
- * 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).
- * Additionally, if your Actors have a maximum speed, there is no need to set this value higher than the maximum speed.
- */
- public const int MAX_STEP_SIZE = 16;
- /**
- * The position of the actor on the pixel grid.
- */
- public Vector3Int pixelPosition;
- /**
- * The offset of the actor from the pixel grid. This offset is used to allow Actors to move at fractional speed values.
- * The subpixel offset is in the following range: -0.5 < subpixel offset <= 0.5.
- */
- public Vector3 subpixelOffset;
- // Added together, the pixel position and the subpixel offset give the "true position" of the Actor.
- // The pixel position is always the rounded version of the true position, where numbers with a fractional part of 0.5 are rounded down.
- // The subpixel offset is always the true position minus the pixel position.
- // 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.
- /**
- * These variables store the boundaries of the Actor's hitbox.
- * They are absolute positions rather than relative.
- * For example, if the actor is at horizontal pixel position 32, and the left side of the hitbox is at horizontal pixel position 24,
- * then the variable hitboxLeft would have value 24 rather than value -8.
- */
- public int hitboxLeft, hitboxRight, hitboxTop, hitboxBottom;
- // The actual collider representing the Actor's hitbox.
- private BoxCollider2D bc;
- /**
- * Initialize the actor.
- */
- protected virtual void Start() {
- // Initialize the pixel position, hitbox boundaries, and subpixel offset of the actor.
- bc = GetComponent<BoxCollider2D>();
- pixelPosition = new Vector3Int(RoundTiesDown(transform.position.x), RoundTiesDown(transform.position.y), 0);
- subpixelOffset = transform.position - pixelPosition;
- ComputeHitbox();
- }
- /**
- * Move the Actor by the given amount along the given axis, ignoring collisions.
- */
- public void Move(float amount, int axis) {
- // Determine the target position.
- float targetPosition = pixelPosition[axis] + subpixelOffset[axis] + amount;
- // Round it to get the target pixel position, and update the pixel position.
- pixelPosition[axis] = RoundTiesDown(targetPosition);
- // Update the subpixel offset: it's the difference between the target position and our new pixel position.
- subpixelOffset[axis] = targetPosition - pixelPosition[axis];
- // We've moved, so recompute our hitbox boundaries and sync pixel position with transform position.
- ComputeHitbox();
- UpdateTransform();
- }
- /**
- * Move the Actor by the given amount along the given axis, handling collisions appropriately.
- * This function depends on two other functions: CheckCollisions and HandleCollisions.
- * The Actor is moved in small steps, using CheckCollisions to see if a step will cause a collision will occur.
- * When collisions are found, they are processed using HandleCollisions.
- * HandleCollisions returns a boolean value that determines if the Actor should stop (e.g., if a wall was hit)
- * or keep moving (e.g., if a collectable object was hit).
- * You can control how each Actor deals with collisions and what kinds of objects they collide with by overriding
- * CheckCollisions and HandleCollisions.
- * The total amount that the Actor moved in pixels is returned.
- */
- public virtual int MoveAndCollide(float amount, int axis) {
- // Determine the target position.
- float targetPosition = pixelPosition[axis] + subpixelOffset[axis] + amount;
- // Round it to get the target pixel position.
- int targetPixelPosition = RoundTiesDown(targetPosition);
- // The pixel amount we want to move is the difference between the target position and current position.
- int pixelAmount = targetPixelPosition - pixelPosition[axis];
- // Update the subpixel offset: it's the difference between the target position and the target pixel position.
- subpixelOffset[axis] = targetPosition - targetPixelPosition;
- // Set up variables related to stepping.
- int stepSize = MAX_STEP_SIZE;
- int stepAmount = System.Math.Sign(amount) * stepSize;
- Vector3Int stepVector = Vector3Int.zero;
- // Track the total amount we actually moved.
- int movedAmount = 0;
- while (pixelAmount != 0) {
- // If the pixel amount left to move is actually smaller than our step size...
- if (System.Math.Abs(pixelAmount) < stepSize) {
- stepAmount = pixelAmount; // Just change our step amount to the entire pixel amount.
- }
- stepVector[axis] = stepAmount;
- // Check for collisions one step ahead.
- List<Collider2D> colliders = CheckCollisions(stepVector);
- if (colliders.Count != 0) { // If we hit something...
- bool stop = HandleCollisions(colliders); // Handle the collisions and figure out whether we should stop
- if (stop) { // If we hit something that should make the actor stop...
- if (stepSize > 1) { // Try to decrease the step size, unless we're already taking one-pixel steps.
- stepSize /= 2;
- stepAmount = System.Math.Sign(amount) * stepSize;
- // Go back to the start of the loop with smaller steps to try to get closer to the collider.
- continue;
- }
- // If we're down to one pixel steps, that means we can't move forward at all without hitting something.
- // So we break out of the loop and return.
- break;
- }
- }
- // Now, we actually take the step (update the pixel position).
- pixelPosition += stepVector;
- // Decrease the pixel amount left to move, and increase the total amount moved.
- pixelAmount -= stepAmount;
- movedAmount += stepAmount;
- // Because the character moved, we need to recompute the hitbox.
- ComputeHitbox();
- }
- // The character is done moving, so sync the transform position with the pixel position.
- // Why don't we call this in the loop? Well, it's because we check for colliders by comparing
- // the hitbox variables of this Actor with other colliders, rather than using the Actor's transform.
- // No Actors other than this one are moving around in the loop, so we don't need to resync transforms,
- // we can just keep hitbox variables updated. If you modify this method to do more complicated things,
- // you might need to sync transforms within the loop.
- UpdateTransform();
- return movedAmount;
- }
- /**
- * Returns a list of colliders that would touch this Actor if it was moved according to the offset vector.
- * For example, if the offset was (0,1,0), this would return a list of colliders one pixel below the Actor.
- * The default implementation reports all collisions except for self-collisions.
- * Override this to change how the actor checks for collisions when MoveAndCollide is called.
- * For example, to implement jump-through platforms, you could modify this method so it does not report a collision
- * unless the Actor is above the jump-through platform.
- */
- protected virtual List<Collider2D> CheckCollisions(Vector3Int offset) {
- Collider2D[] colliders = Physics2D.OverlapAreaAll(new Vector2Int(hitboxLeft + 1, hitboxTop - 1) + ((Vector2Int)offset),
- new Vector2Int(hitboxRight - 1, hitboxBottom + 1) + ((Vector2Int)offset));
- List<Collider2D> filteredColliders = new List<Collider2D>(colliders);
- filteredColliders.Remove(bc);
- return filteredColliders;
- }
- /**
- * Given a list of colliders, this method allows you to implement collision response for each collider.
- * For example, if you collided with a coin, you could destroy the coin and increment a coin counter in this method.
- * The return value determines if the Actor should stop moving when it hits this object during a MoveAndCollide call.
- * If true, the Actor will stop. For example, you could use this to make the Actor stop when a wall is hit.
- * The default implementation makes the Actor stop if it hits anything.
- */
- protected virtual bool HandleCollisions(List<Collider2D> colliders) {
- return colliders.Count != 0;
- }
- /**
- * This is a special round function that always rounds down when the fractional part of the number is 0.5.
- * Usually it doesn't matter if you use this or the "balanced" rounding used by Unity's Mathf.Round.
- * However, this function is used in the default implementations of Move and MoveAndCollide, as it provides more consistent
- * behavior which is desirable for some advanced uses (like implementing Actors which push or carry other Actors).
- */
- protected int RoundTiesDown(float number) {
- return Mathf.CeilToInt(number - 0.5f);
- }
- /**
- * This method computes the boundaries of the Actor's hitbox.
- * When the Actor's pixel position changes, this function should be called to keep the hitbox position in sync.
- */
- protected void ComputeHitbox() {
- hitboxLeft = pixelPosition.x - ((int)bc.bounds.extents.x);
- hitboxRight = pixelPosition.x + ((int)bc.bounds.extents.x);
- hitboxBottom = pixelPosition.y - ((int)bc.bounds.extents.y);
- hitboxTop = pixelPosition.y + ((int)bc.bounds.extents.y);
- }
- /**
- * This method updates the transform of the Actor to match the Actor's pixel position.
- * It also updates Unity's internal tracking of transform positions, so that the new transform position will be reflected
- * in collision checks.
- */
- protected void UpdateTransform() {
- transform.position = pixelPosition;
- Physics2D.SyncTransforms();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement