Last active
March 23, 2024 11:44
-
-
Save Shilo/e8ef6ea371c847b9267967b78d162412 to your computer and use it in GitHub Desktop.
Godot tool for drawing 2d collision shapes. (C# or GDScript)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@tool | |
class_name ColorCollisionShape2D extends CollisionShape2D | |
@export var color: Color: | |
set(value): | |
color = value | |
queue_redraw() | |
@export var outline_color: Color: | |
set(value): | |
outline_color = value | |
queue_redraw() | |
@export var outline_width: int = 0: | |
set(value): | |
value = max(value, 0) | |
if outline_width == value: | |
return | |
outline_width = value | |
queue_redraw() | |
@export var outline_rect_tile_size: Vector2 = Vector2.ZERO: | |
set(value): | |
outline_rect_tile_size = value.abs() | |
queue_redraw() | |
func _draw(): | |
if !shape: | |
return | |
if shape is CircleShape2D: | |
var outline: int = min(outline_width, shape.radius) | |
if outline > 0: | |
_draw_circle(Vector2.ZERO, shape.radius, outline_color) | |
_draw_circle(Vector2.ZERO, shape.radius, color, outline) | |
elif shape is CapsuleShape2D: | |
var outline: int = min(outline_width, shape.radius) | |
if outline > 0: | |
_draw_capsule(shape.radius, shape.height, outline_color) | |
_draw_capsule(shape.radius, shape.height, color, outline) | |
else: | |
var rect: Rect2 = Rect2(-shape.extents, shape.extents * 2) | |
var outline: int = min(outline_width, shape.extents.x, shape.extents.y) | |
if outline_rect_tile_size.x > 0 and outline_rect_tile_size.y > 0: | |
outline = min(outline, outline_rect_tile_size.x, outline_rect_tile_size.y) | |
for x in range(ceil(rect.size.x / outline_rect_tile_size.x)): | |
for y in range(ceil(rect.size.y / outline_rect_tile_size.y)): | |
var width = min( | |
outline_rect_tile_size.x, rect.size.x - x * outline_rect_tile_size.x | |
) | |
var height = min( | |
outline_rect_tile_size.y, rect.size.y - y * outline_rect_tile_size.y | |
) | |
var tile_rect = Rect2( | |
Vector2(x, y) * outline_rect_tile_size + rect.position, | |
Vector2(width, height) | |
) | |
_draw_rect_and_outline(tile_rect, outline) | |
else: | |
_draw_rect_and_outline(rect, outline) | |
func _draw_rect_and_outline(rect: Rect2, outline: int = 0): | |
if outline > 0: | |
_draw_rect(rect, outline_color) | |
_draw_rect(rect, color, outline) | |
func _draw_rect(rect: Rect2, rect_color: Color, inset_width: int = 0): | |
if inset_width > 0: | |
rect = Rect2( | |
rect.position + Vector2(inset_width, inset_width), | |
rect.size - Vector2(inset_width * 2, inset_width * 2) | |
) | |
draw_rect(rect, rect_color) | |
func _draw_circle( | |
circle_position: Vector2, radius: float, circle_color: Color, inset_width: int = 0 | |
): | |
if inset_width > 0: | |
draw_circle(circle_position, radius - inset_width, circle_color) | |
else: | |
draw_circle(circle_position, radius, circle_color) | |
func _draw_capsule(radius: float, height: float, capsule_color: Color, inset_width: int = 0): | |
height -= inset_width * 2 | |
radius -= inset_width | |
var center_height = height - radius * 2 | |
var inset_height = center_height - inset_width * 2 + inset_width * 2 | |
draw_rect( | |
Rect2(Vector2(-radius, -inset_height / 2), Vector2(radius * 2, inset_height)), capsule_color | |
) | |
draw_circle(Vector2(0, -height / 2 + radius), radius, capsule_color) | |
draw_circle(Vector2(0, height / 2 - radius), radius, capsule_color) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using Godot; | |
[Tool] | |
[GlobalClass] | |
public partial class ColorCollisionShape2D : CollisionShape2D | |
{ | |
private Color _color = Colors.White; | |
[Export] | |
public Color Color | |
{ | |
get => _color; | |
set | |
{ | |
_color = value; | |
QueueRedraw(); | |
} | |
} | |
private Color _outlineColor = Colors.White; | |
[Export] | |
public Color OutlineColor | |
{ | |
get => _outlineColor; | |
set | |
{ | |
_outlineColor = value; | |
QueueRedraw(); | |
} | |
} | |
private int _outlineWidth; | |
[Export] | |
public int OutlineWidth | |
{ | |
get => _outlineWidth; | |
set | |
{ | |
_outlineWidth = Mathf.Max(value, 0); | |
QueueRedraw(); | |
} | |
} | |
private Vector2 _outlineTileRectSize = Vector2.Zero; | |
[Export] | |
public Vector2 OutlineTileRectSize | |
{ | |
get => _outlineTileRectSize; | |
set | |
{ | |
_outlineTileRectSize = value.Abs(); | |
QueueRedraw(); | |
} | |
} | |
[Export] | |
public bool IncludeLightOccluder = false; // Only supports RectangleShape2D and ConvexPolygonShape2D | |
public override void _Ready() | |
{ | |
if (!Engine.IsEditorHint()) | |
UpdateLightOccluder(); | |
} | |
public void UpdateLightOccluder() | |
{ | |
if (!IncludeLightOccluder) | |
return; | |
LightOccluder2D lightOccluder = new LightOccluder2D(); | |
OccluderPolygon2D occluderPolygon = new OccluderPolygon2D(); | |
switch (Shape) | |
{ | |
case RectangleShape2D rectangleShape: | |
{ | |
var rect = new Rect2(-rectangleShape.Size / 2, rectangleShape.Size); | |
occluderPolygon.Polygon = new[] | |
{ | |
rect.Position, | |
rect.Position + new Vector2(rect.Size.X, 0), | |
rect.Position + rect.Size, | |
rect.Position + new Vector2(0, rect.Size.Y) | |
}; | |
break; | |
} | |
case ConvexPolygonShape2D convexPolygonShape: | |
{ | |
occluderPolygon.Polygon = convexPolygonShape.Points; | |
break; | |
} | |
} | |
lightOccluder.Occluder = occluderPolygon; | |
foreach (var child in GetChildren()) | |
{ | |
if (child is LightOccluder2D) | |
RemoveChild(child); | |
} | |
AddChild(lightOccluder); | |
} | |
public override void _Draw() | |
{ | |
switch (Shape) | |
{ | |
case null: | |
return; | |
case CircleShape2D circleShape: | |
{ | |
var outline = Mathf.Min(OutlineWidth, (int)circleShape.Radius); | |
if (outline > 0) DrawCircle(Vector2.Zero, circleShape.Radius, OutlineColor); | |
DrawCircle(Vector2.Zero, circleShape.Radius, Color, outline); | |
break; | |
} | |
case CapsuleShape2D capsuleShape: | |
{ | |
var outline = Mathf.Min(OutlineWidth, (int)capsuleShape.Radius); | |
if (outline > 0) DrawCapsule(capsuleShape.Radius, capsuleShape.Height, OutlineColor); | |
DrawCapsule(capsuleShape.Radius, capsuleShape.Height, Color, outline); | |
break; | |
} | |
case RectangleShape2D rectangleShape: | |
{ | |
var rect = new Rect2(-rectangleShape.Size / 2, rectangleShape.Size); | |
var outline = Mathf.Min(Mathf.Min(OutlineWidth, (int)rectangleShape.Size.X), | |
(int)rectangleShape.Size.Y); | |
if (OutlineTileRectSize is { X: > 0, Y: > 0 }) | |
{ | |
outline = Mathf.Min(Mathf.Min(outline, (int)OutlineTileRectSize.X), (int)OutlineTileRectSize.Y); | |
for (var x = 0; x < Mathf.CeilToInt(rect.Size.X / OutlineTileRectSize.X); x++) | |
for (var y = 0; y < Mathf.CeilToInt(rect.Size.Y / OutlineTileRectSize.Y); y++) | |
{ | |
var width = Mathf.Min(OutlineTileRectSize.X, rect.Size.X - x * OutlineTileRectSize.X); | |
var height = Mathf.Min(OutlineTileRectSize.Y, rect.Size.Y - y * OutlineTileRectSize.Y); | |
var tileRect = new Rect2( | |
new Vector2(x, y) * OutlineTileRectSize + rect.Position, | |
new Vector2(width, height) | |
); | |
DrawRectAndOutline(tileRect, outline); | |
} | |
} | |
else | |
{ | |
DrawRectAndOutline(rect, outline); | |
} | |
break; | |
} | |
case ConvexPolygonShape2D convexPolygonShape: | |
{ | |
var outline = OutlineWidth; | |
if (outline > 0) DrawPolygon(convexPolygonShape.Points, OutlineColor); | |
DrawPolygon(convexPolygonShape.Points, Color, outline); | |
break; | |
} | |
} | |
} | |
private void DrawPolygon(Vector2[] points, Color polygonColor, int insetWidth = 0) | |
{ | |
if (insetWidth > 0) | |
{ | |
var insetPoints = new Vector2[points.Length]; | |
var centerPoint = points.Aggregate(Vector2.Zero, (current, point) => current + point); | |
centerPoint /= points.Length; | |
for (var i = 0; i < points.Length; i++) | |
insetPoints[i] = points[i].MoveToward(centerPoint, insetWidth); | |
/* Old method that only works with 1 inset width | |
for (var i = 0; i < points.Length; i++) | |
{ | |
var point = points[i]; | |
var nextPoint = points[(i + 1) % points.Length]; | |
var prevPoint = points[(i - 1 + points.Length) % points.Length]; | |
var nextPointDir = (nextPoint - point).Normalized(); | |
var prevPointDir = (prevPoint - point).Normalized(); | |
var nextPointInset = nextPoint - nextPointDir * insetWidth; | |
var prevPointInset = prevPoint - prevPointDir * insetWidth; | |
var insetPoint = point + (nextPointInset - point).Normalized() + (prevPointInset - point).Normalized(); | |
insetPoints[i] = insetPoint; | |
} | |
*/ | |
points = insetPoints; | |
} | |
base.DrawPolygon(points, new[] { polygonColor }); | |
} | |
private void DrawRectAndOutline(Rect2 rect, int outline = 0) | |
{ | |
if (outline > 0) DrawRect(rect, OutlineColor); | |
DrawRect(rect, Color, outline); | |
} | |
private void DrawRect(Rect2 rect, Color rectColor, int insetWidth = 0) | |
{ | |
if (insetWidth > 0) | |
rect = new Rect2( | |
rect.Position + new Vector2(insetWidth, insetWidth), | |
rect.Size - new Vector2(insetWidth * 2, insetWidth * 2) | |
); | |
base.DrawRect(rect, rectColor); | |
} | |
private void DrawCircle(Vector2 circlePosition, float radius, Color circleColor, int insetWidth = 0) | |
{ | |
if (insetWidth > 0) | |
((CanvasItem)this).DrawCircle(circlePosition, radius - insetWidth, circleColor); | |
else | |
((CanvasItem)this).DrawCircle(circlePosition, radius, circleColor); | |
} | |
private void DrawCapsule(float radius, float height, Color capsuleColor, int insetWidth = 0) | |
{ | |
height -= insetWidth * 2; | |
radius -= insetWidth; | |
var centerHeight = height - radius * 2; | |
var insetHeight = centerHeight - insetWidth * 2 + insetWidth * 2; | |
base.DrawRect( | |
new Rect2(new Vector2(-radius, -insetHeight / 2), new Vector2(radius * 2, insetHeight)), | |
capsuleColor | |
); | |
((CanvasItem)this).DrawCircle(new Vector2(0, -height / 2 + radius), radius, capsuleColor); | |
((CanvasItem)this).DrawCircle(new Vector2(0, height / 2 - radius), radius, capsuleColor); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment