Skip to content

Instantly share code, notes, and snippets.

@dylanwolf
Created April 11, 2023 05:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dylanwolf/1edf97c5e38a369d6b2cd6d7ca18ab52 to your computer and use it in GitHub Desktop.
Save dylanwolf/1edf97c5e38a369d6b2cd6d7ca18ab52 to your computer and use it in GitHub Desktop.
Unity collider builder for procedurally generated platformer
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// Implementation of http://www.imageprocessingplace.com/downloads_V3/root_downloads/tutorials/contour_tracing_Abeer_George_Ghuneim/theo.html
public static class PolygonCreator {
static List<Vector2> result = new List<Vector2>();
enum Direction
{
Up,
Down,
Left,
Right
}
static Direction RotateLeft(Direction current)
{
switch (current)
{
case Direction.Up:
return Direction.Left;
break;
case Direction.Left:
return Direction.Down;
break;
case Direction.Down:
return Direction.Right;
break;
}
return Direction.Up;
}
static Direction RotateRight(Direction current)
{
switch (current)
{
case Direction.Up:
return Direction.Right;
break;
case Direction.Right:
return Direction.Down;
break;
case Direction.Down:
return Direction.Left;
break;
}
return Direction.Up;
}
static bool IsSomething(LevelBlock[,] blocks, int x, int y, bool isPassthrough)
{
if (x < 0 || x > maxX || y < 0 || y > maxY)
return false;
if (isPassthrough)
return blocks[y, x] == LevelBlock.Platform;
else
return blocks[y, x] != LevelBlock.Nothing && blocks[y,x] != LevelBlock.Platform;
}
static void SetDeltaValues(Direction dir)
{
switch (dir)
{
case Direction.Up:
P1X = -1; P1Y = 1;
P2X = 0; P2Y = 1;
break;
case Direction.Left:
P1X = -1; P1Y = -1;
P2X = -1; P2Y = 0;
break;
case Direction.Down:
P1X = 1; P1Y = -1;
P2X = 0; P2Y = -1;
break;
case Direction.Right:
P1X = 1; P1Y = 1;
P2X = 1; P2Y = 0;
break;
}
}
static void CreateVertex(int tileX, int tileY, int cornerX, int cornerY)
{
result.Add(new Vector2(
(tileX * ChunkManager.Current.TileSize) + (cornerX * ChunkManager.Current.TileSize / 2.0f),
(tileY * ChunkManager.Current.TileSize) + (cornerY * ChunkManager.Current.TileSize / 2.0f)
));
}
static int ZeroOrSign(float value)
{
if (value == 0)
return 0;
else
return (int)Mathf.Sign(value);
}
static bool CanJoinLineSegments(Vector2 a, Vector2 b, Vector2 c)
{
return (
ZeroOrSign(b.x - a.x) == ZeroOrSign(c.x - b.x) &&
ZeroOrSign(b.y - a.y) == ZeroOrSign(c.y - b.y)
);
}
static void CleanupResults()
{
int index = 1;
while (index + 1 < result.Count)
{
if (
CanJoinLineSegments(result[index-1],result[index],result[index+1])
)
{
result.RemoveAt(index);
}
else
{
index++;
}
}
}
static int P1X = 0;
static int P2X = 0;
static int P1Y = 0;
static int P2Y = 0;
static int currentX = 0;
static int currentY = 0;
static int maxX = 0;
static int maxY = 0;
static bool[,] generated;
static PolygonCollider2D collider;
static GameObject passthroughColliders;
static GameObject worldColliders;
static PlatformEffector2D platform;
static bool isPassthrough;
const string WORLD_COLLIDERS = "WorldColliders";
const string PASSTHROUGH_COLLIDERS = "PassthroughColliders";
public static void CreatePolygons(GameObject parent, LevelBlock[,] blocks)
{
passthroughColliders = new GameObject();
passthroughColliders.name = PASSTHROUGH_COLLIDERS;
passthroughColliders.layer = LayerMasks.PassthroughLayer;
passthroughColliders.transform.parent = parent.transform;
passthroughColliders.transform.localPosition = Vector3.zero;
worldColliders = new GameObject();
worldColliders.name = WORLD_COLLIDERS;
worldColliders.layer = LayerMasks.WorldLayer;
worldColliders.transform.parent = parent.transform;
worldColliders.transform.localPosition = Vector3.zero;
maxY = blocks.GetLength(0) - 1;
maxX = blocks.GetLength(1) - 1;
generated = new bool[maxY + 1, maxX + 1];
platform = null;
for (int col = 0; col <= maxX; col++)
{
for (int row = 0; row <= maxY; row++)
{
// Is blank or already handled
if (generated[row, col] || blocks[row, col] == LevelBlock.Nothing)
continue;
// Is surrounded
isPassthrough = blocks[row, col] == LevelBlock.Platform;
if (IsSomething(blocks, col + 1, row, isPassthrough) && IsSomething(blocks, col - 1, row, isPassthrough) &&
IsSomething(blocks, col, row + 1, isPassthrough) && IsSomething(blocks, col, row - 1, isPassthrough))
continue;
collider = (isPassthrough ? passthroughColliders : worldColliders).AddComponent<PolygonCollider2D>();
collider.points = CreatePolygon(blocks, col, row, isPassthrough);
}
}
}
public static Vector2[] CreatePolygon(LevelBlock[,] blocks, int startX, int startY, bool isPassthrough)
{
result.Clear();
maxY = blocks.GetLength(0) - 1;
maxX = blocks.GetLength(1) - 1;
currentX = startX;
currentY = startY;
Direction dir = Direction.Up;
SetDeltaValues(dir);
// Create first line segment
CreateVertex(currentX, currentY, -1, -1);
CreateVertex(currentX, currentY, -1, 1);
generated[currentY, currentX] = true;
int rotations = 0;
while (rotations < 3)
{
if (IsSomething(blocks, currentX + P2X, currentY + P2Y, isPassthrough))
{
if (IsSomething(blocks, currentX + P1X, currentY + P1Y, isPassthrough))
{
currentX = currentX + P1X;
currentY = currentY + P1Y;
dir = RotateLeft(dir);
SetDeltaValues(dir);
CreateVertex(currentX, currentY, P1X, P1Y);
generated[currentY, currentX] = true;
}
else
{
currentX = currentX + P2X;
currentY = currentY + P2Y;
CreateVertex(currentX, currentY, P1X, P1Y);
generated[currentY, currentX] = true;
}
if (currentX == startX && currentY == startY)
{
break;
}
rotations = 0;
}
else
{
dir = RotateRight(dir);
SetDeltaValues(dir);
CreateVertex(currentX, currentY, P1X, P1Y);
generated[currentY, currentX] = true;
rotations++;
}
}
CleanupResults();
return result.ToArray();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment