From 5aace8408a1667cae45a0e3698637910fc17e595 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sat, 22 Feb 2025 11:02:10 -0600 Subject: [PATCH] Generic shape/collider configs --- NewHorizons/Builder/Props/ShapeBuilder.cs | 140 ++++++++++++++++++ .../External/Modules/Props/ShapeInfo.cs | 92 ++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 NewHorizons/Builder/Props/ShapeBuilder.cs create mode 100644 NewHorizons/External/Modules/Props/ShapeInfo.cs diff --git a/NewHorizons/Builder/Props/ShapeBuilder.cs b/NewHorizons/Builder/Props/ShapeBuilder.cs new file mode 100644 index 00000000..e7594f6e --- /dev/null +++ b/NewHorizons/Builder/Props/ShapeBuilder.cs @@ -0,0 +1,140 @@ +using NewHorizons.Components; +using NewHorizons.External.Modules.Props; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Builder.Props +{ + public static class ShapeBuilder + { + public static Component AddShapeOrCollider(GameObject go, ShapeInfo info) + { + if (info.useShape.HasValue) + { + // Explicitly add either a shape or collider if specified + if (info.useShape.Value) + return AddShape(go, info); + else + return AddCollider(go, info); + } + else + { + // Prefer colliders over shapes if no preference is specified + if (info.type is ShapeType.Sphere or ShapeType.Box or ShapeType.Capsule) + return AddCollider(go, info); + else + return AddShape(go, info); + } + } + + public static Shape AddShape(GameObject go, ShapeInfo info) + { + if (info.hasCollision) + { + throw new NotSupportedException($"Shapes do not support collision; set {info.hasCollision} to false and use a supported collider type (sphere, box, or capsule)."); + } + if (info.useShape.HasValue && !info.useShape.Value) + { + throw new NotSupportedException($"{info.useShape} was explicitly set to false but a shape is required here."); + } + switch (info.type) + { + case ShapeType.Sphere: + var sphereShape = go.AddComponent(); + sphereShape._radius = info.radius; + sphereShape._center = info.offset ?? Vector3.zero; + return sphereShape; + case ShapeType.Box: + var boxShape = go.AddComponent(); + boxShape._size = info.size ?? Vector3.one; + boxShape._center = info.offset ?? Vector3.zero; + return boxShape; + case ShapeType.Capsule: + var capsuleShape = go.AddComponent(); + capsuleShape._radius = info.radius; + capsuleShape._direction = (int)info.direction; + capsuleShape._height = info.height; + capsuleShape._center = info.offset ?? Vector3.zero; + return capsuleShape; + case ShapeType.Cylinder: + var cylinderShape = go.AddComponent(); + cylinderShape._radius = info.radius; + cylinderShape._height = info.height; + cylinderShape._center = info.offset ?? Vector3.zero; + return cylinderShape; + case ShapeType.Cone: + var coneShape = go.AddComponent(); + coneShape._topRadius = info.innerRadius; + coneShape._bottomRadius = info.outerRadius; + coneShape._direction = (int)info.direction; + coneShape._height = info.height; + coneShape._center = info.offset ?? Vector3.zero; + return coneShape; + case ShapeType.Hemisphere: + var hemisphereShape = go.AddComponent(); + hemisphereShape._radius = info.radius; + hemisphereShape._direction = (int)info.direction; + hemisphereShape._cap = info.cap; + hemisphereShape._center = info.offset ?? Vector3.zero; + return hemisphereShape; + case ShapeType.Hemicapsule: + var hemicapsuleShape = go.AddComponent(); + hemicapsuleShape._radius = info.radius; + hemicapsuleShape._direction = (int)info.direction; + hemicapsuleShape._height = info.height; + hemicapsuleShape._cap = info.cap; + hemicapsuleShape._center = info.offset ?? Vector3.zero; + return hemicapsuleShape; + case ShapeType.Ring: + var ringShape = go.AddComponent(); + ringShape.innerRadius = info.innerRadius; + ringShape.outerRadius = info.outerRadius; + ringShape.height = info.height; + ringShape.center = info.offset ?? Vector3.zero; + return ringShape; + default: + throw new ArgumentOutOfRangeException(nameof(info.type), info.type, $"Unsupported shape type"); + } + } + + public static Collider AddCollider(GameObject go, ShapeInfo info) + { + if (info.useShape.HasValue && info.useShape.Value) + { + throw new NotSupportedException($"{info.useShape} was explicitly set to true but a non-shape collider is required here."); + } + switch (info.type) + { + case ShapeType.Sphere: + var sphereCollider = go.AddComponent(); + sphereCollider.radius = info.radius; + sphereCollider.center = info.offset ?? Vector3.zero; + sphereCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return sphereCollider; + case ShapeType.Box: + var boxCollider = go.AddComponent(); + boxCollider.size = info.size ?? Vector3.one; + boxCollider.center = info.offset ?? Vector3.zero; + boxCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return boxCollider; + case ShapeType.Capsule: + var capsuleCollider = go.AddComponent(); + capsuleCollider.radius = info.radius; + capsuleCollider.direction = (int)info.direction; + capsuleCollider.height = info.height; + capsuleCollider.center = info.offset ?? Vector3.zero; + capsuleCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return capsuleCollider; + default: + throw new ArgumentOutOfRangeException(nameof(info.type), info.type, $"Unsupported collider type"); + } + } + } +} diff --git a/NewHorizons/External/Modules/Props/ShapeInfo.cs b/NewHorizons/External/Modules/Props/ShapeInfo.cs new file mode 100644 index 00000000..19b81ecb --- /dev/null +++ b/NewHorizons/External/Modules/Props/ShapeInfo.cs @@ -0,0 +1,92 @@ +using NewHorizons.External.SerializableData; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Props +{ + [JsonObject] + public class ShapeInfo + { + /// + /// The type of shape or collider to add. Sphere, box, and capsule colliders are more performant and support collision. Defaults to sphere. + /// + public ShapeType type = ShapeType.Sphere; + + /// + /// The radius of the shape or collider. Defaults to 0.5 meters. Only used by spheres, capsules, cylinders, hemispheres, hemicapsules, and rings. + /// + public float radius = 0.5f; + + /// + /// The height of the shape or collider. Defaults to 1 meter. Only used by capsules, cylinders, cones, hemicapsules, and rings. + /// + public float height = 1f; + + /// + /// The axis that the shape or collider is aligned with. Defaults to the Y axis (up). The flat bottom of the shape will be pointing towards the negative axis. Only used by capsules, cones, hemispheres, and hemicapsules. + /// + public ColliderAxis direction = ColliderAxis.Y; + + /// + /// The inner radius of the shape. Defaults to 0 meters. Only used by cones and rings. + /// + public float innerRadius = 0f; + + /// + /// The outer radius of the shape. Defaults to 0.5 meters. Only used by cones and rings. + /// + public float outerRadius = 0.5f; + + /// + /// Whether the shape has an end cap. Defaults to true. Only used by hemispheres and hemicapsules. + /// + public bool cap = true; + + /// + /// The size of the shape or collider. Defaults to (1,1,1). Only used by boxes. + /// + public MVector3 size; + + /// + /// The offset of the shape or collider from the object's origin. Defaults to (0,0,0). Supported by all collider and shape types. + /// + public MVector3 offset; + + /// + /// Whether the collider should have collision enabled. If false, the collider will be a trigger. Defaults to false. Only supported for spheres, boxes, and capsules. + /// + public bool hasCollision = false; + + /// + /// Whether to explicitly use a shape instead of a collider. Shapes do not support collision and are less performant, but support a wider set of shapes and are required by some components. Omit this unless you explicitly want to use a sphere, box, or capsule shape instead of a collider. + /// + public bool? useShape; + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ShapeType + { + [EnumMember(Value = @"sphere")] Sphere, + [EnumMember(Value = @"box")] Box, + [EnumMember(Value = @"capsule")] Capsule, + [EnumMember(Value = @"cylinder")] Cylinder, + [EnumMember(Value = @"cone")] Cone, + [EnumMember(Value = @"hemisphere")] Hemisphere, + [EnumMember(Value = @"hemicapsule")] Hemicapsule, + [EnumMember(Value = @"ring")] Ring, + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ColliderAxis + { + [EnumMember(Value = @"x")] X = 0, + [EnumMember(Value = @"y")] Y = 1, + [EnumMember(Value = @"z")] Z = 2, + } +}