diff --git a/Assets/_Project/Definitions/Towers/BasicTower.asset b/Assets/_Project/Definitions/Towers/BasicArrowTower.asset
similarity index 84%
rename from Assets/_Project/Definitions/Towers/BasicTower.asset
rename to Assets/_Project/Definitions/Towers/BasicArrowTower.asset
index e32563a..5f00eaf 100644
--- a/Assets/_Project/Definitions/Towers/BasicTower.asset
+++ b/Assets/_Project/Definitions/Towers/BasicArrowTower.asset
@@ -10,11 +10,11 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7b353a757b6e6774d97e6fb8ba138fcc, type: 3}
- m_Name: BasicTower
+ m_Name: BasicArrowTower
m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.TowerDefinition
- DisplayName: Basic Arrow
- Description: Targets ground and air. Fires single-target arrows. The bread and butter
- of any maze.
+ DisplayName: Basic Arrow Tower
+ Description: Single-target tower that fires arrows at ground and air enemies. The
+ backbone of any maze.
FootprintSize: {x: 2, y: 2}
GoldCost: 25
BuildTime: 0.5
diff --git a/Assets/_Project/Definitions/Towers/BasicTower.asset.meta b/Assets/_Project/Definitions/Towers/BasicArrowTower.asset.meta
similarity index 100%
rename from Assets/_Project/Definitions/Towers/BasicTower.asset.meta
rename to Assets/_Project/Definitions/Towers/BasicArrowTower.asset.meta
diff --git a/Assets/_Project/Prefabs/Towers/Tower_Basic.prefab b/Assets/_Project/Prefabs/Towers/Tower_BasicArrow.prefab
similarity index 94%
rename from Assets/_Project/Prefabs/Towers/Tower_Basic.prefab
rename to Assets/_Project/Prefabs/Towers/Tower_BasicArrow.prefab
index 42defa0..1e6b505 100644
--- a/Assets/_Project/Prefabs/Towers/Tower_Basic.prefab
+++ b/Assets/_Project/Prefabs/Towers/Tower_BasicArrow.prefab
@@ -69,8 +69,9 @@ GameObject:
- component: {fileID: 9137031893466587143}
- component: {fileID: 805962841523123163}
- component: {fileID: 8853488620519990682}
+ - component: {fileID: 5360241685303413177}
m_Layer: 0
- m_Name: Tower_Basic
+ m_Name: Tower_BasicArrow
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -100,7 +101,7 @@ MeshFilter:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
- m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+ m_Mesh: {fileID: 0}
--- !u!23 &4028055828417179692
MeshRenderer:
m_ObjectHideFlags: 0
@@ -241,6 +242,21 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerRangeIndicator
rangeProjector: {fileID: 8255517343120954594}
projectionDepth: 50
+--- !u!114 &5360241685303413177
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 6482414459531823157}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 93bc533262394e1e8b122bbfad89391a, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::TD.Visuals.ConeMesh
+ sides: 16
+ radius: 0.5
+ height: 1
--- !u!1 &7580197837852108944
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/_Project/Prefabs/Towers/Tower_Basic.prefab.meta b/Assets/_Project/Prefabs/Towers/Tower_BasicArrow.prefab.meta
similarity index 100%
rename from Assets/_Project/Prefabs/Towers/Tower_Basic.prefab.meta
rename to Assets/_Project/Prefabs/Towers/Tower_BasicArrow.prefab.meta
diff --git a/Assets/_Project/Scripts/Visuals.meta b/Assets/_Project/Scripts/Visuals.meta
new file mode 100644
index 0000000..367f7c2
--- /dev/null
+++ b/Assets/_Project/Scripts/Visuals.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 16f3da2efd0d4431888e3f04056646c2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Scripts/Visuals/ConeMesh.cs b/Assets/_Project/Scripts/Visuals/ConeMesh.cs
new file mode 100644
index 0000000..d1790a5
--- /dev/null
+++ b/Assets/_Project/Scripts/Visuals/ConeMesh.cs
@@ -0,0 +1,126 @@
+// Assets/_Project/Scripts/Visuals/ConeMesh.cs
+using UnityEngine;
+
+namespace TD.Visuals
+{
+ ///
+ /// Procedurally generates a cone mesh and assigns it to the GameObject's
+ /// . Unity ships no built-in cone primitive, so this
+ /// fills that gap for tower visuals (e.g. the Basic Arrow Tower).
+ ///
+ ///
+ /// Visual only. No networking, no collision — the mesh is purely cosmetic.
+ /// The existing MeshRenderer + material (and any owner/paint tinting via
+ /// TowerInstance.tintedRenderers) keep working unchanged.
+ ///
+ /// Pivot. The cone is centred on the local origin: its base ring sits at
+ /// y = -height/2 and its apex at y = +height/2, matching the vertical
+ /// extents of a unit cube so it sits on a build tile the same way the old cube did.
+ ///
+ /// Edit-time. [ExecuteAlways] rebuilds the mesh in the editor so the
+ /// cone is visible without entering play mode. The generated mesh is flagged
+ /// HideFlags.DontSave so it is never written out as a project asset.
+ ///
+ [ExecuteAlways]
+ [RequireComponent(typeof(MeshFilter))]
+ public class ConeMesh : MonoBehaviour
+ {
+ [Tooltip("Number of radial segments around the base. Higher = smoother circle.")]
+ [Range(3, 64)]
+ [SerializeField] private int sides = 16;
+
+ [Tooltip("Radius of the cone's base in local units.")]
+ [Min(0.001f)]
+ [SerializeField] private float radius = 0.5f;
+
+ [Tooltip("Height of the cone from base to apex in local units.")]
+ [Min(0.001f)]
+ [SerializeField] private float height = 1f;
+
+ private Mesh mesh;
+
+ private void OnEnable() => Rebuild();
+
+ private void OnValidate()
+ {
+ // OnValidate can fire before OnEnable in the editor; guard the component.
+ if (isActiveAndEnabled) Rebuild();
+ }
+
+ private void OnDisable()
+ {
+ // Clean up the generated mesh so it doesn't linger when the object is destroyed.
+ if (mesh != null)
+ {
+ if (Application.isPlaying) Destroy(mesh);
+ else DestroyImmediate(mesh);
+ mesh = null;
+ }
+ }
+
+ private void Rebuild()
+ {
+ var filter = GetComponent();
+ if (filter == null) return;
+
+ if (mesh == null)
+ {
+ mesh = new Mesh { name = "ProceduralCone", hideFlags = HideFlags.DontSave };
+ }
+
+ BuildCone(mesh);
+ filter.sharedMesh = mesh;
+ }
+
+ private void BuildCone(Mesh target)
+ {
+ target.Clear();
+
+ float halfHeight = height * 0.5f;
+
+ // Vertex layout:
+ // [0 .. sides-1] base ring
+ // [sides] apex
+ // [sides + 1] base centre (for the bottom cap)
+ int baseRingStart = 0;
+ int apexIndex = sides;
+ int baseCentreIndex = sides + 1;
+
+ var vertices = new Vector3[sides + 2];
+ for (int i = 0; i < sides; i++)
+ {
+ float angle = (i / (float)sides) * Mathf.PI * 2f;
+ vertices[baseRingStart + i] = new Vector3(
+ Mathf.Cos(angle) * radius,
+ -halfHeight,
+ Mathf.Sin(angle) * radius);
+ }
+ vertices[apexIndex] = new Vector3(0f, halfHeight, 0f);
+ vertices[baseCentreIndex] = new Vector3(0f, -halfHeight, 0f);
+
+ // Side faces (base ring -> apex) + base cap (fan from centre).
+ // Wind both so they face outward / downward respectively.
+ var triangles = new int[sides * 3 * 2];
+ int t = 0;
+ for (int i = 0; i < sides; i++)
+ {
+ int next = (i + 1) % sides;
+
+ // Side triangle, outward-facing.
+ triangles[t++] = baseRingStart + i;
+ triangles[t++] = apexIndex;
+ triangles[t++] = baseRingStart + next;
+
+ // Base cap triangle, downward-facing.
+ triangles[t++] = baseCentreIndex;
+ triangles[t++] = baseRingStart + next;
+ triangles[t++] = baseRingStart + i;
+ }
+
+ target.vertices = vertices;
+ target.triangles = triangles;
+ target.RecalculateNormals();
+ target.RecalculateBounds();
+ }
+ }
+}
diff --git a/Assets/_Project/Scripts/Visuals/ConeMesh.cs.meta b/Assets/_Project/Scripts/Visuals/ConeMesh.cs.meta
new file mode 100644
index 0000000..2db0b9d
--- /dev/null
+++ b/Assets/_Project/Scripts/Visuals/ConeMesh.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 93bc533262394e1e8b122bbfad89391a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: