using System.Collections.Generic; using UnityEngine; public enum BuildAxis { X, // YZ plane Y, // XZ plane (horizontal) Z // XY plane } public class VirtualPlatformManager : MonoBehaviour { private static VirtualPlatformManager instance; [Header("Platform Settings")] [SerializeField] private float platformThickness = 0.1f; [Header("Visual Settings")] [SerializeField] private bool showGrid = true; [SerializeField] private float gridSpacing = 1f; [SerializeField] private int gridExtent = 50; // How many grid lines from center [SerializeField] private float lineWidth = 0.5f; [SerializeField] private Color gridColor = new Color(1f, 1f, 1f, 0.3f); [SerializeField] private Material gridMaterial; [Header("Current Platform")] [SerializeField] private BuildAxis currentAxis = BuildAxis.Y; [SerializeField] private float currentDistance = 0f; private List gridLines = new List(); private GameObject currentPlatform; private GameObject gridVisual; private GameObject colliderObj; private BoxCollider platformCollider; private Camera mainCamera; private void Awake() { if (instance == null) instance = this; else Destroy(this.gameObject); } public static VirtualPlatformManager getInstance() => instance; private void Start() { mainCamera = Camera.main; if (gridMaterial == null) { gridMaterial = new Material(Shader.Find("Sprites/Default")); } CreatePlatform(); } private void LateUpdate() { if (showGrid && gridVisual != null) { UpdateGridPosition(); } // Update collider position and size to match grid if (platformCollider != null) { UpdateColliderPosition(); } } private void UpdateGridPosition() { Vector3 cameraPos = mainCamera.transform.position; Vector3 planeOrigin = GetPlatformOrigin(); Vector3 axis1, axis2; GetPlatformAxes(out axis1, out axis2); Vector3 relativePos = cameraPos - planeOrigin; float coord1 = Mathf.Round(Vector3.Dot(relativePos, axis1) / gridSpacing) * gridSpacing; float coord2 = Mathf.Round(Vector3.Dot(relativePos, axis2) / gridSpacing) * gridSpacing; Vector3 gridCenter = planeOrigin + axis1 * coord1 + axis2 * coord2; UpdateGridLines(gridCenter, axis1, axis2); } private void UpdateColliderPosition() { Vector3 cameraPos = mainCamera.transform.position; Vector3 planeOrigin = GetPlatformOrigin(); Vector3 axis1, axis2; GetPlatformAxes(out axis1, out axis2); // Project camera position onto the plane Vector3 relativePos = cameraPos - planeOrigin; float coord1 = Vector3.Dot(relativePos, axis1); float coord2 = Vector3.Dot(relativePos, axis2); // Center collider on camera's projected position Vector3 colliderCenter = planeOrigin + axis1 * coord1 + axis2 * coord2; colliderObj.transform.position = colliderCenter; // Make collider large enough to cover the grid float colliderSize = gridExtent * gridSpacing * 2f; platformCollider.size = new Vector3(colliderSize, platformThickness, colliderSize); } private void UpdateGridLines(Vector3 center, Vector3 axis1, Vector3 axis2) { int lineIndex = 0; // Update lines along axis1 for (int i = -gridExtent; i <= gridExtent; i++) { Vector3 offset = axis2 * (i * gridSpacing); Vector3 start = center + offset - axis1 * (gridExtent * gridSpacing); Vector3 end = center + offset + axis1 * (gridExtent * gridSpacing); if (lineIndex < gridLines.Count) { gridLines[lineIndex].SetPosition(0, start); gridLines[lineIndex].SetPosition(1, end); } lineIndex++; } // Update lines along axis2 for (int i = -gridExtent; i <= gridExtent; i++) { Vector3 offset = axis1 * (i * gridSpacing); Vector3 start = center + offset - axis2 * (gridExtent * gridSpacing); Vector3 end = center + offset + axis2 * (gridExtent * gridSpacing); if (lineIndex < gridLines.Count) { gridLines[lineIndex].SetPosition(0, start); gridLines[lineIndex].SetPosition(1, end); } lineIndex++; } } public void SwitchToAxis(BuildAxis axis) { currentAxis = axis; RecreatePlatform(); } public void MovePlatform(float delta) { currentDistance += delta; UpdatePlatformDistance(); } public void SetPlatformDistance(float distance) { currentDistance = distance; UpdatePlatformDistance(); } private void UpdatePlatformDistance() { // Just update the rotation/orientation without recreating everything SetupPlatformTransform(); } private void RecreatePlatform() { if (currentPlatform != null) Destroy(currentPlatform); if (gridVisual != null) Destroy(gridVisual); CreatePlatform(); } private void CreatePlatform() { // Create container currentPlatform = new GameObject($"Platform_{currentAxis}_{currentDistance}"); currentPlatform.transform.parent = transform; // Create invisible collider platform colliderObj = new GameObject("Collider"); colliderObj.transform.parent = currentPlatform.transform; colliderObj.layer = LayerMask.NameToLayer("World"); platformCollider = colliderObj.AddComponent(); // Position and orient the platform based on axis SetupPlatformTransform(); // Create visual grid if enabled if (showGrid) { CreateGridVisual(); } } private void CreateGridVisual() { gridVisual = new GameObject("GridVisual"); gridVisual.transform.parent = currentPlatform.transform; // Pre-create all line renderers int totalLines = (gridExtent * 2 + 1) * 2; gridLines.Clear(); for (int i = 0; i < totalLines; i++) { LineRenderer line = CreateLine(Vector3.zero, Vector3.zero); gridLines.Add(line); } } private void SetupPlatformTransform() { Vector3 rotation = Vector3.zero; switch (currentAxis) { case BuildAxis.X: // YZ plane rotation = new Vector3(0, 0, 90); break; case BuildAxis.Y: // XZ plane (horizontal) rotation = Vector3.zero; break; case BuildAxis.Z: // XY plane rotation = new Vector3(90, 0, 0); break; } colliderObj.transform.rotation = Quaternion.Euler(rotation); // Initial position will be updated in LateUpdate UpdateColliderPosition(); } private LineRenderer CreateLine(Vector3 start, Vector3 end) { GameObject lineObj = new GameObject("GridLine"); lineObj.transform.parent = gridVisual.transform; LineRenderer line = lineObj.AddComponent(); line.material = gridMaterial; line.startColor = gridColor; line.endColor = gridColor; line.startWidth = lineWidth; line.endWidth = lineWidth; line.positionCount = 2; line.SetPosition(0, start); line.SetPosition(1, end); line.useWorldSpace = true; return line; } private Vector3 GetPlatformOrigin() { switch (currentAxis) { case BuildAxis.X: return new Vector3(currentDistance, 0, 0); case BuildAxis.Y: return new Vector3(0, currentDistance, 0); case BuildAxis.Z: return new Vector3(0, 0, currentDistance); default: return Vector3.zero; } } private void GetPlatformAxes(out Vector3 axis1, out Vector3 axis2) { switch (currentAxis) { case BuildAxis.X: // YZ plane axis1 = Vector3.up; axis2 = Vector3.forward; break; case BuildAxis.Y: // XZ plane axis1 = Vector3.right; axis2 = Vector3.forward; break; case BuildAxis.Z: // XY plane axis1 = Vector3.right; axis2 = Vector3.up; break; default: axis1 = Vector3.right; axis2 = Vector3.up; break; } } public void ToggleGridVisibility(bool visible) { showGrid = visible; if (gridVisual != null) gridVisual.SetActive(visible); } public BuildAxis GetCurrentAxis() => currentAxis; public float GetCurrentDistance() => currentDistance; public Vector3 GetPlatformPosition() => GetPlatformOrigin(); }