From 0b20d6fb45e0bd5b46b22d457ac43265a81a8f47 Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:27:04 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E4=BA=A4=E4=BA=92=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BlockInteractionSystem: 方块放置/破坏核心逻辑 - BlockHighlight: 高亮显示瞄准的方块 - SimpleInventory: 简单背包系统,支持9个槽位 - 更新 PlayerController 集成方块交互 - 修改天气快捷键避免与移动键冲突 (S->T, D->N) 操作说明: - 鼠标左键: 破坏方块 - 鼠标右键: 放置方块 - 数字键1-9/滚轮: 切换背包槽位 --- Assets/Scripts/BlockHighlight.cs | 153 +++++++++ Assets/Scripts/BlockInteractionSystem.cs | 406 +++++++++++++++++++++++ Assets/Scripts/PlayerController.cs | 24 +- Assets/Scripts/SimpleInventory.cs | 335 +++++++++++++++++++ 4 files changed, 916 insertions(+), 2 deletions(-) create mode 100644 Assets/Scripts/BlockHighlight.cs create mode 100644 Assets/Scripts/BlockInteractionSystem.cs create mode 100644 Assets/Scripts/SimpleInventory.cs diff --git a/Assets/Scripts/BlockHighlight.cs b/Assets/Scripts/BlockHighlight.cs new file mode 100644 index 00000000..11410ed0 --- /dev/null +++ b/Assets/Scripts/BlockHighlight.cs @@ -0,0 +1,153 @@ +using UnityEngine; + +/// +/// 方块高亮显示 - 显示玩家当前瞄准的方块 +/// +public class BlockHighlight : MonoBehaviour +{ + [Header("高亮设置")] + public Color highlightColor = new Color(1f, 1f, 1f, 0.3f); + public float blockSize = 1f; + public float outlineWidth = 0.02f; + + private GameObject highlightCube; + private Material highlightMaterial; + private LineRenderer[] outlineRenderers; + private bool isVisible = false; + + void Start() + { + CreateHighlightCube(); + CreateOutline(); + SetVisible(false); + } + + /// + /// 创建高亮方块 + /// + void CreateHighlightCube() + { + highlightCube = GameObject.CreatePrimitive(PrimitiveType.Cube); + highlightCube.name = "HighlightCube"; + highlightCube.transform.SetParent(transform); + highlightCube.transform.localScale = Vector3.one * (blockSize + 0.01f); + + // 移除碰撞体 + Collider col = highlightCube.GetComponent(); + if (col != null) Destroy(col); + + // 创建半透明材质 + highlightMaterial = new Material(Shader.Find("Standard")); + highlightMaterial.color = highlightColor; + + // 设置为透明模式 + highlightMaterial.SetFloat("_Mode", 3); + highlightMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + highlightMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + highlightMaterial.SetInt("_ZWrite", 0); + highlightMaterial.DisableKeyword("_ALPHATEST_ON"); + highlightMaterial.EnableKeyword("_ALPHABLEND_ON"); + highlightMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + highlightMaterial.renderQueue = 3000; + + Renderer renderer = highlightCube.GetComponent(); + renderer.material = highlightMaterial; + renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + renderer.receiveShadows = false; + } + + /// + /// 创建边框线 + /// + void CreateOutline() + { + outlineRenderers = new LineRenderer[12]; + + // 定义立方体的12条边 + Vector3[][] edges = new Vector3[][] + { + // 底面4条边 + new Vector3[] { new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0.5f, -0.5f, -0.5f) }, + new Vector3[] { new Vector3(0.5f, -0.5f, -0.5f), new Vector3(0.5f, -0.5f, 0.5f) }, + new Vector3[] { new Vector3(0.5f, -0.5f, 0.5f), new Vector3(-0.5f, -0.5f, 0.5f) }, + new Vector3[] { new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(-0.5f, -0.5f, -0.5f) }, + // 顶面4条边 + new Vector3[] { new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(0.5f, 0.5f, -0.5f) }, + new Vector3[] { new Vector3(0.5f, 0.5f, -0.5f), new Vector3(0.5f, 0.5f, 0.5f) }, + new Vector3[] { new Vector3(0.5f, 0.5f, 0.5f), new Vector3(-0.5f, 0.5f, 0.5f) }, + new Vector3[] { new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(-0.5f, 0.5f, -0.5f) }, + // 垂直4条边 + new Vector3[] { new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(-0.5f, 0.5f, -0.5f) }, + new Vector3[] { new Vector3(0.5f, -0.5f, -0.5f), new Vector3(0.5f, 0.5f, -0.5f) }, + new Vector3[] { new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0.5f) }, + new Vector3[] { new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(-0.5f, 0.5f, 0.5f) } + }; + + Material lineMaterial = new Material(Shader.Find("Sprites/Default")); + lineMaterial.color = Color.black; + + for (int i = 0; i < 12; i++) + { + GameObject lineObj = new GameObject($"OutlineLine_{i}"); + lineObj.transform.SetParent(transform); + + LineRenderer lr = lineObj.AddComponent(); + lr.material = lineMaterial; + lr.startWidth = outlineWidth; + lr.endWidth = outlineWidth; + lr.positionCount = 2; + lr.useWorldSpace = false; + lr.SetPosition(0, edges[i][0] * blockSize); + lr.SetPosition(1, edges[i][1] * blockSize); + lr.startColor = Color.black; + lr.endColor = Color.black; + lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + lr.receiveShadows = false; + + outlineRenderers[i] = lr; + } + } + + /// + /// 设置高亮目标位置 + /// + public void SetTarget(Vector3 position, bool visible) + { + transform.position = position; + SetVisible(visible); + } + + /// + /// 设置可见性 + /// + void SetVisible(bool visible) + { + if (isVisible == visible) return; + + isVisible = visible; + + if (highlightCube != null) + { + highlightCube.SetActive(visible); + } + + if (outlineRenderers != null) + { + foreach (var lr in outlineRenderers) + { + if (lr != null) + { + lr.gameObject.SetActive(visible); + } + } + } + } + + void OnDestroy() + { + if (highlightMaterial != null) + { + Destroy(highlightMaterial); + } + } +} diff --git a/Assets/Scripts/BlockInteractionSystem.cs b/Assets/Scripts/BlockInteractionSystem.cs new file mode 100644 index 00000000..6db3a558 --- /dev/null +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -0,0 +1,406 @@ +using UnityEngine; + +/// +/// 方块交互系统 - 处理方块的放置和破坏 +/// +public class BlockInteractionSystem : MonoBehaviour +{ + [Header("交互设置")] + public float interactionRange = 5f; // 交互距离 + public LayerMask blockLayer; // 方块层级 + public KeyCode destroyKey = KeyCode.Mouse0; // 破坏方块按键(左键) + public KeyCode placeKey = KeyCode.Mouse1; // 放置方块按键(右键) + + [Header("方块设置")] + public GameObject cubePrefab; // 方块预制体 + public float blockSize = 1f; // 方块大小 + + [Header("视觉反馈")] + public bool showBlockHighlight = true; // 是否显示高亮 + public Color highlightColor = new Color(1f, 1f, 1f, 0.3f); + + [Header("音效")] + public AudioClip destroySound; + public AudioClip placeSound; + + private Camera playerCamera; + private BlockHighlight blockHighlight; + private SimpleInventory inventory; + private AudioSource audioSource; + + // 当前瞄准的方块信息 + private GameObject targetBlock; + private Vector3 targetBlockPosition; + private Vector3 placePosition; + private bool hasTarget; + + void Start() + { + InitializeComponents(); + } + + void InitializeComponents() + { + // 获取玩家相机 + playerCamera = GetComponentInChildren(); + if (playerCamera == null) + { + playerCamera = Camera.main; + } + + // 获取或创建背包组件 + inventory = GetComponent(); + if (inventory == null) + { + inventory = gameObject.AddComponent(); + } + + // 获取方块预制体 + if (cubePrefab == null) + { + CubeGenerator cubeGen = FindObjectOfType(); + if (cubeGen != null && cubeGen.cubePrefab != null) + { + cubePrefab = cubeGen.cubePrefab; + } + } + + // 创建高亮显示组件 + if (showBlockHighlight) + { + CreateBlockHighlight(); + } + + // 设置音频源 + audioSource = GetComponent(); + if (audioSource == null) + { + audioSource = gameObject.AddComponent(); + audioSource.spatialBlend = 0f; + audioSource.playOnAwake = false; + } + + // 设置默认层级(如果未设置) + if (blockLayer == 0) + { + blockLayer = ~0; // 所有层级 + } + + Debug.Log("BlockInteractionSystem: 方块交互系统已初始化"); + } + + void CreateBlockHighlight() + { + GameObject highlightObj = new GameObject("BlockHighlight"); + blockHighlight = highlightObj.AddComponent(); + blockHighlight.highlightColor = highlightColor; + blockHighlight.blockSize = blockSize; + } + + void Update() + { + UpdateTargetBlock(); + HandleInput(); + } + + /// + /// 更新当前瞄准的方块 + /// + void UpdateTargetBlock() + { + if (playerCamera == null) return; + + Ray ray = playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)); + RaycastHit hit; + + if (Physics.Raycast(ray, out hit, interactionRange, blockLayer)) + { + hasTarget = true; + targetBlock = hit.collider.gameObject; + + // 计算方块中心位置(对齐到网格) + targetBlockPosition = GetBlockPosition(hit.point - hit.normal * 0.1f); + + // 计算放置位置(在命中面的外侧) + placePosition = GetBlockPosition(hit.point + hit.normal * 0.5f); + + // 更新高亮显示 + if (blockHighlight != null) + { + blockHighlight.SetTarget(targetBlockPosition, true); + } + } + else + { + hasTarget = false; + targetBlock = null; + + if (blockHighlight != null) + { + blockHighlight.SetTarget(Vector3.zero, false); + } + } + } + + /// + /// 处理输入 + /// + void HandleInput() + { + if (!hasTarget) return; + + // 破坏方块 + if (Input.GetKeyDown(destroyKey)) + { + DestroyBlock(); + } + + // 放置方块 + if (Input.GetKeyDown(placeKey)) + { + PlaceBlock(); + } + } + + /// + /// 破坏方块 + /// + void DestroyBlock() + { + if (targetBlock == null) return; + + // 获取方块颜色用于背包 + Renderer renderer = targetBlock.GetComponent(); + Color blockColor = Color.white; + if (renderer != null && renderer.material != null) + { + blockColor = renderer.material.color; + } + + // 添加到背包 + inventory.AddBlock(blockColor); + + // 播放音效 + PlaySound(destroySound); + + // 创建破坏粒子效果 + CreateDestroyEffect(targetBlockPosition, blockColor); + + // 销毁方块 + Destroy(targetBlock); + + Debug.Log($"BlockInteractionSystem: 破坏方块于 {targetBlockPosition}"); + } + + /// + /// 放置方块 + /// + void PlaceBlock() + { + if (cubePrefab == null) + { + Debug.LogWarning("BlockInteractionSystem: 未设置方块预制体"); + return; + } + + // 检查背包是否有方块 + if (!inventory.HasBlocks()) + { + Debug.Log("BlockInteractionSystem: 背包中没有方块"); + return; + } + + // 检查放置位置是否与玩家重叠 + if (IsPositionOccupiedByPlayer(placePosition)) + { + Debug.Log("BlockInteractionSystem: 无法在玩家位置放置方块"); + return; + } + + // 检查位置是否已有方块 + if (IsPositionOccupied(placePosition)) + { + Debug.Log("BlockInteractionSystem: 该位置已有方块"); + return; + } + + // 从背包取出方块 + Color blockColor = inventory.RemoveBlock(); + + // 创建新方块 + GameObject newBlock = Instantiate(cubePrefab, placePosition, Quaternion.identity); + newBlock.name = "PlacedBlock"; + + // 设置颜色 + Renderer renderer = newBlock.GetComponent(); + if (renderer != null) + { + Material mat = new Material(Shader.Find("Standard")); + mat.color = blockColor; + renderer.material = mat; + } + + // 确保有碰撞体 + if (newBlock.GetComponent() == null) + { + newBlock.AddComponent(); + } + + // 播放音效 + PlaySound(placeSound); + + // 创建放置粒子效果 + CreatePlaceEffect(placePosition, blockColor); + + Debug.Log($"BlockInteractionSystem: 放置方块于 {placePosition}"); + } + + + /// + /// 将世界坐标对齐到方块网格 + /// + Vector3 GetBlockPosition(Vector3 worldPos) + { + return new Vector3( + Mathf.Round(worldPos.x / blockSize) * blockSize, + Mathf.Round(worldPos.y / blockSize) * blockSize, + Mathf.Round(worldPos.z / blockSize) * blockSize + ); + } + + /// + /// 检查位置是否被玩家占用 + /// + bool IsPositionOccupiedByPlayer(Vector3 position) + { + Vector3 playerPos = transform.position; + float playerHeight = 2f; + + // 检查方块是否与玩家碰撞盒重叠 + if (Mathf.Abs(position.x - playerPos.x) < blockSize && + position.y >= playerPos.y - 0.5f && position.y <= playerPos.y + playerHeight && + Mathf.Abs(position.z - playerPos.z) < blockSize) + { + return true; + } + + return false; + } + + /// + /// 检查位置是否已有方块 + /// + bool IsPositionOccupied(Vector3 position) + { + Collider[] colliders = Physics.OverlapSphere(position, blockSize * 0.4f, blockLayer); + return colliders.Length > 0; + } + + /// + /// 播放音效 + /// + void PlaySound(AudioClip clip) + { + if (clip != null && audioSource != null) + { + audioSource.PlayOneShot(clip); + } + } + + /// + /// 创建破坏粒子效果 + /// + void CreateDestroyEffect(Vector3 position, Color color) + { + // 创建简单的粒子效果 + GameObject effectObj = new GameObject("DestroyEffect"); + effectObj.transform.position = position; + + ParticleSystem ps = effectObj.AddComponent(); + var main = ps.main; + main.startLifetime = 0.5f; + main.startSpeed = 3f; + main.startSize = 0.15f; + main.startColor = color; + main.gravityModifier = 1f; + main.maxParticles = 20; + main.duration = 0.1f; + main.loop = false; + + var emission = ps.emission; + emission.rateOverTime = 0; + emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 15) }); + + var shape = ps.shape; + shape.shapeType = ParticleSystemShapeType.Sphere; + shape.radius = 0.3f; + + var renderer = ps.GetComponent(); + renderer.material = new Material(Shader.Find("Particles/Standard Unlit")); + renderer.material.color = color; + + ps.Play(); + + Destroy(effectObj, 1f); + } + + /// + /// 创建放置粒子效果 + /// + void CreatePlaceEffect(Vector3 position, Color color) + { + GameObject effectObj = new GameObject("PlaceEffect"); + effectObj.transform.position = position; + + ParticleSystem ps = effectObj.AddComponent(); + var main = ps.main; + main.startLifetime = 0.3f; + main.startSpeed = 1f; + main.startSize = 0.1f; + main.startColor = new Color(color.r, color.g, color.b, 0.5f); + main.gravityModifier = 0f; + main.maxParticles = 10; + main.duration = 0.1f; + main.loop = false; + + var emission = ps.emission; + emission.rateOverTime = 0; + emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 8) }); + + var shape = ps.shape; + shape.shapeType = ParticleSystemShapeType.Box; + shape.scale = Vector3.one * blockSize; + + var renderer = ps.GetComponent(); + renderer.material = new Material(Shader.Find("Particles/Standard Unlit")); + renderer.material.color = color; + + ps.Play(); + + Destroy(effectObj, 0.5f); + } + + /// + /// 获取当前瞄准的方块位置 + /// + public Vector3 GetTargetBlockPosition() + { + return targetBlockPosition; + } + + /// + /// 获取当前放置位置 + /// + public Vector3 GetPlacePosition() + { + return placePosition; + } + + /// + /// 是否有瞄准目标 + /// + public bool HasTarget() + { + return hasTarget; + } +} diff --git a/Assets/Scripts/PlayerController.cs b/Assets/Scripts/PlayerController.cs index c384f4f3..cba2a467 100644 --- a/Assets/Scripts/PlayerController.cs +++ b/Assets/Scripts/PlayerController.cs @@ -15,12 +15,16 @@ public class PlayerController : MonoBehaviour [Header("天气控制")] public KeyCode toggleRainKey = KeyCode.R; // 按R键切换雨的状态 - public KeyCode toggleSnowKey = KeyCode.S; // 按S键切换雪的状态 - public KeyCode toggleDayNightKey = KeyCode.D; // 按D键切换日夜状态 + public KeyCode toggleSnowKey = KeyCode.T; // 按T键切换雪的状态(改为T避免与移动冲突) + public KeyCode toggleDayNightKey = KeyCode.N; // 按N键切换日夜状态(改为N避免与移动冲突) public KeyCode triggerLightningKey = KeyCode.L; // 按L键触发闪电 + [Header("方块交互")] + public bool enableBlockInteraction = true; // 是否启用方块交互系统 + private CharacterController characterController; private Camera playerCamera; + private BlockInteractionSystem blockInteractionSystem; private float rotationX = 0; private Vector3 moveDirection = Vector3.zero; private bool isGrounded; @@ -42,6 +46,12 @@ void Start() return; } + // 初始化方块交互系统 + if (enableBlockInteraction) + { + InitializeBlockInteraction(); + } + // 锁定并隐藏光标 Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; @@ -49,6 +59,16 @@ void Start() Debug.Log("玩家控制器已初始化"); } + void InitializeBlockInteraction() + { + blockInteractionSystem = GetComponent(); + if (blockInteractionSystem == null) + { + blockInteractionSystem = gameObject.AddComponent(); + Debug.Log("PlayerController: 已添加方块交互系统"); + } + } + void Update() { // 检查是否在地面上 diff --git a/Assets/Scripts/SimpleInventory.cs b/Assets/Scripts/SimpleInventory.cs new file mode 100644 index 00000000..bfa3d28a --- /dev/null +++ b/Assets/Scripts/SimpleInventory.cs @@ -0,0 +1,335 @@ +using UnityEngine; +using System.Collections.Generic; + +/// +/// 简单背包系统 - 存储收集的方块 +/// +public class SimpleInventory : MonoBehaviour +{ + [Header("背包设置")] + public int maxSlots = 9; // 最大槽位数 + public int maxStackSize = 64; // 每个槽位最大堆叠数 + public int selectedSlot = 0; // 当前选中的槽位 + + [Header("初始物品")] + public int initialBlockCount = 20; // 初始方块数量 + public Color initialBlockColor = new Color(0.6f, 0.4f, 0.2f); // 初始方块颜色(泥土色) + + [Header("UI设置")] + public bool showInventoryUI = true; + public KeyCode[] slotKeys = new KeyCode[] + { + KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, + KeyCode.Alpha4, KeyCode.Alpha5, KeyCode.Alpha6, + KeyCode.Alpha7, KeyCode.Alpha8, KeyCode.Alpha9 + }; + + // 背包槽位 + private List slots = new List(); + + // UI相关 + private GUIStyle slotStyle; + private GUIStyle selectedSlotStyle; + private GUIStyle countStyle; + private bool stylesInitialized = false; + + void Start() + { + InitializeInventory(); + } + + void InitializeInventory() + { + // 初始化槽位 + slots.Clear(); + for (int i = 0; i < maxSlots; i++) + { + slots.Add(new InventorySlot()); + } + + // 添加初始方块 + if (initialBlockCount > 0) + { + AddBlock(initialBlockColor, initialBlockCount); + } + + // 添加一些不同颜色的方块作为初始物品 + AddBlock(new Color(0.4f, 0.7f, 0.2f), 10); // 草地色 + AddBlock(new Color(0.5f, 0.5f, 0.5f), 10); // 石头色 + AddBlock(new Color(0.9f, 0.8f, 0.5f), 5); // 沙子色 + + Debug.Log("SimpleInventory: 背包已初始化"); + } + + void Update() + { + HandleSlotSelection(); + HandleScrollWheel(); + } + + /// + /// 处理数字键选择槽位 + /// + void HandleSlotSelection() + { + for (int i = 0; i < slotKeys.Length && i < maxSlots; i++) + { + if (Input.GetKeyDown(slotKeys[i])) + { + selectedSlot = i; + } + } + } + + /// + /// 处理滚轮切换槽位 + /// + void HandleScrollWheel() + { + float scroll = Input.GetAxis("Mouse ScrollWheel"); + if (scroll != 0) + { + if (scroll > 0) + { + selectedSlot--; + if (selectedSlot < 0) selectedSlot = maxSlots - 1; + } + else + { + selectedSlot++; + if (selectedSlot >= maxSlots) selectedSlot = 0; + } + } + } + + /// + /// 添加方块到背包 + /// + public bool AddBlock(Color color, int count = 1) + { + // 首先尝试堆叠到已有的相同颜色槽位 + for (int i = 0; i < slots.Count; i++) + { + if (!slots[i].isEmpty && ColorEquals(slots[i].blockColor, color)) + { + int spaceLeft = maxStackSize - slots[i].count; + if (spaceLeft > 0) + { + int toAdd = Mathf.Min(count, spaceLeft); + slots[i].count += toAdd; + count -= toAdd; + + if (count <= 0) return true; + } + } + } + + // 如果还有剩余,放入空槽位 + while (count > 0) + { + int emptySlot = FindEmptySlot(); + if (emptySlot == -1) + { + Debug.Log("SimpleInventory: 背包已满"); + return false; + } + + int toAdd = Mathf.Min(count, maxStackSize); + slots[emptySlot].blockColor = color; + slots[emptySlot].count = toAdd; + slots[emptySlot].isEmpty = false; + count -= toAdd; + } + + return true; + } + + /// + /// 从当前选中槽位移除一个方块 + /// + public Color RemoveBlock() + { + if (selectedSlot < 0 || selectedSlot >= slots.Count) + return Color.white; + + InventorySlot slot = slots[selectedSlot]; + if (slot.isEmpty || slot.count <= 0) + return Color.white; + + Color color = slot.blockColor; + slot.count--; + + if (slot.count <= 0) + { + slot.isEmpty = true; + slot.blockColor = Color.white; + } + + return color; + } + + /// + /// 检查当前槽位是否有方块 + /// + public bool HasBlocks() + { + if (selectedSlot < 0 || selectedSlot >= slots.Count) + return false; + + return !slots[selectedSlot].isEmpty && slots[selectedSlot].count > 0; + } + + /// + /// 获取当前选中槽位的方块颜色 + /// + public Color GetSelectedBlockColor() + { + if (selectedSlot < 0 || selectedSlot >= slots.Count) + return Color.white; + + return slots[selectedSlot].blockColor; + } + + /// + /// 获取当前选中槽位的方块数量 + /// + public int GetSelectedBlockCount() + { + if (selectedSlot < 0 || selectedSlot >= slots.Count) + return 0; + + return slots[selectedSlot].count; + } + + /// + /// 查找空槽位 + /// + int FindEmptySlot() + { + for (int i = 0; i < slots.Count; i++) + { + if (slots[i].isEmpty) + return i; + } + return -1; + } + + /// + /// 比较两个颜色是否相等(允许小误差) + /// + bool ColorEquals(Color a, Color b) + { + float threshold = 0.01f; + return Mathf.Abs(a.r - b.r) < threshold && + Mathf.Abs(a.g - b.g) < threshold && + Mathf.Abs(a.b - b.b) < threshold; + } + + void OnGUI() + { + if (!showInventoryUI) return; + + InitStyles(); + DrawHotbar(); + } + + void InitStyles() + { + if (stylesInitialized) return; + + slotStyle = new GUIStyle(GUI.skin.box); + slotStyle.normal.background = MakeTexture(2, 2, new Color(0.2f, 0.2f, 0.2f, 0.8f)); + slotStyle.alignment = TextAnchor.MiddleCenter; + + selectedSlotStyle = new GUIStyle(GUI.skin.box); + selectedSlotStyle.normal.background = MakeTexture(2, 2, new Color(0.4f, 0.4f, 0.4f, 0.9f)); + selectedSlotStyle.alignment = TextAnchor.MiddleCenter; + + countStyle = new GUIStyle(GUI.skin.label); + countStyle.fontSize = 12; + countStyle.fontStyle = FontStyle.Bold; + countStyle.normal.textColor = Color.white; + countStyle.alignment = TextAnchor.LowerRight; + + stylesInitialized = true; + } + + void DrawHotbar() + { + float slotSize = 50f; + float padding = 5f; + float totalWidth = maxSlots * (slotSize + padding) - padding; + float startX = (Screen.width - totalWidth) / 2; + float startY = Screen.height - slotSize - 20f; + + for (int i = 0; i < maxSlots; i++) + { + float x = startX + i * (slotSize + padding); + Rect slotRect = new Rect(x, startY, slotSize, slotSize); + + // 绘制槽位背景 + GUIStyle style = (i == selectedSlot) ? selectedSlotStyle : slotStyle; + GUI.Box(slotRect, "", style); + + // 绘制方块颜色预览 + if (!slots[i].isEmpty && slots[i].count > 0) + { + Rect colorRect = new Rect(x + 5, startY + 5, slotSize - 10, slotSize - 10); + Texture2D colorTex = MakeTexture(1, 1, slots[i].blockColor); + GUI.DrawTexture(colorRect, colorTex); + + // 绘制数量 + Rect countRect = new Rect(x, startY, slotSize - 3, slotSize - 3); + GUI.Label(countRect, slots[i].count.ToString(), countStyle); + } + + // 绘制槽位编号 + Rect numRect = new Rect(x + 3, startY + 2, 15, 15); + GUI.Label(numRect, (i + 1).ToString()); + } + + // 绘制准星 + DrawCrosshair(); + } + + void DrawCrosshair() + { + float size = 20f; + float thickness = 2f; + float centerX = Screen.width / 2; + float centerY = Screen.height / 2; + + Color crosshairColor = new Color(1f, 1f, 1f, 0.8f); + Texture2D tex = MakeTexture(1, 1, crosshairColor); + + // 水平线 + GUI.DrawTexture(new Rect(centerX - size / 2, centerY - thickness / 2, size, thickness), tex); + // 垂直线 + GUI.DrawTexture(new Rect(centerX - thickness / 2, centerY - size / 2, thickness, size), tex); + } + + Texture2D MakeTexture(int width, int height, Color color) + { + Color[] pixels = new Color[width * height]; + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = color; + } + + Texture2D tex = new Texture2D(width, height); + tex.SetPixels(pixels); + tex.Apply(); + return tex; + } + + /// + /// 背包槽位数据 + /// + [System.Serializable] + public class InventorySlot + { + public Color blockColor = Color.white; + public int count = 0; + public bool isEmpty = true; + } +} From aa8a058ac65c9ebdf13e366c05072cd08d580919 Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:34:56 +0800 Subject: [PATCH 2/9] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B9=E5=9D=97=E4=BA=A4=E4=BA=92=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=AF=B4=E6=98=8E=E5=92=8C=E5=AE=8C=E6=95=B4=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E9=94=AE=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/BlockHighlight.cs.meta | 11 ++++++++ Assets/Scripts/BlockInteractionSystem.cs.meta | 11 ++++++++ Assets/Scripts/SimpleInventory.cs | 26 ++++++++++++++----- Assets/Scripts/SimpleInventory.cs.meta | 11 ++++++++ README.md | 9 +++++++ README_CN.md | 9 +++++++ 6 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 Assets/Scripts/BlockHighlight.cs.meta create mode 100644 Assets/Scripts/BlockInteractionSystem.cs.meta create mode 100644 Assets/Scripts/SimpleInventory.cs.meta diff --git a/Assets/Scripts/BlockHighlight.cs.meta b/Assets/Scripts/BlockHighlight.cs.meta new file mode 100644 index 00000000..a85651c4 --- /dev/null +++ b/Assets/Scripts/BlockHighlight.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: WXga4XusUS9NkPRLYr6r56vUhq1eAqjk4OgBdlzKB1wWIJoqhq9isBo= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/BlockInteractionSystem.cs.meta b/Assets/Scripts/BlockInteractionSystem.cs.meta new file mode 100644 index 00000000..b3533df8 --- /dev/null +++ b/Assets/Scripts/BlockInteractionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: CygY5yKvVHOca7N4chio0HPIloVwyUQqxEIXZhgUJ74X403WvSwSVms= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/SimpleInventory.cs b/Assets/Scripts/SimpleInventory.cs index bfa3d28a..01eb1f86 100644 --- a/Assets/Scripts/SimpleInventory.cs +++ b/Assets/Scripts/SimpleInventory.cs @@ -31,6 +31,7 @@ public class SimpleInventory : MonoBehaviour private GUIStyle slotStyle; private GUIStyle selectedSlotStyle; private GUIStyle countStyle; + private GUIStyle slotNumberStyle; private bool stylesInitialized = false; void Start() @@ -242,15 +243,21 @@ void InitStyles() slotStyle.alignment = TextAnchor.MiddleCenter; selectedSlotStyle = new GUIStyle(GUI.skin.box); - selectedSlotStyle.normal.background = MakeTexture(2, 2, new Color(0.4f, 0.4f, 0.4f, 0.9f)); + selectedSlotStyle.normal.background = MakeTexture(2, 2, new Color(0.5f, 0.5f, 0.5f, 0.95f)); selectedSlotStyle.alignment = TextAnchor.MiddleCenter; countStyle = new GUIStyle(GUI.skin.label); - countStyle.fontSize = 12; + countStyle.fontSize = 14; countStyle.fontStyle = FontStyle.Bold; countStyle.normal.textColor = Color.white; countStyle.alignment = TextAnchor.LowerRight; + slotNumberStyle = new GUIStyle(GUI.skin.label); + slotNumberStyle.fontSize = 14; + slotNumberStyle.fontStyle = FontStyle.Bold; + slotNumberStyle.normal.textColor = Color.white; + slotNumberStyle.alignment = TextAnchor.MiddleCenter; + stylesInitialized = true; } @@ -278,14 +285,21 @@ void DrawHotbar() Texture2D colorTex = MakeTexture(1, 1, slots[i].blockColor); GUI.DrawTexture(colorRect, colorTex); - // 绘制数量 + // 绘制数量(右下角,带阴影) + Rect countShadowRect = new Rect(x + 1, startY + 1, slotSize - 3, slotSize - 3); + GUI.color = Color.black; + GUI.Label(countShadowRect, slots[i].count.ToString(), countStyle); + GUI.color = Color.white; + Rect countRect = new Rect(x, startY, slotSize - 3, slotSize - 3); GUI.Label(countRect, slots[i].count.ToString(), countStyle); } - // 绘制槽位编号 - Rect numRect = new Rect(x + 3, startY + 2, 15, 15); - GUI.Label(numRect, (i + 1).ToString()); + // 绘制槽位编号(槽位上方) + Rect numRect = new Rect(x, startY - 18, slotSize, 16); + GUI.color = (i == selectedSlot) ? Color.yellow : Color.white; + GUI.Label(numRect, (i + 1).ToString(), slotNumberStyle); + GUI.color = Color.white; } // 绘制准星 diff --git a/Assets/Scripts/SimpleInventory.cs.meta b/Assets/Scripts/SimpleInventory.cs.meta new file mode 100644 index 00000000..dbad3d9a --- /dev/null +++ b/Assets/Scripts/SimpleInventory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: DHMasnupVC5vbnPVa1bmjOl5I6WYmxGSpr1kZEeZ4gNtOkUYq6qrFlA= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 529b4a54..e5c262a9 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ This solution lowers the barrier to game programming skills, enabling every team ### 🎮 Core Gameplay - **First-Person Perspective Control**: Smooth player movement and camera control - **Cube World Generation**: Procedurally generated cube terrain +- **Block Interaction System**: Place and destroy blocks to build your world +- **Inventory System**: 9-slot hotbar with block stacking support - **Physics Interaction**: Realistic physics collision and gravity system - **Interactive Animals**: Cute animals roaming the world, including rabbits, sheep, and chickens with natural behaviors - **Ferris Wheel**: A rotating Ferris wheel landmark that adds life to the cube world @@ -100,6 +102,13 @@ Refer to https://github.com/CoderGamester/mcp-unity/blob/main/README.md for MCP - **WASD** - Movement - **Mouse** - Camera control - **Space** - Jump +- **Left Click** - Destroy block +- **Right Click** - Place block +- **1-9 / Scroll Wheel** - Select hotbar slot +- **R** - Toggle rain +- **T** - Toggle snow +- **N** - Toggle day/night +- **L** - Trigger lightning --- diff --git a/README_CN.md b/README_CN.md index e8d175e8..e3e62380 100644 --- a/README_CN.md +++ b/README_CN.md @@ -34,6 +34,8 @@ CubeVerse 是一个通过 AI 辅助开发工具(如 Kiro)结合自然语言 ### 🎮 核心玩法 - **第一人称视角控制**:流畅的玩家移动和视角控制 - **立方体世界生成**:程序化生成的立方体地形 +- **方块交互系统**:放置和破坏方块,建造你的世界 +- **背包系统**:9格快捷栏,支持方块堆叠 - **物理交互**:真实的物理碰撞和重力系统 - **互动小动物**:可爱的小动物在世界中漫游,包括兔子、绵羊和小鸡,具有自然的行为表现 - **摩天轮**:旋转的摩天轮地标建筑,为立方体世界增添生机 @@ -100,6 +102,13 @@ CubeVerse 是一个通过 AI 辅助开发工具(如 Kiro)结合自然语言 - **WASD** - 移动 - **鼠标** - 视角控制 - **空格** - 跳跃 +- **鼠标左键** - 破坏方块 +- **鼠标右键** - 放置方块 +- **数字键1-9 / 滚轮** - 切换快捷栏槽位 +- **R** - 切换雨天 +- **T** - 切换雪天 +- **N** - 切换日夜 +- **L** - 触发闪电 --- From e3d79456dcafa6674a53b93818a2d130d745119e Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:36:34 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=E6=94=B9=E4=B8=BA=E9=95=BF?= =?UTF-8?q?=E6=8C=89=E7=A0=B4=E5=9D=8F=E6=96=B9=E5=9D=97=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=A0=B4=E5=9D=8F=E8=BF=9B=E5=BA=A6=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 需要长按左键0.5秒才能破坏方块 - 方块会显示红色进度覆盖层 - 切换目标或松开按键会重置进度 - 防止误触破坏方块 --- Assets/Scripts/BlockHighlight.cs | 69 ++++++++++++++++++++++++ Assets/Scripts/BlockInteractionSystem.cs | 52 +++++++++++++++--- 2 files changed, 114 insertions(+), 7 deletions(-) diff --git a/Assets/Scripts/BlockHighlight.cs b/Assets/Scripts/BlockHighlight.cs index 11410ed0..07f11f9d 100644 --- a/Assets/Scripts/BlockHighlight.cs +++ b/Assets/Scripts/BlockHighlight.cs @@ -15,10 +15,16 @@ public class BlockHighlight : MonoBehaviour private LineRenderer[] outlineRenderers; private bool isVisible = false; + // 破坏进度相关 + private float destroyProgress = 0f; + private GameObject progressOverlay; + private Material progressMaterial; + void Start() { CreateHighlightCube(); CreateOutline(); + CreateProgressOverlay(); SetVisible(false); } @@ -149,5 +155,68 @@ void OnDestroy() { Destroy(highlightMaterial); } + if (progressMaterial != null) + { + Destroy(progressMaterial); + } + } + + /// + /// 创建破坏进度覆盖层 + /// + void CreateProgressOverlay() + { + progressOverlay = GameObject.CreatePrimitive(PrimitiveType.Cube); + progressOverlay.name = "ProgressOverlay"; + progressOverlay.transform.SetParent(transform); + progressOverlay.transform.localScale = Vector3.one * (blockSize + 0.02f); + progressOverlay.transform.localPosition = Vector3.zero; + + // 移除碰撞体 + Collider col = progressOverlay.GetComponent(); + if (col != null) Destroy(col); + + // 创建破坏进度材质(红色半透明) + progressMaterial = new Material(Shader.Find("Standard")); + progressMaterial.color = new Color(1f, 0f, 0f, 0f); + + // 设置为透明模式 + progressMaterial.SetFloat("_Mode", 3); + progressMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + progressMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + progressMaterial.SetInt("_ZWrite", 0); + progressMaterial.DisableKeyword("_ALPHATEST_ON"); + progressMaterial.EnableKeyword("_ALPHABLEND_ON"); + progressMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + progressMaterial.renderQueue = 3001; + + Renderer renderer = progressOverlay.GetComponent(); + renderer.material = progressMaterial; + renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + renderer.receiveShadows = false; + + progressOverlay.SetActive(false); + } + + /// + /// 设置破坏进度 (0-1) + /// + public void SetDestroyProgress(float progress) + { + destroyProgress = Mathf.Clamp01(progress); + + if (progressOverlay != null && progressMaterial != null) + { + if (destroyProgress > 0) + { + progressOverlay.SetActive(true); + // 进度越高,红色越深 + progressMaterial.color = new Color(1f, 0.2f, 0.2f, destroyProgress * 0.6f); + } + else + { + progressOverlay.SetActive(false); + } + } } } diff --git a/Assets/Scripts/BlockInteractionSystem.cs b/Assets/Scripts/BlockInteractionSystem.cs index 6db3a558..2a4ee968 100644 --- a/Assets/Scripts/BlockInteractionSystem.cs +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -10,6 +10,7 @@ public class BlockInteractionSystem : MonoBehaviour public LayerMask blockLayer; // 方块层级 public KeyCode destroyKey = KeyCode.Mouse0; // 破坏方块按键(左键) public KeyCode placeKey = KeyCode.Mouse1; // 放置方块按键(右键) + public float destroyTime = 0.5f; // 破坏所需时间(秒) [Header("方块设置")] public GameObject cubePrefab; // 方块预制体 @@ -34,6 +35,11 @@ public class BlockInteractionSystem : MonoBehaviour private Vector3 placePosition; private bool hasTarget; + // 破坏进度 + private float destroyProgress = 0f; + private Vector3 lastDestroyPosition; + private bool isDestroying = false; + void Start() { InitializeComponents(); @@ -147,16 +153,48 @@ void UpdateTargetBlock() /// void HandleInput() { - if (!hasTarget) return; - - // 破坏方块 - if (Input.GetKeyDown(destroyKey)) + // 破坏方块(长按) + if (Input.GetKey(destroyKey) && hasTarget) { - DestroyBlock(); + // 检查是否切换了目标方块 + if (targetBlockPosition != lastDestroyPosition) + { + destroyProgress = 0f; + lastDestroyPosition = targetBlockPosition; + } + + isDestroying = true; + destroyProgress += Time.deltaTime; + + // 更新高亮显示破坏进度 + if (blockHighlight != null) + { + blockHighlight.SetDestroyProgress(destroyProgress / destroyTime); + } + + // 破坏完成 + if (destroyProgress >= destroyTime) + { + DestroyBlock(); + destroyProgress = 0f; + } + } + else + { + // 松开按键,重置进度 + if (isDestroying) + { + isDestroying = false; + destroyProgress = 0f; + if (blockHighlight != null) + { + blockHighlight.SetDestroyProgress(0f); + } + } } - // 放置方块 - if (Input.GetKeyDown(placeKey)) + // 放置方块(单击) + if (Input.GetKeyDown(placeKey) && hasTarget) { PlaceBlock(); } From 0f1f7c8bbc7b7c5f3658f1d58510fd0734a5859b Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:38:52 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9C=B0?= =?UTF-8?q?=E5=BD=A2=E5=8E=9A=E5=BA=A6=E4=B8=BA4=E5=B1=82=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E6=8C=96=E7=A9=BF=E6=8E=89=E8=90=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 表面层:地形颜色(草地/沙子等) - 第2层:泥土 - 第3-4层:石头 - 可通过 terrainDepth 参数调整 --- Assets/Scripts/CubeGenerator.cs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 7a8dc39d..8105f7ff 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -12,6 +12,7 @@ public class CubeGenerator : MonoBehaviour public float noiseScale = 20f; public float heightScale = 10f; public int seed; + public int terrainDepth = 4; // 地形厚度(层数) [Header("颜色设置")] public Color grassColor = new Color(0.4f, 0.7f, 0.2f); @@ -208,8 +209,31 @@ void GenerateChunk(Vector2Int chunkPos, Transform parent) float height = GenerateHeight(worldX, worldZ); int intHeight = Mathf.FloorToInt(height); - // 生成地面方块 - CreateCube(new Vector3(worldX, intHeight, worldZ), GetTerrainColor(intHeight, height), parent); + // 生成多层地形方块 + for (int depth = 0; depth < terrainDepth; depth++) + { + int y = intHeight - depth; + if (y < 0) break; // 不生成负高度的方块 + + Color blockColor; + if (depth == 0) + { + // 表面层使用地形颜色 + blockColor = GetTerrainColor(intHeight, height); + } + else if (depth < 2) + { + // 第2层是泥土 + blockColor = dirtColor; + } + else + { + // 更深层是石头 + blockColor = stoneColor; + } + + CreateCube(new Vector3(worldX, y, worldZ), blockColor, parent); + } // 生成水面 int waterLevel = 3; From a5b58bebe5a6559c31cb1b0b4b2b2bed6007f6d6 Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:46:59 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=9C=B0?= =?UTF-8?q?=E5=BD=A2=E7=94=9F=E6=88=90=20-=20=E5=8F=AA=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E6=96=B9=E5=9D=97=20+=20=E7=A0=B4=E5=9D=8F?= =?UTF-8?q?=E6=97=B6=E5=8A=A8=E6=80=81=E7=94=9F=E6=88=90=E4=B8=8B=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 地形生成优化:只生成暴露在外的方块,大幅减少方块数量 - 破坏方块时自动生成下方隐藏的方块 - 支持 terrainDepth=100 等大深度值而不卡顿 --- Assets/Scenes/SampleScene.scene | 4 +- Assets/Scripts/BlockInteractionSystem.cs | 122 +++++++++++++++++++++++ Assets/Scripts/CubeGenerator.cs | 45 ++++++--- 3 files changed, 156 insertions(+), 15 deletions(-) diff --git a/Assets/Scenes/SampleScene.scene b/Assets/Scenes/SampleScene.scene index 5b96e1f8..5b50fe6c 100644 --- a/Assets/Scenes/SampleScene.scene +++ b/Assets/Scenes/SampleScene.scene @@ -356,9 +356,6 @@ MonoBehaviour: cabinCount: 6 wheelRadius: 30 rotationSpeed: 8 - exitKey: 101 - enterKey: 101 - interactionDistance: 20 frameColor: {r: 0.7, g: 0.7, b: 0.7, a: 1} wheelColor: {r: 0.3, g: 0.6, b: 1, a: 1} accentColor: {r: 1, g: 0.8, b: 0.2, a: 1} @@ -683,6 +680,7 @@ MonoBehaviour: noiseScale: 20 heightScale: 10 seed: 0 + terrainDepth: 100 grassColor: {r: 0.4, g: 0.7, b: 0.2, a: 1} dirtColor: {r: 0.6, g: 0.4, b: 0.2, a: 1} stoneColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} diff --git a/Assets/Scripts/BlockInteractionSystem.cs b/Assets/Scripts/BlockInteractionSystem.cs index 2a4ee968..58e7cd37 100644 --- a/Assets/Scripts/BlockInteractionSystem.cs +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -215,6 +215,9 @@ void DestroyBlock() blockColor = renderer.material.color; } + // 记录被破坏方块的位置 + Vector3 destroyedPos = targetBlockPosition; + // 添加到背包 inventory.AddBlock(blockColor); @@ -227,9 +230,128 @@ void DestroyBlock() // 销毁方块 Destroy(targetBlock); + // 检查并生成下方隐藏的方块 + RevealHiddenBlocks(destroyedPos); + Debug.Log($"BlockInteractionSystem: 破坏方块于 {targetBlockPosition}"); } + /// + /// 破坏方块后,检查并生成周围被隐藏的方块 + /// + void RevealHiddenBlocks(Vector3 destroyedPos) + { + // 延迟一帧执行,确保方块已被销毁 + StartCoroutine(RevealHiddenBlocksDelayed(destroyedPos)); + } + + System.Collections.IEnumerator RevealHiddenBlocksDelayed(Vector3 destroyedPos) + { + yield return null; // 等待一帧 + + CubeGenerator cubeGen = FindObjectOfType(); + if (cubeGen == null || cubeGen.cubePrefab == null) yield break; + + int worldX = Mathf.RoundToInt(destroyedPos.x); + int worldZ = Mathf.RoundToInt(destroyedPos.z); + int worldY = Mathf.RoundToInt(destroyedPos.y); + + // 计算该位置的地形高度 + float surfaceHeight = GetTerrainHeight(worldX, worldZ, cubeGen); + int intSurfaceHeight = Mathf.FloorToInt(surfaceHeight); + + // 检查下方位置 + Vector3 belowPos = new Vector3(worldX, worldY - 1, worldZ); + int belowY = worldY - 1; + + if (belowY >= 0) + { + int depth = intSurfaceHeight - belowY; + + // 如果在地形深度范围内,直接生成(不检测是否占用,因为优化后下方本来就没有方块) + if (depth >= 0 && depth < cubeGen.terrainDepth) + { + // 用更大的检测范围确认下方确实没有方块 + Collider[] colliders = Physics.OverlapBox(belowPos, Vector3.one * 0.4f); + if (colliders.Length == 0) + { + Color blockColor = GetBlockColorForDepth(depth, intSurfaceHeight, cubeGen); + CreateRevealedBlock(belowPos, blockColor); + Debug.Log($"BlockInteractionSystem: 生成隐藏方块于 {belowPos}, 深度={depth}"); + } + } + } + } + + /// + /// 获取地形高度 + /// + float GetTerrainHeight(int x, int z, CubeGenerator cubeGen) + { + float scale = cubeGen.noiseScale; + int seed = cubeGen.seed; + float heightScale = cubeGen.heightScale; + + float height = Mathf.PerlinNoise((x + seed) / scale, (z + seed) / scale) * heightScale; + height += Mathf.PerlinNoise((x + seed) / (scale * 0.5f), (z + seed) / (scale * 0.5f)) * 2; + + return height; + } + + /// + /// 根据深度获取方块颜色 + /// + Color GetBlockColorForDepth(int depth, int surfaceHeight, CubeGenerator cubeGen) + { + if (depth == 0) + { + // 表面层 + int waterLevel = 3; + if (surfaceHeight < waterLevel - 1) + return cubeGen.stoneColor; + else if (surfaceHeight < waterLevel) + return cubeGen.sandColor; + else if (surfaceHeight < 8) + return cubeGen.grassColor; + else if (surfaceHeight < 12) + return cubeGen.dirtColor; + else + return cubeGen.stoneColor; + } + else if (depth < 3) + { + return cubeGen.dirtColor; + } + else + { + return cubeGen.stoneColor; + } + } + + /// + /// 创建被揭露的方块 + /// + void CreateRevealedBlock(Vector3 position, Color color) + { + if (cubePrefab == null) return; + + GameObject newBlock = Instantiate(cubePrefab, position, Quaternion.identity); + newBlock.name = "RevealedBlock"; + + Renderer renderer = newBlock.GetComponent(); + if (renderer != null) + { + Material mat = new Material(Shader.Find("Standard")); + mat.color = color; + renderer.material = mat; + } + + if (newBlock.GetComponent() == null) + { + newBlock.AddComponent(); + } + } + /// /// 放置方块 /// diff --git a/Assets/Scripts/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 8105f7ff..965d79ca 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -12,7 +12,7 @@ public class CubeGenerator : MonoBehaviour public float noiseScale = 20f; public float heightScale = 10f; public int seed; - public int terrainDepth = 4; // 地形厚度(层数) + public int terrainDepth = 100; // 地形厚度(层数) [Header("颜色设置")] public Color grassColor = new Color(0.4f, 0.7f, 0.2f); @@ -197,6 +197,19 @@ void GenerateChunk(Vector2Int chunkPos, Transform parent) int startX = chunkPos.x * chunkSize; int startZ = chunkPos.y * chunkSize; + // 先计算整个区块的高度图 + int[,] heightMap = new int[chunkSize + 2, chunkSize + 2]; // +2 用于边界检测 + for (int x = -1; x <= chunkSize; x++) + { + for (int z = -1; z <= chunkSize; z++) + { + int worldX = startX + x; + int worldZ = startZ + z; + float height = GenerateHeight(worldX, worldZ); + heightMap[x + 1, z + 1] = Mathf.FloorToInt(height); + } + } + // 生成地形 for (int x = 0; x < chunkSize; x++) { @@ -204,31 +217,39 @@ void GenerateChunk(Vector2Int chunkPos, Transform parent) { int worldX = startX + x; int worldZ = startZ + z; + int intHeight = heightMap[x + 1, z + 1]; - // 使用柏林噪声生成高度 - float height = GenerateHeight(worldX, worldZ); - int intHeight = Mathf.FloorToInt(height); + // 获取相邻位置的高度 + int heightN = heightMap[x + 1, z + 2]; // 北 + int heightS = heightMap[x + 1, z]; // 南 + int heightE = heightMap[x + 2, z + 1]; // 东 + int heightW = heightMap[x, z + 1]; // 西 + + // 计算需要暴露的最低高度(相邻最低高度) + int minNeighborHeight = Mathf.Min(Mathf.Min(heightN, heightS), Mathf.Min(heightE, heightW)); + + // 只生成从表面到相邻最低点的方块,再加一层底部 + int bottomY = Mathf.Max(0, minNeighborHeight - 1); + + // 限制最大深度,避免生成过多方块 + int maxDepth = Mathf.Min(terrainDepth, intHeight - bottomY + 1); - // 生成多层地形方块 - for (int depth = 0; depth < terrainDepth; depth++) + for (int depth = 0; depth < maxDepth; depth++) { int y = intHeight - depth; - if (y < 0) break; // 不生成负高度的方块 + if (y < 0) break; Color blockColor; if (depth == 0) { - // 表面层使用地形颜色 - blockColor = GetTerrainColor(intHeight, height); + blockColor = GetTerrainColor(intHeight, intHeight); } - else if (depth < 2) + else if (depth < 3) { - // 第2层是泥土 blockColor = dirtColor; } else { - // 更深层是石头 blockColor = stoneColor; } From a3d0e642e94b7b0b28ebbef113a288d52486c1fd Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 18:54:53 +0800 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20=E7=AE=80=E5=8C=96=E5=9C=B0=E5=BD=A2?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=EF=BC=8CterrainDepth=3D5?= =?UTF-8?q?=20=E4=BF=9D=E8=AF=81=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除复杂的可见性优化,改为直接生成完整深度 - 地形厚度设为5层,平衡性能和可玩性 - 简化方块揭露逻辑(方块已全部预生成) --- Assets/Scripts/BlockInteractionSystem.cs | 41 +----------------------- Assets/Scripts/CubeGenerator.cs | 37 ++++----------------- 2 files changed, 7 insertions(+), 71 deletions(-) diff --git a/Assets/Scripts/BlockInteractionSystem.cs b/Assets/Scripts/BlockInteractionSystem.cs index 58e7cd37..16cf19b3 100644 --- a/Assets/Scripts/BlockInteractionSystem.cs +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -241,46 +241,7 @@ void DestroyBlock() /// void RevealHiddenBlocks(Vector3 destroyedPos) { - // 延迟一帧执行,确保方块已被销毁 - StartCoroutine(RevealHiddenBlocksDelayed(destroyedPos)); - } - - System.Collections.IEnumerator RevealHiddenBlocksDelayed(Vector3 destroyedPos) - { - yield return null; // 等待一帧 - - CubeGenerator cubeGen = FindObjectOfType(); - if (cubeGen == null || cubeGen.cubePrefab == null) yield break; - - int worldX = Mathf.RoundToInt(destroyedPos.x); - int worldZ = Mathf.RoundToInt(destroyedPos.z); - int worldY = Mathf.RoundToInt(destroyedPos.y); - - // 计算该位置的地形高度 - float surfaceHeight = GetTerrainHeight(worldX, worldZ, cubeGen); - int intSurfaceHeight = Mathf.FloorToInt(surfaceHeight); - - // 检查下方位置 - Vector3 belowPos = new Vector3(worldX, worldY - 1, worldZ); - int belowY = worldY - 1; - - if (belowY >= 0) - { - int depth = intSurfaceHeight - belowY; - - // 如果在地形深度范围内,直接生成(不检测是否占用,因为优化后下方本来就没有方块) - if (depth >= 0 && depth < cubeGen.terrainDepth) - { - // 用更大的检测范围确认下方确实没有方块 - Collider[] colliders = Physics.OverlapBox(belowPos, Vector3.one * 0.4f); - if (colliders.Length == 0) - { - Color blockColor = GetBlockColorForDepth(depth, intSurfaceHeight, cubeGen); - CreateRevealedBlock(belowPos, blockColor); - Debug.Log($"BlockInteractionSystem: 生成隐藏方块于 {belowPos}, 深度={depth}"); - } - } - } + // 方块已经全部生成,不需要额外揭露逻辑 } /// diff --git a/Assets/Scripts/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 965d79ca..4345d3ee 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -12,7 +12,7 @@ public class CubeGenerator : MonoBehaviour public float noiseScale = 20f; public float heightScale = 10f; public int seed; - public int terrainDepth = 100; // 地形厚度(层数) + public int terrainDepth = 5; // 地形厚度(层数) [Header("颜色设置")] public Color grassColor = new Color(0.4f, 0.7f, 0.2f); @@ -197,19 +197,6 @@ void GenerateChunk(Vector2Int chunkPos, Transform parent) int startX = chunkPos.x * chunkSize; int startZ = chunkPos.y * chunkSize; - // 先计算整个区块的高度图 - int[,] heightMap = new int[chunkSize + 2, chunkSize + 2]; // +2 用于边界检测 - for (int x = -1; x <= chunkSize; x++) - { - for (int z = -1; z <= chunkSize; z++) - { - int worldX = startX + x; - int worldZ = startZ + z; - float height = GenerateHeight(worldX, worldZ); - heightMap[x + 1, z + 1] = Mathf.FloorToInt(height); - } - } - // 生成地形 for (int x = 0; x < chunkSize; x++) { @@ -217,27 +204,15 @@ void GenerateChunk(Vector2Int chunkPos, Transform parent) { int worldX = startX + x; int worldZ = startZ + z; - int intHeight = heightMap[x + 1, z + 1]; - - // 获取相邻位置的高度 - int heightN = heightMap[x + 1, z + 2]; // 北 - int heightS = heightMap[x + 1, z]; // 南 - int heightE = heightMap[x + 2, z + 1]; // 东 - int heightW = heightMap[x, z + 1]; // 西 - // 计算需要暴露的最低高度(相邻最低高度) - int minNeighborHeight = Mathf.Min(Mathf.Min(heightN, heightS), Mathf.Min(heightE, heightW)); - - // 只生成从表面到相邻最低点的方块,再加一层底部 - int bottomY = Mathf.Max(0, minNeighborHeight - 1); - - // 限制最大深度,避免生成过多方块 - int maxDepth = Mathf.Min(terrainDepth, intHeight - bottomY + 1); + // 使用柏林噪声生成高度 + float height = GenerateHeight(worldX, worldZ); + int intHeight = Mathf.FloorToInt(height); - for (int depth = 0; depth < maxDepth; depth++) + // 生成完整深度的地形方块 + for (int depth = 0; depth < terrainDepth; depth++) { int y = intHeight - depth; - if (y < 0) break; Color blockColor; if (depth == 0) From e87f07d7ba70ef18378cf51e88d7a7899efe6e67 Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 19:00:22 +0800 Subject: [PATCH 7/9] =?UTF-8?q?perf:=20=E5=87=8F=E5=B0=8F=20renderDistance?= =?UTF-8?q?=20=E4=B8=BA=202=20=E6=8F=90=E5=8D=87=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scenes/SampleScene.scene | 2 +- Assets/Scripts/CubeGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Scenes/SampleScene.scene b/Assets/Scenes/SampleScene.scene index 5b50fe6c..34138cbc 100644 --- a/Assets/Scenes/SampleScene.scene +++ b/Assets/Scenes/SampleScene.scene @@ -680,7 +680,7 @@ MonoBehaviour: noiseScale: 20 heightScale: 10 seed: 0 - terrainDepth: 100 + terrainDepth: 5 grassColor: {r: 0.4, g: 0.7, b: 0.2, a: 1} dirtColor: {r: 0.6, g: 0.4, b: 0.2, a: 1} stoneColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} diff --git a/Assets/Scripts/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 4345d3ee..3f76146a 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -6,7 +6,7 @@ public class CubeGenerator : MonoBehaviour [Header("方块设置")] public GameObject cubePrefab; public int chunkSize = 16; - public int renderDistance = 3; + public int renderDistance = 2; // 减小渲染距离提升性能 [Header("地形设置")] public float noiseScale = 20f; From 56330ecb9396180d74fffd8ec9b82d19f0df6752 Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 20:23:22 +0800 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E4=BA=A4=E4=BA=92=E7=B3=BB=E7=BB=9F=E5=92=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构 BlockInteractionSystem 和 CubeGenerator - 优化 AnimalManager 代码结构 - 增强 BlockHighlight 功能 - 新增 World 系统和 Shaders 资源 - 更新场景配置 --- Assets/Scenes/SampleScene.scene | 4 +- Assets/Scripts/AnimalManager.cs | 848 ++++++++---------- Assets/Scripts/BlockHighlight.cs | 14 + Assets/Scripts/BlockInteractionSystem.cs | 493 ++++------ Assets/Scripts/CubeGenerator.cs | 448 +++++---- Assets/Scripts/World.meta | 8 + Assets/Scripts/World/BlockType.cs | 126 +++ Assets/Scripts/World/BlockType.cs.meta | 11 + Assets/Scripts/World/ChunkData.cs | 106 +++ Assets/Scripts/World/ChunkData.cs.meta | 11 + Assets/Scripts/World/ChunkMeshBuilder.cs | 217 +++++ Assets/Scripts/World/ChunkMeshBuilder.cs.meta | 11 + Assets/Scripts/World/ChunkRaycast.cs | 174 ++++ Assets/Scripts/World/ChunkRaycast.cs.meta | 11 + Assets/Scripts/World/ChunkRenderer.cs | 248 +++++ Assets/Scripts/World/ChunkRenderer.cs.meta | 11 + Assets/Scripts/World/WorldData.cs | 142 +++ Assets/Scripts/World/WorldData.cs.meta | 11 + Assets/Shaders.meta | 8 + Assets/Shaders/VertexColor.shader | 41 + Assets/Shaders/VertexColor.shader.meta | 9 + Assets/Shaders/VertexColorTransparent.shader | 41 + .../VertexColorTransparent.shader.meta | 9 + Assets/Shaders/VertexColorUnlit.shader | 51 ++ Assets/Shaders/VertexColorUnlit.shader.meta | 9 + 25 files changed, 2029 insertions(+), 1033 deletions(-) create mode 100644 Assets/Scripts/World.meta create mode 100644 Assets/Scripts/World/BlockType.cs create mode 100644 Assets/Scripts/World/BlockType.cs.meta create mode 100644 Assets/Scripts/World/ChunkData.cs create mode 100644 Assets/Scripts/World/ChunkData.cs.meta create mode 100644 Assets/Scripts/World/ChunkMeshBuilder.cs create mode 100644 Assets/Scripts/World/ChunkMeshBuilder.cs.meta create mode 100644 Assets/Scripts/World/ChunkRaycast.cs create mode 100644 Assets/Scripts/World/ChunkRaycast.cs.meta create mode 100644 Assets/Scripts/World/ChunkRenderer.cs create mode 100644 Assets/Scripts/World/ChunkRenderer.cs.meta create mode 100644 Assets/Scripts/World/WorldData.cs create mode 100644 Assets/Scripts/World/WorldData.cs.meta create mode 100644 Assets/Shaders.meta create mode 100644 Assets/Shaders/VertexColor.shader create mode 100644 Assets/Shaders/VertexColor.shader.meta create mode 100644 Assets/Shaders/VertexColorTransparent.shader create mode 100644 Assets/Shaders/VertexColorTransparent.shader.meta create mode 100644 Assets/Shaders/VertexColorUnlit.shader create mode 100644 Assets/Shaders/VertexColorUnlit.shader.meta diff --git a/Assets/Scenes/SampleScene.scene b/Assets/Scenes/SampleScene.scene index 34138cbc..84ef7c8a 100644 --- a/Assets/Scenes/SampleScene.scene +++ b/Assets/Scenes/SampleScene.scene @@ -674,13 +674,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c26403aa601d24355ac79a56b499c912, type: 3} m_Name: m_EditorClassIdentifier: - cubePrefab: {fileID: 7006843965498854288, guid: 7691624e7fa5c44ef8d9dedf1b26148b, type: 3} chunkSize: 16 renderDistance: 3 noiseScale: 20 heightScale: 10 seed: 0 - terrainDepth: 5 + terrainDepth: 100 grassColor: {r: 0.4, g: 0.7, b: 0.2, a: 1} dirtColor: {r: 0.6, g: 0.4, b: 0.2, a: 1} stoneColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} @@ -688,6 +687,7 @@ MonoBehaviour: sandColor: {r: 0.9, g: 0.8, b: 0.5, a: 1} treeColor: {r: 0.3, g: 0.2, b: 0.1, a: 1} leafColor: {r: 0.2, g: 0.5, b: 0.1, a: 1} + cubePrefab: {fileID: 7006843965498854288, guid: 7691624e7fa5c44ef8d9dedf1b26148b, type: 3} --- !u!4 &1896583719 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/AnimalManager.cs b/Assets/Scripts/AnimalManager.cs index c5fe49ad..e0decd57 100644 --- a/Assets/Scripts/AnimalManager.cs +++ b/Assets/Scripts/AnimalManager.cs @@ -24,81 +24,81 @@ public class AnimalManager : MonoBehaviour { Animal.AnimalType.Rabbit, new AnimalData( - new Color(0.9f, 0.9f, 0.9f), // 白色 - new Color(0.8f, 0.8f, 0.8f), // 浅灰 - 0.4f, // 缩放 - 8f, // 跳跃力 - 3f // 移动速度 + new Color(0.95f, 0.95f, 0.95f), // 白色 + new Color(1f, 0.6f, 0.6f), // 粉色内耳 + 1.2f, // 缩放 - 更大 + 8f, + 3f ) }, { Animal.AnimalType.Chicken, new AnimalData( - new Color(1f, 0.8f, 0.2f), // 黄色 - new Color(1f, 0.3f, 0.1f), // 红色 - 0.3f, // 缩放 - 2f, // 跳跃力 - 1.5f // 移动速度 + new Color(1f, 0.85f, 0.3f), // 黄色 + new Color(1f, 0.2f, 0.1f), // 红色鸡冠 + 1.0f, // 缩放 + 2f, + 1.5f ) }, { Animal.AnimalType.Cat, new AnimalData( - new Color(0.8f, 0.8f, 0.8f), // 灰色 - new Color(0.6f, 0.6f, 0.6f), // 深灰 - 0.4f, // 缩放 - 6f, // 跳跃力 - 4f // 移动速度 + new Color(1f, 0.6f, 0.2f), // 橘猫 + new Color(0.95f, 0.95f, 0.95f), // 白色 + 1.0f, // 缩放 + 6f, + 4f ) }, { Animal.AnimalType.Dog, new AnimalData( - new Color(0.6f, 0.4f, 0.2f), // 棕色 - new Color(0.4f, 0.3f, 0.1f), // 深棕 - 0.5f, // 缩放 - 5f, // 跳跃力 - 3.5f // 移动速度 + new Color(0.65f, 0.45f, 0.25f), // 棕色 + new Color(0.4f, 0.25f, 0.1f), // 深棕耳朵 + 1.2f, // 缩放 + 5f, + 3.5f ) }, { Animal.AnimalType.Sheep, new AnimalData( - new Color(1f, 1f, 1f), // 白色 - new Color(0.9f, 0.9f, 0.9f), // 浅灰 - 0.6f, // 缩放 - 3f, // 跳跃力 - 2f // 移动速度 + new Color(1f, 1f, 1f), // 白羊毛 + new Color(0.15f, 0.15f, 0.15f), // 黑脸 + 1.3f, // 缩放 + 3f, + 2f ) }, { Animal.AnimalType.Tiger, new AnimalData( new Color(1f, 0.6f, 0.1f), // 橙色 - new Color(0.1f, 0.1f, 0.1f), // 黑色 - 0.7f, // 缩放 - 6f, // 跳跃力 - 5f // 移动速度 + new Color(0.1f, 0.1f, 0.1f), // 黑条纹 + 1.4f, // 缩放 + 6f, + 5f ) }, { Animal.AnimalType.Lion, new AnimalData( - new Color(0.9f, 0.7f, 0.3f), // 金色 - new Color(0.6f, 0.4f, 0.1f), // 深金 - 0.7f, // 缩放 - 6f, // 跳跃力 - 5f // 移动速度 + new Color(0.9f, 0.7f, 0.35f), // 金色身体 + new Color(0.7f, 0.45f, 0.15f), // 棕色鬃毛 + 1.4f, // 缩放 + 6f, + 5f ) }, { Animal.AnimalType.Elephant, new AnimalData( - new Color(0.5f, 0.5f, 0.5f), // 灰色 - new Color(0.4f, 0.4f, 0.4f), // 深灰 - 0.8f, // 缩放 - 2f, // 跳跃力 - 2f // 移动速度 + new Color(0.55f, 0.55f, 0.6f), // 灰色 + new Color(0.7f, 0.5f, 0.5f), // 粉色内耳 + 1.5f, // 缩放 - 大象最大 + 2f, + 2f ) } }; @@ -247,504 +247,376 @@ void CreateAnimalModel(Transform parent, Animal.AnimalType type, AnimalData data break; } } - GameObject CreateCube(Vector3 position, Transform parent, Color color) + GameObject CreatePart(Vector3 position, Transform parent, Color color, PrimitiveType shapeType, Vector3 scale, Vector3? rotation = null) { - if (cubePrefab != null) + GameObject part = GameObject.CreatePrimitive(shapeType); + part.transform.parent = parent; + part.transform.localPosition = position; + part.transform.localScale = scale; + + if (rotation.HasValue) { - // 添加微小的随机偏移来避免Z-fighting - Vector3 offset = new Vector3( - Random.Range(-0.01f, 0.01f), - Random.Range(-0.01f, 0.01f), - Random.Range(-0.01f, 0.01f) - ); - - GameObject cube = Instantiate(cubePrefab, Vector3.zero, Quaternion.identity, parent); - cube.transform.localPosition = position + offset; - - Renderer renderer = cube.GetComponent(); - if (renderer != null) - { - Material material = new Material(Shader.Find("Standard")); - material.color = color; - - // 优化渲染设置 - material.enableInstancing = true; - material.SetFloat("_Metallic", 0); - material.SetFloat("_Glossiness", 0.1f); - - // 设置更好的阴影 - renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On; - renderer.receiveShadows = true; - - renderer.material = material; - - // 缓存材质 - if (!materialCache.ContainsKey(parent.gameObject)) - { - materialCache[parent.gameObject] = new Material[1]; - } - materialCache[parent.gameObject][0] = material; - } - - return cube; + part.transform.localEulerAngles = rotation.Value; + } + + // 移除碰撞体,避免物理干扰 + Collider col = part.GetComponent(); + if (col != null) Destroy(col); + + Renderer renderer = part.GetComponent(); + if (renderer != null) + { + Material material = new Material(Shader.Find("Standard")); + material.color = color; + material.SetFloat("_Metallic", 0); + material.SetFloat("_Glossiness", 0.3f); + renderer.material = material; } - return null; + + return part; + } + + // 简化版:默认立方体 + GameObject CreateCube(Vector3 position, Transform parent, Color color) + { + return CreatePart(position, parent, color, PrimitiveType.Cube, Vector3.one); } void CreateRabbit(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体(更圆润的形状) - for (float z = -0.5f; z <= 0.5f; z += 0.5f) - { - for (float x = -0.5f; x <= 0.5f; x += 0.5f) - { - CreateCube(new Vector3(x, 0.5f, z), parent, data.mainColor); - } - } - - // 头部(更精细的细节) - CreateCube(new Vector3(0, 1f, 0.7f), parent, data.mainColor); - CreateCube(new Vector3(-0.2f, 1f, 0.7f), parent, data.mainColor); - CreateCube(new Vector3(0.2f, 1f, 0.7f), parent, data.mainColor); - + + // 圆头 + CreatePart(new Vector3(0, 0.9f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.8f, 0.7f, 0.7f)); + + // 长耳朵 - 兔子最明显特征 (胶囊形) + CreatePart(new Vector3(-0.2f, 1.6f, 0), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.15f, 0.5f, 0.08f)); + CreatePart(new Vector3(0.2f, 1.6f, 0), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.15f, 0.5f, 0.08f)); + // 粉色内耳 + CreatePart(new Vector3(-0.2f, 1.6f, 0.03f), parent, data.secondaryColor, PrimitiveType.Capsule, new Vector3(0.08f, 0.4f, 0.02f)); + CreatePart(new Vector3(0.2f, 1.6f, 0.03f), parent, data.secondaryColor, PrimitiveType.Capsule, new Vector3(0.08f, 0.4f, 0.02f)); + // 眼睛 - CreateCube(new Vector3(-0.3f, 1.1f, 1f), parent, Color.black); - CreateCube(new Vector3(0.3f, 1.1f, 1f), parent, Color.black); - - // 鼻子 - CreateCube(new Vector3(0, 0.9f, 1.1f), parent, new Color(1f, 0.8f, 0.8f)); - - // 长耳朵 - for (float y = 0; y <= 1f; y += 0.5f) - { - CreateCube(new Vector3(-0.2f, 1.5f + y, 0.7f), parent, data.mainColor); - CreateCube(new Vector3(0.2f, 1.5f + y, 0.7f), parent, data.mainColor); - } - - // 内耳 - CreateCube(new Vector3(-0.2f, 2f, 0.8f), parent, new Color(1f, 0.8f, 0.8f)); - CreateCube(new Vector3(0.2f, 2f, 0.8f), parent, new Color(1f, 0.8f, 0.8f)); - - // 后腿(更强壮) - CreateCube(new Vector3(-0.3f, 0.2f, 0), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 0.2f, 0), parent, data.mainColor); - CreateCube(new Vector3(-0.3f, 0, 0.2f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 0, 0.2f), parent, data.mainColor); - - // 前腿 - CreateCube(new Vector3(-0.3f, 0.2f, 0.6f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 0.2f, 0.6f), parent, data.mainColor); - - // 蓬松的尾巴 - CreateCube(new Vector3(0, 0.5f, -0.4f), parent, Color.white); - CreateCube(new Vector3(0.2f, 0.5f, -0.4f), parent, Color.white); - CreateCube(new Vector3(-0.2f, 0.5f, -0.4f), parent, Color.white); + CreatePart(new Vector3(-0.2f, 1f, 0.3f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.15f, 0.15f, 0.1f)); + CreatePart(new Vector3(0.2f, 1f, 0.3f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.15f, 0.15f, 0.1f)); + + // 粉鼻子 + CreatePart(new Vector3(0, 0.85f, 0.4f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.1f, 0.08f, 0.08f)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.35f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.6f, 0.5f, 0.7f)); + + // 短腿 + CreatePart(new Vector3(-0.2f, 0.1f, 0.15f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.2f, 0.15f)); + CreatePart(new Vector3(0.2f, 0.1f, 0.15f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.2f, 0.15f)); + CreatePart(new Vector3(-0.15f, 0.1f, -0.2f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.2f, 0.2f, 0.25f)); + CreatePart(new Vector3(0.15f, 0.1f, -0.2f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.2f, 0.2f, 0.25f)); + + // 圆尾巴 + CreatePart(new Vector3(0, 0.4f, -0.4f), parent, Color.white, PrimitiveType.Sphere, new Vector3(0.25f, 0.25f, 0.25f)); } void CreateChicken(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体(圆润的形状) - for (float x = -0.3f; x <= 0.3f; x += 0.3f) - { - for (float z = -0.3f; z <= 0.3f; z += 0.3f) - { - CreateCube(new Vector3(x, 0.3f, z), parent, data.mainColor); - } - } - - // 头部 - CreateCube(new Vector3(0, 0.6f, 0.3f), parent, data.mainColor); - - // 鸡冠 - for (float x = -0.2f; x <= 0.2f; x += 0.2f) - { - CreateCube(new Vector3(x, 0.8f, 0.3f), parent, data.secondaryColor); - } - + + // 小圆头 + CreatePart(new Vector3(0, 0.9f, 0.1f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.4f, 0.45f, 0.4f)); + + // 红色鸡冠 - 最明显特征 + CreatePart(new Vector3(0, 1.2f, 0.1f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.2f, 0.08f)); + CreatePart(new Vector3(0.08f, 1.1f, 0.1f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.12f, 0.15f, 0.06f)); + CreatePart(new Vector3(-0.08f, 1.1f, 0.1f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.12f, 0.15f, 0.06f)); + // 眼睛 - CreateCube(new Vector3(-0.15f, 0.6f, 0.5f), parent, Color.black); - CreateCube(new Vector3(0.15f, 0.6f, 0.5f), parent, Color.black); - - // 喙 - CreateCube(new Vector3(0, 0.5f, 0.6f), parent, new Color(1f, 0.6f, 0)); - + CreatePart(new Vector3(-0.12f, 0.95f, 0.28f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.05f)); + CreatePart(new Vector3(0.12f, 0.95f, 0.28f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.05f)); + + // 尖喙 - 圆锥形用立方体模拟 + CreatePart(new Vector3(0, 0.85f, 0.4f), parent, new Color(1f, 0.6f, 0.2f), PrimitiveType.Cube, new Vector3(0.1f, 0.08f, 0.2f)); + + // 红色肉垂 + CreatePart(new Vector3(0, 0.72f, 0.3f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.08f, 0.12f, 0.06f)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.45f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.5f, 0.55f, 0.65f)); + // 翅膀 - for (float y = 0.2f; y <= 0.4f; y += 0.2f) - { - CreateCube(new Vector3(-0.4f, y, 0), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, 0), parent, data.mainColor); - } - - // 尾羽 - for (float x = -0.2f; x <= 0.2f; x += 0.2f) - { - CreateCube(new Vector3(x, 0.4f, -0.4f), parent, data.mainColor); - } - - // 腿 - CreateCube(new Vector3(-0.2f, 0, 0), parent, data.secondaryColor); - CreateCube(new Vector3(0.2f, 0, 0), parent, data.secondaryColor); + CreatePart(new Vector3(-0.3f, 0.5f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.25f, 0.35f)); + CreatePart(new Vector3(0.3f, 0.5f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.25f, 0.35f)); + + // 尾巴羽毛 + CreatePart(new Vector3(0, 0.6f, -0.35f), parent, data.mainColor, PrimitiveType.Cube, new Vector3(0.08f, 0.3f, 0.15f), new Vector3(-30, 0, 0)); + + // 细黄腿 + CreatePart(new Vector3(-0.12f, 0.1f, 0.05f), parent, new Color(1f, 0.7f, 0.2f), PrimitiveType.Cylinder, new Vector3(0.05f, 0.12f, 0.05f)); + CreatePart(new Vector3(0.12f, 0.1f, 0.05f), parent, new Color(1f, 0.7f, 0.2f), PrimitiveType.Cylinder, new Vector3(0.05f, 0.12f, 0.05f)); } void CreateCat(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体 - for (float z = -0.7f; z <= 0.7f; z += 0.5f) - { - for (float x = -0.4f; x <= 0.4f; x += 0.4f) - { - CreateCube(new Vector3(x, 0.5f, z), parent, data.mainColor); - } - } - - // 头部 - CreateCube(new Vector3(0, 1f, 1f), parent, data.mainColor); - CreateCube(new Vector3(-0.2f, 1f, 1f), parent, data.mainColor); - CreateCube(new Vector3(0.2f, 1f, 1f), parent, data.mainColor); - - // 眼睛(发光的猫眼) - CreateCube(new Vector3(-0.2f, 1.1f, 1.3f), parent, new Color(0.3f, 0.8f, 0.3f)); - CreateCube(new Vector3(0.2f, 1.1f, 1.3f), parent, new Color(0.3f, 0.8f, 0.3f)); - - // 鼻子 - CreateCube(new Vector3(0, 0.9f, 1.4f), parent, new Color(1f, 0.8f, 0.8f)); - - // 耳朵(三角形) - CreateCube(new Vector3(-0.3f, 1.5f, 1f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 1.5f, 1f), parent, data.mainColor); - CreateCube(new Vector3(-0.3f, 1.7f, 1f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 1.7f, 1f), parent, data.mainColor); - - // 内耳 - CreateCube(new Vector3(-0.3f, 1.5f, 1.1f), parent, new Color(1f, 0.8f, 0.8f)); - CreateCube(new Vector3(0.3f, 1.5f, 1.1f), parent, new Color(1f, 0.8f, 0.8f)); - - // 腿 - CreateCube(new Vector3(-0.3f, 0, -0.3f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 0, -0.3f), parent, data.mainColor); - CreateCube(new Vector3(-0.3f, 0, 0.3f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, 0, 0.3f), parent, data.mainColor); - - // 优雅的尾巴(弧形) - float tailLength = 1.2f; - int tailSegments = 4; - for (int i = 0; i < tailSegments; i++) - { - float t = i / (float)(tailSegments - 1); - float angle = Mathf.Lerp(0, Mathf.PI * 0.5f, t); - Vector3 pos = new Vector3( - 0, - 0.5f + Mathf.Sin(angle) * 0.5f, - -0.7f - Mathf.Cos(angle) * tailLength - ); - CreateCube(pos, parent, data.mainColor); - } + + // 圆头 + CreatePart(new Vector3(0, 0.85f, 0.2f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.55f, 0.5f, 0.5f)); + + // 三角尖耳朵 - 猫的特征 + CreatePart(new Vector3(-0.2f, 1.15f, 0.15f), parent, data.mainColor, PrimitiveType.Cube, new Vector3(0.15f, 0.2f, 0.08f), new Vector3(0, 0, 15)); + CreatePart(new Vector3(0.2f, 1.15f, 0.15f), parent, data.mainColor, PrimitiveType.Cube, new Vector3(0.15f, 0.2f, 0.08f), new Vector3(0, 0, -15)); + // 粉色内耳 + CreatePart(new Vector3(-0.2f, 1.12f, 0.18f), parent, new Color(1f, 0.6f, 0.6f), PrimitiveType.Cube, new Vector3(0.08f, 0.12f, 0.02f), new Vector3(0, 0, 15)); + CreatePart(new Vector3(0.2f, 1.12f, 0.18f), parent, new Color(1f, 0.6f, 0.6f), PrimitiveType.Cube, new Vector3(0.08f, 0.12f, 0.02f), new Vector3(0, 0, -15)); + + // 大眼睛 + CreatePart(new Vector3(-0.15f, 0.9f, 0.42f), parent, new Color(0.2f, 0.8f, 0.2f), PrimitiveType.Sphere, new Vector3(0.14f, 0.16f, 0.08f)); + CreatePart(new Vector3(0.15f, 0.9f, 0.42f), parent, new Color(0.2f, 0.8f, 0.2f), PrimitiveType.Sphere, new Vector3(0.14f, 0.16f, 0.08f)); + // 瞳孔 + CreatePart(new Vector3(-0.15f, 0.9f, 0.46f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.05f, 0.12f, 0.02f)); + CreatePart(new Vector3(0.15f, 0.9f, 0.46f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.05f, 0.12f, 0.02f)); + + // 粉鼻子 + CreatePart(new Vector3(0, 0.8f, 0.45f), parent, new Color(1f, 0.6f, 0.6f), PrimitiveType.Sphere, new Vector3(0.08f, 0.06f, 0.06f)); + + // 白色嘴巴 + CreatePart(new Vector3(0, 0.73f, 0.4f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.1f, 0.12f)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.4f, -0.15f), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.35f, 0.3f, 0.4f), new Vector3(90, 0, 0)); + + // 四条腿 + CreatePart(new Vector3(-0.15f, 0.15f, 0.1f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.08f, 0.15f, 0.08f)); + CreatePart(new Vector3(0.15f, 0.15f, 0.1f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.08f, 0.15f, 0.08f)); + CreatePart(new Vector3(-0.15f, 0.15f, -0.35f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.08f, 0.15f, 0.08f)); + CreatePart(new Vector3(0.15f, 0.15f, -0.35f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.08f, 0.15f, 0.08f)); + + // 翘起的长尾巴 + CreatePart(new Vector3(0, 0.45f, -0.55f), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.06f, 0.2f, 0.06f), new Vector3(-30, 0, 0)); + CreatePart(new Vector3(0, 0.7f, -0.65f), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.06f, 0.15f, 0.06f), new Vector3(30, 0, 0)); } void CreateDog(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体 - for (float z = -0.8f; z <= 0.8f; z += 0.4f) - { - for (float x = -0.4f; x <= 0.4f; x += 0.4f) - { - CreateCube(new Vector3(x, 0.6f, z), parent, data.mainColor); - } - } - - // 头部 - for (float x = -0.4f; x <= 0.4f; x += 0.4f) - { - for (float z = 0.8f; z <= 1.2f; z += 0.4f) - { - CreateCube(new Vector3(x, 1f, z), parent, data.mainColor); - } - } - - // 吻部 - CreateCube(new Vector3(0, 0.8f, 1.4f), parent, data.mainColor); - CreateCube(new Vector3(0, 0.6f, 1.4f), parent, data.secondaryColor); - + + // 圆头 + CreatePart(new Vector3(0, 0.85f, 0.15f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.55f, 0.5f, 0.55f)); + + // 长嘴巴 - 狗的特征 + CreatePart(new Vector3(0, 0.75f, 0.45f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.25f, 0.2f, 0.3f)); + + // 垂耳 - 狗的特征 + CreatePart(new Vector3(-0.3f, 0.75f, 0.1f), parent, data.secondaryColor, PrimitiveType.Capsule, new Vector3(0.12f, 0.2f, 0.08f)); + CreatePart(new Vector3(0.3f, 0.75f, 0.1f), parent, data.secondaryColor, PrimitiveType.Capsule, new Vector3(0.12f, 0.2f, 0.08f)); + // 眼睛 - CreateCube(new Vector3(-0.3f, 1.1f, 1.3f), parent, Color.black); - CreateCube(new Vector3(0.3f, 1.1f, 1.3f), parent, Color.black); - - // 鼻子 - CreateCube(new Vector3(0, 0.8f, 1.6f), parent, Color.black); - - // 耳朵(下垂) - for (float y = 1.4f; y >= 1f; y -= 0.2f) - { - CreateCube(new Vector3(-0.4f, y, 1f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, 1f), parent, data.mainColor); - } - - // 腿 - float legHeight = 0.8f; - for (float y = 0; y < legHeight; y += 0.2f) - { - CreateCube(new Vector3(-0.3f, y, -0.6f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, y, -0.6f), parent, data.mainColor); - CreateCube(new Vector3(-0.3f, y, 0.6f), parent, data.mainColor); - CreateCube(new Vector3(0.3f, y, 0.6f), parent, data.mainColor); - } - - // 摇摆的尾巴 - float tailLength = 0.8f; - int tailSegments = 4; - for (int i = 0; i < tailSegments; i++) - { - float t = i / (float)(tailSegments - 1); - Vector3 pos = new Vector3( - Mathf.Sin(t * Mathf.PI * 0.5f) * 0.3f, - 0.8f + t * 0.4f, - -0.8f - t * tailLength - ); - CreateCube(pos, parent, data.mainColor); - } + CreatePart(new Vector3(-0.15f, 0.92f, 0.35f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.1f, 0.1f, 0.06f)); + CreatePart(new Vector3(0.15f, 0.92f, 0.35f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.1f, 0.1f, 0.06f)); + + // 黑鼻子 + CreatePart(new Vector3(0, 0.8f, 0.6f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.1f, 0.08f, 0.08f)); + + // 吐舌头 + CreatePart(new Vector3(0, 0.65f, 0.55f), parent, new Color(1f, 0.5f, 0.5f), PrimitiveType.Cube, new Vector3(0.08f, 0.02f, 0.15f), new Vector3(20, 0, 0)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.4f, -0.2f), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.35f, 0.35f, 0.45f), new Vector3(90, 0, 0)); + + // 四条腿 + CreatePart(new Vector3(-0.18f, 0.18f, 0.1f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.18f, 0.18f, 0.1f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(-0.18f, 0.18f, -0.4f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.18f, 0.18f, -0.4f), parent, data.mainColor, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + + // 翘起的尾巴 + CreatePart(new Vector3(0, 0.55f, -0.6f), parent, data.mainColor, PrimitiveType.Capsule, new Vector3(0.08f, 0.2f, 0.08f), new Vector3(-45, 0, 0)); } void CreateSheep(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 蓬松的身体 - for (float x = -0.6f; x <= 0.6f; x += 0.3f) - { - for (float y = 0.3f; y <= 0.9f; y += 0.3f) - { - for (float z = -0.6f; z <= 0.6f; z += 0.3f) - { - if (Random.value < 0.8f) // 随机创建蓬松效果 - { - CreateCube(new Vector3(x, y, z), parent, Color.white); - } - } - } - } - - // 头 - CreateCube(new Vector3(0, 0.9f, 0.9f), parent, data.secondaryColor); - CreateCube(new Vector3(-0.2f, 0.9f, 0.9f), parent, data.secondaryColor); - CreateCube(new Vector3(0.2f, 0.9f, 0.9f), parent, data.secondaryColor); - + + // 蓬松羊毛头 - 多个球组成 + CreatePart(new Vector3(0, 0.95f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.5f, 0.45f, 0.45f)); + CreatePart(new Vector3(-0.2f, 1.05f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.25f, 0.25f, 0.25f)); + CreatePart(new Vector3(0.2f, 1.05f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.25f, 0.25f, 0.25f)); + CreatePart(new Vector3(0, 1.15f, 0), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.2f, 0.2f, 0.2f)); + + // 黑色小脸 - 绵羊特征 + CreatePart(new Vector3(0, 0.85f, 0.3f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.25f, 0.3f, 0.2f)); + // 眼睛 - CreateCube(new Vector3(-0.3f, 1f, 1.1f), parent, Color.black); - CreateCube(new Vector3(0.3f, 1f, 1.1f), parent, Color.black); - - // 耳朵 - CreateCube(new Vector3(-0.4f, 1.1f, 0.9f), parent, data.secondaryColor); - CreateCube(new Vector3(0.4f, 1.1f, 0.9f), parent, data.secondaryColor); - - // 腿 - CreateCube(new Vector3(-0.3f, 0, -0.3f), parent, data.secondaryColor); - CreateCube(new Vector3(0.3f, 0, -0.3f), parent, data.secondaryColor); - CreateCube(new Vector3(-0.3f, 0, 0.3f), parent, data.secondaryColor); - CreateCube(new Vector3(0.3f, 0, 0.3f), parent, data.secondaryColor); + CreatePart(new Vector3(-0.1f, 0.92f, 0.38f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.06f, 0.06f, 0.04f)); + CreatePart(new Vector3(0.1f, 0.92f, 0.38f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.06f, 0.06f, 0.04f)); + + // 小黑耳朵 + CreatePart(new Vector3(-0.32f, 0.9f, 0.1f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.12f, 0.08f, 0.06f)); + CreatePart(new Vector3(0.32f, 0.9f, 0.1f), parent, data.secondaryColor, PrimitiveType.Sphere, new Vector3(0.12f, 0.08f, 0.06f)); + + // 蓬松身体 - 多个球组成 + CreatePart(new Vector3(0, 0.45f, -0.1f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.6f, 0.5f, 0.7f)); + CreatePart(new Vector3(-0.25f, 0.5f, -0.1f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.3f, 0.3f, 0.35f)); + CreatePart(new Vector3(0.25f, 0.5f, -0.1f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.3f, 0.3f, 0.35f)); + CreatePart(new Vector3(0, 0.6f, -0.1f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.35f, 0.25f, 0.4f)); + + // 黑色细腿 + CreatePart(new Vector3(-0.2f, 0.12f, 0.15f), parent, data.secondaryColor, PrimitiveType.Cylinder, new Vector3(0.06f, 0.12f, 0.06f)); + CreatePart(new Vector3(0.2f, 0.12f, 0.15f), parent, data.secondaryColor, PrimitiveType.Cylinder, new Vector3(0.06f, 0.12f, 0.06f)); + CreatePart(new Vector3(-0.2f, 0.12f, -0.3f), parent, data.secondaryColor, PrimitiveType.Cylinder, new Vector3(0.06f, 0.12f, 0.06f)); + CreatePart(new Vector3(0.2f, 0.12f, -0.3f), parent, data.secondaryColor, PrimitiveType.Cylinder, new Vector3(0.06f, 0.12f, 0.06f)); + + // 小尾巴 + CreatePart(new Vector3(0, 0.45f, -0.45f), parent, data.mainColor, PrimitiveType.Sphere, new Vector3(0.15f, 0.12f, 0.1f)); } void CreateTiger(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体 - for (float z = -1f; z <= 1f; z += 0.4f) - { - for (float x = -0.6f; x <= 0.6f; x += 0.4f) - { - CreateCube(new Vector3(x, 0.8f, z), parent, data.mainColor); - // 添加条纹 - if (Mathf.Abs(z) % 0.8f < 0.4f) - { - CreateCube(new Vector3(x, 0.8f, z), parent, data.secondaryColor); - } - } - } - - // 头部 - for (float x = -0.5f; x <= 0.5f; x += 0.25f) - { - for (float z = 1f; z <= 1.5f; z += 0.25f) - { - CreateCube(new Vector3(x, 1.2f, z), parent, data.mainColor); - } - } - - // 眼睛(发光的黄色) - CreateCube(new Vector3(-0.25f, 1.3f, 1.6f), parent, new Color(1f, 0.8f, 0)); - CreateCube(new Vector3(0.25f, 1.3f, 1.6f), parent, new Color(1f, 0.8f, 0)); - - // 耳朵 - CreateCube(new Vector3(-0.4f, 1.6f, 1.2f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, 1.6f, 1.2f), parent, data.mainColor); - - // 强壮的腿 - float legHeight = 0.8f; - for (float y = 0; y < legHeight; y += 0.2f) - { - CreateCube(new Vector3(-0.4f, y, -0.8f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, -0.8f), parent, data.mainColor); - CreateCube(new Vector3(-0.4f, y, 0.8f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, 0.8f), parent, data.mainColor); - } - - // 长尾巴 - float tailLength = 1.5f; - int tailSegments = 6; - for (int i = 0; i < tailSegments; i++) - { - float t = i / (float)(tailSegments - 1); - Vector3 pos = new Vector3( - 0, - 0.8f - t * 0.3f, - -1f - t * tailLength - ); - CreateCube(pos, parent, data.mainColor); - } + Color orange = data.mainColor; + Color black = data.secondaryColor; + Color white = new Color(1f, 0.95f, 0.9f); + + // 大圆头 + CreatePart(new Vector3(0, 0.9f, 0.1f), parent, orange, PrimitiveType.Sphere, new Vector3(0.6f, 0.55f, 0.55f)); + + // 脸部条纹 - 老虎最明显特征 + CreatePart(new Vector3(0, 1.05f, 0.1f), parent, black, PrimitiveType.Cube, new Vector3(0.08f, 0.15f, 0.4f)); + CreatePart(new Vector3(-0.2f, 0.95f, 0.25f), parent, black, PrimitiveType.Cube, new Vector3(0.04f, 0.2f, 0.15f), new Vector3(0, 0, -20)); + CreatePart(new Vector3(0.2f, 0.95f, 0.25f), parent, black, PrimitiveType.Cube, new Vector3(0.04f, 0.2f, 0.15f), new Vector3(0, 0, 20)); + + // 圆耳朵 + CreatePart(new Vector3(-0.25f, 1.15f, 0.05f), parent, orange, PrimitiveType.Sphere, new Vector3(0.15f, 0.15f, 0.08f)); + CreatePart(new Vector3(0.25f, 1.15f, 0.05f), parent, orange, PrimitiveType.Sphere, new Vector3(0.15f, 0.15f, 0.08f)); + // 白色内耳 + CreatePart(new Vector3(-0.25f, 1.13f, 0.08f), parent, white, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.04f)); + CreatePart(new Vector3(0.25f, 1.13f, 0.08f), parent, white, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.04f)); + + // 眼睛 + CreatePart(new Vector3(-0.15f, 0.95f, 0.38f), parent, new Color(1f, 0.8f, 0.2f), PrimitiveType.Sphere, new Vector3(0.12f, 0.1f, 0.06f)); + CreatePart(new Vector3(0.15f, 0.95f, 0.38f), parent, new Color(1f, 0.8f, 0.2f), PrimitiveType.Sphere, new Vector3(0.12f, 0.1f, 0.06f)); + // 瞳孔 + CreatePart(new Vector3(-0.15f, 0.95f, 0.41f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.05f, 0.08f, 0.02f)); + CreatePart(new Vector3(0.15f, 0.95f, 0.41f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.05f, 0.08f, 0.02f)); + + // 白色嘴部 + CreatePart(new Vector3(0, 0.78f, 0.35f), parent, white, PrimitiveType.Sphere, new Vector3(0.2f, 0.15f, 0.18f)); + + // 粉鼻子 + CreatePart(new Vector3(0, 0.85f, 0.45f), parent, new Color(1f, 0.6f, 0.6f), PrimitiveType.Sphere, new Vector3(0.08f, 0.06f, 0.06f)); + + // 椭圆身体带条纹 + CreatePart(new Vector3(0, 0.45f, -0.2f), parent, orange, PrimitiveType.Capsule, new Vector3(0.4f, 0.35f, 0.5f), new Vector3(90, 0, 0)); + // 身体条纹 + CreatePart(new Vector3(-0.22f, 0.5f, -0.15f), parent, black, PrimitiveType.Cube, new Vector3(0.03f, 0.2f, 0.15f)); + CreatePart(new Vector3(0.22f, 0.5f, -0.15f), parent, black, PrimitiveType.Cube, new Vector3(0.03f, 0.2f, 0.15f)); + CreatePart(new Vector3(0, 0.55f, -0.25f), parent, black, PrimitiveType.Cube, new Vector3(0.03f, 0.15f, 0.2f)); + + // 四条腿 + CreatePart(new Vector3(-0.2f, 0.18f, 0.1f), parent, orange, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.2f, 0.18f, 0.1f), parent, orange, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(-0.2f, 0.18f, -0.4f), parent, orange, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.2f, 0.18f, -0.4f), parent, orange, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + + // 长尾巴带条纹 + CreatePart(new Vector3(0, 0.5f, -0.6f), parent, orange, PrimitiveType.Capsule, new Vector3(0.08f, 0.25f, 0.08f), new Vector3(-50, 0, 0)); + // 尾巴条纹 + CreatePart(new Vector3(0, 0.6f, -0.7f), parent, black, PrimitiveType.Cylinder, new Vector3(0.09f, 0.03f, 0.09f)); + CreatePart(new Vector3(0, 0.75f, -0.6f), parent, black, PrimitiveType.Cylinder, new Vector3(0.09f, 0.03f, 0.09f)); } void CreateLion(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 身体 - for (float z = -1f; z <= 1f; z += 0.4f) - { - for (float x = -0.6f; x <= 0.6f; x += 0.4f) - { - CreateCube(new Vector3(x, 0.8f, z), parent, data.mainColor); - } - } - - // 头部和鬃毛 - for (float x = -0.8f; x <= 0.8f; x += 0.2f) - { - for (float z = 1f; z <= 1.8f; z += 0.2f) - { - for (float y = 1f; y <= 1.6f; y += 0.2f) - { - if (Random.value < 0.7f) // 随机创建蓬松的鬃毛 - { - CreateCube(new Vector3(x, y, z), parent, data.secondaryColor); - } - } - } - } - - // 面部 - CreateCube(new Vector3(0, 1.2f, 1.9f), parent, data.mainColor); - - // 眼睛(发光的黄色) - CreateCube(new Vector3(-0.3f, 1.3f, 2f), parent, new Color(1f, 0.8f, 0)); - CreateCube(new Vector3(0.3f, 1.3f, 2f), parent, new Color(1f, 0.8f, 0)); - - // 鼻子 - CreateCube(new Vector3(0, 1.1f, 2.1f), parent, Color.black); - - // 强壮的腿 - float legHeight = 0.8f; - for (float y = 0; y < legHeight; y += 0.2f) - { - CreateCube(new Vector3(-0.4f, y, -0.8f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, -0.8f), parent, data.mainColor); - CreateCube(new Vector3(-0.4f, y, 0.8f), parent, data.mainColor); - CreateCube(new Vector3(0.4f, y, 0.8f), parent, data.mainColor); - } - - // 尾巴 - float tailLength = 1.5f; - int tailSegments = 6; - for (int i = 0; i < tailSegments; i++) - { - float t = i / (float)(tailSegments - 1); - Vector3 pos = new Vector3( - 0, - 0.8f - t * 0.3f, - -1f - t * tailLength - ); - CreateCube(pos, parent, data.mainColor); - } - - // 尾巴尖端的毛 - for (float x = -0.2f; x <= 0.2f; x += 0.2f) - { - for (float y = -0.2f; y <= 0.2f; y += 0.2f) - { - CreateCube(new Vector3(x, 0.2f + y, -1f - tailLength), parent, data.secondaryColor); - } - } + Color body = data.mainColor; + Color mane = data.secondaryColor; + + // 大蓬松鬃毛 - 狮子最明显的特征(围绕脸一圈) + CreatePart(new Vector3(0, 1.05f, -0.05f), parent, mane, PrimitiveType.Sphere, new Vector3(0.9f, 0.8f, 0.7f)); + // 额外鬃毛球 + CreatePart(new Vector3(-0.35f, 1f, 0), parent, mane, PrimitiveType.Sphere, new Vector3(0.3f, 0.35f, 0.25f)); + CreatePart(new Vector3(0.35f, 1f, 0), parent, mane, PrimitiveType.Sphere, new Vector3(0.3f, 0.35f, 0.25f)); + CreatePart(new Vector3(0, 1.25f, 0), parent, mane, PrimitiveType.Sphere, new Vector3(0.35f, 0.25f, 0.25f)); + CreatePart(new Vector3(-0.25f, 0.75f, 0.1f), parent, mane, PrimitiveType.Sphere, new Vector3(0.25f, 0.25f, 0.2f)); + CreatePart(new Vector3(0.25f, 0.75f, 0.1f), parent, mane, PrimitiveType.Sphere, new Vector3(0.25f, 0.25f, 0.2f)); + + // 金色脸 + CreatePart(new Vector3(0, 0.95f, 0.2f), parent, body, PrimitiveType.Sphere, new Vector3(0.45f, 0.4f, 0.4f)); + + // 小圆耳朵 + CreatePart(new Vector3(-0.3f, 1.2f, -0.05f), parent, body, PrimitiveType.Sphere, new Vector3(0.12f, 0.12f, 0.06f)); + CreatePart(new Vector3(0.3f, 1.2f, -0.05f), parent, body, PrimitiveType.Sphere, new Vector3(0.12f, 0.12f, 0.06f)); + + // 眼睛 + CreatePart(new Vector3(-0.12f, 1f, 0.4f), parent, new Color(0.9f, 0.7f, 0.2f), PrimitiveType.Sphere, new Vector3(0.1f, 0.08f, 0.05f)); + CreatePart(new Vector3(0.12f, 1f, 0.4f), parent, new Color(0.9f, 0.7f, 0.2f), PrimitiveType.Sphere, new Vector3(0.1f, 0.08f, 0.05f)); + // 瞳孔 + CreatePart(new Vector3(-0.12f, 1f, 0.43f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.04f, 0.06f, 0.02f)); + CreatePart(new Vector3(0.12f, 1f, 0.43f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.04f, 0.06f, 0.02f)); + + // 嘴巴区域 + CreatePart(new Vector3(0, 0.85f, 0.4f), parent, new Color(1f, 0.95f, 0.9f), PrimitiveType.Sphere, new Vector3(0.18f, 0.12f, 0.15f)); + + // 棕色鼻子 + CreatePart(new Vector3(0, 0.9f, 0.48f), parent, new Color(0.3f, 0.2f, 0.1f), PrimitiveType.Sphere, new Vector3(0.08f, 0.06f, 0.06f)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.45f, -0.25f), parent, body, PrimitiveType.Capsule, new Vector3(0.4f, 0.35f, 0.5f), new Vector3(90, 0, 0)); + + // 四条腿 + CreatePart(new Vector3(-0.18f, 0.18f, 0.05f), parent, body, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.18f, 0.18f, 0.05f), parent, body, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(-0.18f, 0.18f, -0.45f), parent, body, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + CreatePart(new Vector3(0.18f, 0.18f, -0.45f), parent, body, PrimitiveType.Cylinder, new Vector3(0.1f, 0.18f, 0.1f)); + + // 尾巴带毛球 + CreatePart(new Vector3(0, 0.5f, -0.65f), parent, body, PrimitiveType.Capsule, new Vector3(0.06f, 0.25f, 0.06f), new Vector3(-50, 0, 0)); + // 毛球 + CreatePart(new Vector3(0, 0.72f, -0.78f), parent, mane, PrimitiveType.Sphere, new Vector3(0.15f, 0.15f, 0.12f)); } void CreateElephant(Transform parent, AnimalData data) { parent.localScale = Vector3.one * data.scale; - - // 庞大的身体 - for (float x = -0.8f; x <= 0.8f; x += 0.4f) - { - for (float z = -1f; z <= 1f; z += 0.4f) - { - CreateCube(new Vector3(x, 1.2f, z), parent, data.mainColor); - } - } - - // 头部 - for (float x = -0.6f; x <= 0.6f; x += 0.3f) - { - for (float y = 1.2f; y <= 2f; y += 0.4f) - { - CreateCube(new Vector3(x, y, 1.2f), parent, data.mainColor); - } - } - + Color gray = data.mainColor; + Color pink = data.secondaryColor; + + // 大圆头 + CreatePart(new Vector3(0, 1f, 0.1f), parent, gray, PrimitiveType.Sphere, new Vector3(0.7f, 0.65f, 0.6f)); + + // 超大扇形耳朵 - 大象最明显的特征 + // 左耳 + CreatePart(new Vector3(-0.5f, 0.95f, 0.05f), parent, gray, PrimitiveType.Sphere, new Vector3(0.45f, 0.55f, 0.08f)); + // 粉色内耳 + CreatePart(new Vector3(-0.5f, 0.95f, 0.1f), parent, pink, PrimitiveType.Sphere, new Vector3(0.3f, 0.4f, 0.04f)); + // 右耳 + CreatePart(new Vector3(0.5f, 0.95f, 0.05f), parent, gray, PrimitiveType.Sphere, new Vector3(0.45f, 0.55f, 0.08f)); + // 粉色内耳 + CreatePart(new Vector3(0.5f, 0.95f, 0.1f), parent, pink, PrimitiveType.Sphere, new Vector3(0.3f, 0.4f, 0.04f)); + // 眼睛 - CreateCube(new Vector3(-0.4f, 1.8f, 1.4f), parent, Color.black); - CreateCube(new Vector3(0.4f, 1.8f, 1.4f), parent, Color.black); - - // 大耳朵 - for (float y = 1.4f; y <= 2f; y += 0.3f) - { - for (float z = 0.9f; z <= 1.5f; z += 0.3f) - { - CreateCube(new Vector3(-1f, y, z), parent, data.secondaryColor); - CreateCube(new Vector3(1f, y, z), parent, data.secondaryColor); - } - } - - // 长鼻子 - float trunkLength = 2f; - int segments = 8; - for (int i = 0; i < segments; i++) - { - float t = i / (float)(segments - 1); - float angle = t * Mathf.PI * 0.5f; - Vector3 pos = new Vector3( - 0, - 1.6f - Mathf.Sin(angle) * trunkLength, - 1.4f + Mathf.Cos(angle) * trunkLength - ); - CreateCube(pos, parent, data.mainColor); - } - - // 粗壮的腿 - float legHeight = 1.2f; - float legWidth = 0.6f; - for (float y = 0; y < legHeight; y += 0.3f) - { - for (float x = -legWidth; x <= legWidth; x += 0.3f) - { - CreateCube(new Vector3(x, y, -0.8f), parent, data.mainColor); - CreateCube(new Vector3(x, y, 0.8f), parent, data.mainColor); - } - } + CreatePart(new Vector3(-0.2f, 1.05f, 0.35f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.05f)); + CreatePart(new Vector3(0.2f, 1.05f, 0.35f), parent, Color.black, PrimitiveType.Sphere, new Vector3(0.08f, 0.08f, 0.05f)); + + // 长鼻子 - 大象第二明显特征 (用多个圆柱连接) + CreatePart(new Vector3(0, 0.85f, 0.45f), parent, gray, PrimitiveType.Sphere, new Vector3(0.2f, 0.2f, 0.2f)); + CreatePart(new Vector3(0, 0.65f, 0.55f), parent, gray, PrimitiveType.Capsule, new Vector3(0.12f, 0.15f, 0.12f), new Vector3(20, 0, 0)); + CreatePart(new Vector3(0, 0.4f, 0.6f), parent, gray, PrimitiveType.Capsule, new Vector3(0.1f, 0.15f, 0.1f), new Vector3(10, 0, 0)); + CreatePart(new Vector3(0, 0.15f, 0.55f), parent, gray, PrimitiveType.Capsule, new Vector3(0.09f, 0.12f, 0.09f), new Vector3(-20, 0, 0)); + // 鼻子末端 + CreatePart(new Vector3(0, 0.08f, 0.4f), parent, gray, PrimitiveType.Sphere, new Vector3(0.1f, 0.08f, 0.12f)); + + // 椭圆身体 + CreatePart(new Vector3(0, 0.5f, -0.25f), parent, gray, PrimitiveType.Sphere, new Vector3(0.55f, 0.5f, 0.7f)); + + // 粗腿 + CreatePart(new Vector3(-0.22f, 0.2f, 0.1f), parent, gray, PrimitiveType.Cylinder, new Vector3(0.12f, 0.2f, 0.12f)); + CreatePart(new Vector3(0.22f, 0.2f, 0.1f), parent, gray, PrimitiveType.Cylinder, new Vector3(0.12f, 0.2f, 0.12f)); + CreatePart(new Vector3(-0.22f, 0.2f, -0.45f), parent, gray, PrimitiveType.Cylinder, new Vector3(0.12f, 0.2f, 0.12f)); + CreatePart(new Vector3(0.22f, 0.2f, -0.45f), parent, gray, PrimitiveType.Cylinder, new Vector3(0.12f, 0.2f, 0.12f)); + + // 小尾巴 + CreatePart(new Vector3(0, 0.55f, -0.6f), parent, gray, PrimitiveType.Capsule, new Vector3(0.04f, 0.15f, 0.04f), new Vector3(-30, 0, 0)); + // 尾巴末端毛 + CreatePart(new Vector3(0, 0.38f, -0.68f), parent, new Color(0.4f, 0.4f, 0.45f), PrimitiveType.Sphere, new Vector3(0.06f, 0.08f, 0.04f)); } void OnDestroy() diff --git a/Assets/Scripts/BlockHighlight.cs b/Assets/Scripts/BlockHighlight.cs index 07f11f9d..9e121a17 100644 --- a/Assets/Scripts/BlockHighlight.cs +++ b/Assets/Scripts/BlockHighlight.cs @@ -122,6 +122,20 @@ public void SetTarget(Vector3 position, bool visible) transform.position = position; SetVisible(visible); } + + /// + /// 设置高亮目标位置 (Vector3Int - 方块坐标) + /// + public void SetTarget(Vector3Int blockPosition, bool visible) + { + // Convert to world position (center of block) + transform.position = new Vector3( + blockPosition.x + 0.5f, + blockPosition.y + 0.5f, + blockPosition.z + 0.5f + ); + SetVisible(visible); + } /// /// 设置可见性 diff --git a/Assets/Scripts/BlockInteractionSystem.cs b/Assets/Scripts/BlockInteractionSystem.cs index 16cf19b3..374ebfa4 100644 --- a/Assets/Scripts/BlockInteractionSystem.cs +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -1,83 +1,72 @@ using UnityEngine; /// -/// 方块交互系统 - 处理方块的放置和破坏 +/// Block interaction system - handles block placement and destruction +/// Uses optimized math-based raycast instead of physics raycast /// public class BlockInteractionSystem : MonoBehaviour { - [Header("交互设置")] - public float interactionRange = 5f; // 交互距离 - public LayerMask blockLayer; // 方块层级 - public KeyCode destroyKey = KeyCode.Mouse0; // 破坏方块按键(左键) - public KeyCode placeKey = KeyCode.Mouse1; // 放置方块按键(右键) - public float destroyTime = 0.5f; // 破坏所需时间(秒) - - [Header("方块设置")] - public GameObject cubePrefab; // 方块预制体 - public float blockSize = 1f; // 方块大小 - - [Header("视觉反馈")] - public bool showBlockHighlight = true; // 是否显示高亮 + [Header("Interaction Settings")] + public float interactionRange = 5f; + public KeyCode destroyKey = KeyCode.Mouse0; + public KeyCode placeKey = KeyCode.Mouse1; + public float destroyTime = 0.5f; + + [Header("Visual Feedback")] + public bool showBlockHighlight = true; public Color highlightColor = new Color(1f, 1f, 1f, 0.3f); - - [Header("音效")] + + [Header("Audio")] public AudioClip destroySound; public AudioClip placeSound; - + private Camera playerCamera; private BlockHighlight blockHighlight; private SimpleInventory inventory; private AudioSource audioSource; - - // 当前瞄准的方块信息 - private GameObject targetBlock; - private Vector3 targetBlockPosition; - private Vector3 placePosition; + private CubeGenerator cubeGenerator; + + // Current target info + private Vector3Int targetBlockPos; + private Vector3 targetNormal; private bool hasTarget; - - // 破坏进度 + + // Destroy progress private float destroyProgress = 0f; - private Vector3 lastDestroyPosition; + private Vector3Int lastDestroyPos; private bool isDestroying = false; - + void Start() { InitializeComponents(); } - + void InitializeComponents() { - // 获取玩家相机 + // Get player camera playerCamera = GetComponentInChildren(); if (playerCamera == null) { playerCamera = Camera.main; } - - // 获取或创建背包组件 + + // Get or create inventory inventory = GetComponent(); if (inventory == null) { inventory = gameObject.AddComponent(); } - - // 获取方块预制体 - if (cubePrefab == null) - { - CubeGenerator cubeGen = FindObjectOfType(); - if (cubeGen != null && cubeGen.cubePrefab != null) - { - cubePrefab = cubeGen.cubePrefab; - } - } - - // 创建高亮显示组件 + + // Get CubeGenerator reference + cubeGenerator = FindObjectOfType(); + + // Create highlight if (showBlockHighlight) { CreateBlockHighlight(); } - - // 设置音频源 + + // Setup audio audioSource = GetComponent(); if (audioSource == null) { @@ -85,94 +74,93 @@ void InitializeComponents() audioSource.spatialBlend = 0f; audioSource.playOnAwake = false; } - - // 设置默认层级(如果未设置) - if (blockLayer == 0) - { - blockLayer = ~0; // 所有层级 - } - - Debug.Log("BlockInteractionSystem: 方块交互系统已初始化"); + + Debug.Log("BlockInteractionSystem: Initialized with optimized raycast"); } - + void CreateBlockHighlight() { GameObject highlightObj = new GameObject("BlockHighlight"); blockHighlight = highlightObj.AddComponent(); blockHighlight.highlightColor = highlightColor; - blockHighlight.blockSize = blockSize; + blockHighlight.blockSize = 1f; } - + void Update() { UpdateTargetBlock(); HandleInput(); } - + /// - /// 更新当前瞄准的方块 + /// Update current target block using raycast /// void UpdateTargetBlock() { if (playerCamera == null) return; - + Ray ray = playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)); - RaycastHit hit; - - if (Physics.Raycast(ray, out hit, interactionRange, blockLayer)) + + // Try math raycast first + hasTarget = ChunkRaycast.Raycast(ray, interactionRange, out targetBlockPos, out targetNormal); + + // Fallback to physics raycast if math raycast fails + if (!hasTarget) { - hasTarget = true; - targetBlock = hit.collider.gameObject; - - // 计算方块中心位置(对齐到网格) - targetBlockPosition = GetBlockPosition(hit.point - hit.normal * 0.1f); - - // 计算放置位置(在命中面的外侧) - placePosition = GetBlockPosition(hit.point + hit.normal * 0.5f); - - // 更新高亮显示 - if (blockHighlight != null) + RaycastHit hit; + if (Physics.Raycast(ray, out hit, interactionRange)) { - blockHighlight.SetTarget(targetBlockPosition, true); + hasTarget = true; + // Calculate block position from hit point + Vector3 blockCenter = hit.point - hit.normal * 0.5f; + targetBlockPos = new Vector3Int( + Mathf.FloorToInt(blockCenter.x), + Mathf.FloorToInt(blockCenter.y), + Mathf.FloorToInt(blockCenter.z) + ); + targetNormal = hit.normal; } } - else + + // Update highlight + if (blockHighlight != null) { - hasTarget = false; - targetBlock = null; - - if (blockHighlight != null) + if (hasTarget) + { + blockHighlight.SetTarget(targetBlockPos, true); + } + else { blockHighlight.SetTarget(Vector3.zero, false); } } } - + /// - /// 处理输入 + /// Handle input for block interaction /// void HandleInput() { - // 破坏方块(长按) + // Destroy block (hold) if (Input.GetKey(destroyKey) && hasTarget) { - // 检查是否切换了目标方块 - if (targetBlockPosition != lastDestroyPosition) + // Check if target changed + if (targetBlockPos != lastDestroyPos) { destroyProgress = 0f; - lastDestroyPosition = targetBlockPosition; + lastDestroyPos = targetBlockPos; } - + isDestroying = true; destroyProgress += Time.deltaTime; - - // 更新高亮显示破坏进度 + + // Update highlight progress if (blockHighlight != null) { blockHighlight.SetDestroyProgress(destroyProgress / destroyTime); } - - // 破坏完成 + + // Destroy complete if (destroyProgress >= destroyTime) { DestroyBlock(); @@ -181,7 +169,7 @@ void HandleInput() } else { - // 松开按键,重置进度 + // Reset progress when released if (isDestroying) { isDestroying = false; @@ -192,233 +180,122 @@ void HandleInput() } } } - - // 放置方块(单击) + + // Place block (click) if (Input.GetKeyDown(placeKey) && hasTarget) { PlaceBlock(); } } - + /// - /// 破坏方块 + /// Destroy the target block /// void DestroyBlock() { - if (targetBlock == null) return; - - // 获取方块颜色用于背包 - Renderer renderer = targetBlock.GetComponent(); - Color blockColor = Color.white; - if (renderer != null && renderer.material != null) + // Get block type for inventory + BlockType blockType = BlockType.Dirt; // Default + Color blockColor = BlockTypeConfig.GetColor(BlockType.Dirt); + + if (WorldData.Instance != null) { - blockColor = renderer.material.color; + blockType = WorldData.Instance.GetBlock(targetBlockPos); + if (blockType != BlockType.Air) + { + blockColor = BlockTypeConfig.GetColor(blockType); + } + // Update world data + WorldData.Instance.SetBlock(targetBlockPos, BlockType.Air); } - - // 记录被破坏方块的位置 - Vector3 destroyedPos = targetBlockPosition; - - // 添加到背包 + + // Add to inventory inventory.AddBlock(blockColor); - - // 播放音效 + + // Play sound PlaySound(destroySound); - - // 创建破坏粒子效果 - CreateDestroyEffect(targetBlockPosition, blockColor); - - // 销毁方块 - Destroy(targetBlock); - - // 检查并生成下方隐藏的方块 - RevealHiddenBlocks(destroyedPos); - - Debug.Log($"BlockInteractionSystem: 破坏方块于 {targetBlockPosition}"); - } - - /// - /// 破坏方块后,检查并生成周围被隐藏的方块 - /// - void RevealHiddenBlocks(Vector3 destroyedPos) - { - // 方块已经全部生成,不需要额外揭露逻辑 - } - - /// - /// 获取地形高度 - /// - float GetTerrainHeight(int x, int z, CubeGenerator cubeGen) - { - float scale = cubeGen.noiseScale; - int seed = cubeGen.seed; - float heightScale = cubeGen.heightScale; - - float height = Mathf.PerlinNoise((x + seed) / scale, (z + seed) / scale) * heightScale; - height += Mathf.PerlinNoise((x + seed) / (scale * 0.5f), (z + seed) / (scale * 0.5f)) * 2; - - return height; - } - - /// - /// 根据深度获取方块颜色 - /// - Color GetBlockColorForDepth(int depth, int surfaceHeight, CubeGenerator cubeGen) - { - if (depth == 0) - { - // 表面层 - int waterLevel = 3; - if (surfaceHeight < waterLevel - 1) - return cubeGen.stoneColor; - else if (surfaceHeight < waterLevel) - return cubeGen.sandColor; - else if (surfaceHeight < 8) - return cubeGen.grassColor; - else if (surfaceHeight < 12) - return cubeGen.dirtColor; - else - return cubeGen.stoneColor; - } - else if (depth < 3) - { - return cubeGen.dirtColor; - } - else - { - return cubeGen.stoneColor; - } - } - - /// - /// 创建被揭露的方块 - /// - void CreateRevealedBlock(Vector3 position, Color color) - { - if (cubePrefab == null) return; - - GameObject newBlock = Instantiate(cubePrefab, position, Quaternion.identity); - newBlock.name = "RevealedBlock"; - - Renderer renderer = newBlock.GetComponent(); - if (renderer != null) - { - Material mat = new Material(Shader.Find("Standard")); - mat.color = color; - renderer.material = mat; - } - - if (newBlock.GetComponent() == null) + + // Create effect + CreateDestroyEffect(targetBlockPos, blockColor); + + // Notify chunk system to rebuild mesh + if (cubeGenerator != null) { - newBlock.AddComponent(); + cubeGenerator.OnBlockChanged(targetBlockPos); } + + Debug.Log($"BlockInteractionSystem: Destroyed block at {targetBlockPos}"); } - + /// - /// 放置方块 + /// Place a block /// void PlaceBlock() { - if (cubePrefab == null) - { - Debug.LogWarning("BlockInteractionSystem: 未设置方块预制体"); - return; - } - - // 检查背包是否有方块 + // Check inventory if (!inventory.HasBlocks()) { - Debug.Log("BlockInteractionSystem: 背包中没有方块"); + Debug.Log("BlockInteractionSystem: No blocks in inventory"); return; } - - // 检查放置位置是否与玩家重叠 - if (IsPositionOccupiedByPlayer(placePosition)) + + // Calculate place position + Vector3Int placePos = ChunkRaycast.GetPlacePosition(targetBlockPos, targetNormal); + + // Check if position is occupied by player + if (IsPositionOccupiedByPlayer(placePos)) { - Debug.Log("BlockInteractionSystem: 无法在玩家位置放置方块"); + Debug.Log("BlockInteractionSystem: Cannot place block at player position"); return; } - - // 检查位置是否已有方块 - if (IsPositionOccupied(placePosition)) + + // Check if position is already occupied (using physics) + Vector3 checkPos = new Vector3(placePos.x + 0.5f, placePos.y + 0.5f, placePos.z + 0.5f); + if (Physics.CheckBox(checkPos, Vector3.one * 0.4f)) { - Debug.Log("BlockInteractionSystem: 该位置已有方块"); + Debug.Log("BlockInteractionSystem: Position already occupied"); return; } - - // 从背包取出方块 + + // Get block from inventory Color blockColor = inventory.RemoveBlock(); - - // 创建新方块 - GameObject newBlock = Instantiate(cubePrefab, placePosition, Quaternion.identity); - newBlock.name = "PlacedBlock"; - - // 设置颜色 - Renderer renderer = newBlock.GetComponent(); - if (renderer != null) + BlockType blockType = BlockTypeConfig.GetBlockTypeFromColor(blockColor); + + // Update world data + if (WorldData.Instance != null) { - Material mat = new Material(Shader.Find("Standard")); - mat.color = blockColor; - renderer.material = mat; + WorldData.Instance.SetBlock(placePos, blockType); } - - // 确保有碰撞体 - if (newBlock.GetComponent() == null) + + // Notify chunk system to rebuild mesh + if (cubeGenerator != null) { - newBlock.AddComponent(); + cubeGenerator.OnBlockChanged(placePos); } - - // 播放音效 + + // Play sound PlaySound(placeSound); - - // 创建放置粒子效果 - CreatePlaceEffect(placePosition, blockColor); - - Debug.Log($"BlockInteractionSystem: 放置方块于 {placePosition}"); - } - - /// - /// 将世界坐标对齐到方块网格 - /// - Vector3 GetBlockPosition(Vector3 worldPos) - { - return new Vector3( - Mathf.Round(worldPos.x / blockSize) * blockSize, - Mathf.Round(worldPos.y / blockSize) * blockSize, - Mathf.Round(worldPos.z / blockSize) * blockSize - ); + // Create effect + CreatePlaceEffect(placePos, blockColor); + + Debug.Log($"BlockInteractionSystem: Placed block at {placePos}"); } - + /// - /// 检查位置是否被玩家占用 + /// Check if position is occupied by player /// - bool IsPositionOccupiedByPlayer(Vector3 position) + bool IsPositionOccupiedByPlayer(Vector3Int position) { Vector3 playerPos = transform.position; float playerHeight = 2f; - - // 检查方块是否与玩家碰撞盒重叠 - if (Mathf.Abs(position.x - playerPos.x) < blockSize && - position.y >= playerPos.y - 0.5f && position.y <= playerPos.y + playerHeight && - Mathf.Abs(position.z - playerPos.z) < blockSize) - { - return true; - } - - return false; - } - - /// - /// 检查位置是否已有方块 - /// - bool IsPositionOccupied(Vector3 position) - { - Collider[] colliders = Physics.OverlapSphere(position, blockSize * 0.4f, blockLayer); - return colliders.Length > 0; + + return Mathf.Abs(position.x - playerPos.x) < 1f && + position.y >= playerPos.y - 0.5f && position.y <= playerPos.y + playerHeight && + Mathf.Abs(position.z - playerPos.z) < 1f; } - + /// - /// 播放音效 + /// Play sound effect /// void PlaySound(AudioClip clip) { @@ -427,98 +304,102 @@ void PlaySound(AudioClip clip) audioSource.PlayOneShot(clip); } } - + /// - /// 创建破坏粒子效果 + /// Create destroy particle effect /// - void CreateDestroyEffect(Vector3 position, Color color) + void CreateDestroyEffect(Vector3Int position, Color color) { - // 创建简单的粒子效果 GameObject effectObj = new GameObject("DestroyEffect"); - effectObj.transform.position = position; - + effectObj.transform.position = new Vector3(position.x + 0.5f, position.y + 0.5f, position.z + 0.5f); + ParticleSystem ps = effectObj.AddComponent(); + ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + var main = ps.main; + main.duration = 0.1f; + main.loop = false; main.startLifetime = 0.5f; main.startSpeed = 3f; main.startSize = 0.15f; main.startColor = color; main.gravityModifier = 1f; main.maxParticles = 20; - main.duration = 0.1f; - main.loop = false; - + var emission = ps.emission; emission.rateOverTime = 0; emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 15) }); - + var shape = ps.shape; shape.shapeType = ParticleSystemShapeType.Sphere; shape.radius = 0.3f; - + var renderer = ps.GetComponent(); renderer.material = new Material(Shader.Find("Particles/Standard Unlit")); renderer.material.color = color; - + ps.Play(); - + Destroy(effectObj, 1f); } - + /// - /// 创建放置粒子效果 + /// Create place particle effect /// - void CreatePlaceEffect(Vector3 position, Color color) + void CreatePlaceEffect(Vector3Int position, Color color) { GameObject effectObj = new GameObject("PlaceEffect"); - effectObj.transform.position = position; - + effectObj.transform.position = new Vector3(position.x + 0.5f, position.y + 0.5f, position.z + 0.5f); + ParticleSystem ps = effectObj.AddComponent(); + ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + var main = ps.main; + main.duration = 0.1f; + main.loop = false; main.startLifetime = 0.3f; main.startSpeed = 1f; main.startSize = 0.1f; main.startColor = new Color(color.r, color.g, color.b, 0.5f); main.gravityModifier = 0f; main.maxParticles = 10; - main.duration = 0.1f; - main.loop = false; - + var emission = ps.emission; emission.rateOverTime = 0; emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 8) }); - + var shape = ps.shape; shape.shapeType = ParticleSystemShapeType.Box; - shape.scale = Vector3.one * blockSize; - + shape.scale = Vector3.one; + var renderer = ps.GetComponent(); renderer.material = new Material(Shader.Find("Particles/Standard Unlit")); renderer.material.color = color; - + ps.Play(); - + Destroy(effectObj, 0.5f); } - + /// - /// 获取当前瞄准的方块位置 + /// Get current target block position /// public Vector3 GetTargetBlockPosition() { - return targetBlockPosition; + return new Vector3(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); } - + /// - /// 获取当前放置位置 + /// Get current place position /// public Vector3 GetPlacePosition() { - return placePosition; + Vector3Int placePos = ChunkRaycast.GetPlacePosition(targetBlockPos, targetNormal); + return new Vector3(placePos.x, placePos.y, placePos.z); } - + /// - /// 是否有瞄准目标 + /// Check if has target /// public bool HasTarget() { diff --git a/Assets/Scripts/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 3f76146a..869b9e97 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -1,20 +1,22 @@ using UnityEngine; using System.Collections.Generic; +/// +/// Generates terrain using optimized chunk-based mesh merging +/// public class CubeGenerator : MonoBehaviour { - [Header("方块设置")] - public GameObject cubePrefab; + [Header("Chunk Settings")] public int chunkSize = 16; - public int renderDistance = 2; // 减小渲染距离提升性能 - - [Header("地形设置")] + public int renderDistance = 2; + + [Header("Terrain Settings")] public float noiseScale = 20f; public float heightScale = 10f; public int seed; - public int terrainDepth = 5; // 地形厚度(层数) - - [Header("颜色设置")] + public int terrainDepth = 5; + + [Header("Colors (for reference)")] public Color grassColor = new Color(0.4f, 0.7f, 0.2f); public Color dirtColor = new Color(0.6f, 0.4f, 0.2f); public Color stoneColor = new Color(0.5f, 0.5f, 0.5f); @@ -22,69 +24,76 @@ public class CubeGenerator : MonoBehaviour public Color sandColor = new Color(0.9f, 0.8f, 0.5f); public Color treeColor = new Color(0.3f, 0.2f, 0.1f); public Color leafColor = new Color(0.2f, 0.5f, 0.1f); - - private Dictionary chunks = new Dictionary(); + + // Legacy field for BlockInteractionSystem compatibility + public GameObject cubePrefab; + + // Chunk management using new renderer system + private Dictionary chunkRenderers = new Dictionary(); private Transform player; private Vector2Int currentChunk = new Vector2Int(0, 0); private Vector3 lastPlayerPosition = Vector3.zero; private float distanceMoved = 0; - + + // WorldData reference + private WorldData worldData; + + // Chunk update queue for gradual loading + private Queue chunkLoadQueue = new Queue(); + private const int MAX_CHUNKS_PER_FRAME = 2; + void Start() { - // 设置随机种子 + // Setup random seed if (seed == 0) seed = Random.Range(1, 99999); Random.InitState(seed); - - // 延迟初始化,等待玩家生成 + + // Ensure WorldData exists + worldData = GetComponent(); + if (worldData == null) + { + worldData = gameObject.AddComponent(); + } + + // Delayed initialization Invoke("InitializeWorld", 0.5f); } - + void InitializeWorld() { - // 首先尝试从GameManager获取玩家引用 + // Get player reference if (GameManager.Instance != null && GameManager.Instance.playerTransform != null) { player = GameManager.Instance.playerTransform; - lastPlayerPosition = player.position; - - // 生成初始区块 - UpdateChunks(); } else { - // 查找玩家 GameObject playerObj = GameObject.FindWithTag("Player"); if (playerObj != null) { player = playerObj.transform; - lastPlayerPosition = player.position; - - // 生成初始区块 - UpdateChunks(); } else if (Camera.main != null) { player = Camera.main.transform; - lastPlayerPosition = player.position; - - // 生成初始区块 - UpdateChunks(); } else { - // 如果还没找到相机,继续尝试 Invoke("InitializeWorld", 0.5f); + return; } } + + lastPlayerPosition = player.position; + UpdateChunks(); } - + void Update() { - // 确保有玩家 + // Ensure player reference if (player == null) { - // 首先尝试从GameManager获取玩家引用 if (GameManager.Instance != null && GameManager.Instance.playerTransform != null) { player = GameManager.Instance.playerTransform; @@ -105,333 +114,298 @@ void Update() } else { - return; // 如果没有玩家,不执行更新 + return; } } } - - // 计算玩家移动距离 + + // Track player movement distanceMoved += Vector3.Distance(player.position, lastPlayerPosition); lastPlayerPosition = player.position; - - // 获取玩家当前所在区块 + + // Get current chunk Vector2Int newChunk = new Vector2Int( Mathf.FloorToInt(player.position.x / chunkSize), Mathf.FloorToInt(player.position.z / chunkSize) ); - - // 每帧都检查一下玩家位置,确保不会掉出地图 + + // Respawn if fallen if (player.position.y < -10) { - // 找到GameManager并重置玩家位置 GameManager manager = GameManager.Instance; if (manager != null) { manager.RespawnPlayer(); } } - - // 如果玩家移动到新区块或移动了足够远的距离,更新可见区块 + + // Update chunks when player moves if (newChunk != currentChunk || distanceMoved > chunkSize / 2) { currentChunk = newChunk; distanceMoved = 0; UpdateChunks(); } - - // 每5秒强制更新一次区块,确保地形正确生成 + + // Process chunk load queue + ProcessChunkQueue(); + + // Periodic update if (Time.frameCount % 300 == 0) { UpdateChunks(); } } - + void UpdateChunks() { - if (player == null) - { - return; - } - - // 记录需要保留的区块 + if (player == null) return; + HashSet neededChunks = new HashSet(); - - // 计算需要生成的区块范围 + + // Calculate needed chunks for (int x = -renderDistance; x <= renderDistance; x++) { for (int z = -renderDistance; z <= renderDistance; z++) { Vector2Int chunkPos = new Vector2Int(currentChunk.x + x, currentChunk.y + z); neededChunks.Add(chunkPos); - - // 如果区块不存在,生成它 - if (!chunks.ContainsKey(chunkPos)) + + // Queue chunk for loading if not exists + if (!chunkRenderers.ContainsKey(chunkPos) && !chunkLoadQueue.Contains(chunkPos)) { - GameObject newChunk = new GameObject($"Chunk_{chunkPos.x}_{chunkPos.y}"); - newChunk.transform.parent = transform; - chunks[chunkPos] = newChunk; - GenerateChunk(chunkPos, newChunk.transform); + chunkLoadQueue.Enqueue(chunkPos); } } } - - // 移除不再需要的区块 + + // Remove unneeded chunks List chunksToRemove = new List(); - foreach (var chunk in chunks) + foreach (var kvp in chunkRenderers) { - if (!neededChunks.Contains(chunk.Key)) + if (!neededChunks.Contains(kvp.Key)) { - chunksToRemove.Add(chunk.Key); + chunksToRemove.Add(kvp.Key); } } - + foreach (var chunkPos in chunksToRemove) { - Destroy(chunks[chunkPos]); - chunks.Remove(chunkPos); + chunkRenderers[chunkPos].Unload(); + chunkRenderers.Remove(chunkPos); + worldData.RemoveChunk(chunkPos); + } + } + + void ProcessChunkQueue() + { + int processed = 0; + while (chunkLoadQueue.Count > 0 && processed < MAX_CHUNKS_PER_FRAME) + { + Vector2Int chunkPos = chunkLoadQueue.Dequeue(); + + // Skip if already loaded + if (chunkRenderers.ContainsKey(chunkPos)) + continue; + + LoadChunk(chunkPos); + processed++; } } - - void GenerateChunk(Vector2Int chunkPos, Transform parent) + + void LoadChunk(Vector2Int chunkPos) + { + // Get or create chunk data + ChunkData chunkData = worldData.GetOrCreateChunk(chunkPos); + + // Generate terrain data if empty + if (chunkData.IsEmpty()) + { + GenerateChunkData(chunkPos, chunkData); + } + + // Create renderer + GameObject chunkObj = new GameObject($"Chunk_{chunkPos.x}_{chunkPos.y}"); + chunkObj.transform.parent = transform; + + ChunkRenderer renderer = chunkObj.AddComponent(); + renderer.Initialize(chunkData, chunkPos); + + chunkRenderers[chunkPos] = renderer; + } + + /// + /// Generate terrain data for a chunk (no GameObjects created) + /// + void GenerateChunkData(Vector2Int chunkPos, ChunkData chunkData) { int startX = chunkPos.x * chunkSize; int startZ = chunkPos.y * chunkSize; - - // 生成地形 + for (int x = 0; x < chunkSize; x++) { for (int z = 0; z < chunkSize; z++) { int worldX = startX + x; int worldZ = startZ + z; - - // 使用柏林噪声生成高度 + + // Generate height using Perlin noise float height = GenerateHeight(worldX, worldZ); int intHeight = Mathf.FloorToInt(height); - - // 生成完整深度的地形方块 + + // Generate terrain layers for (int depth = 0; depth < terrainDepth; depth++) { int y = intHeight - depth; - - Color blockColor; + if (y < 0 || y >= ChunkData.HEIGHT) continue; + + BlockType blockType; if (depth == 0) { - blockColor = GetTerrainColor(intHeight, intHeight); + blockType = GetTerrainBlockType(intHeight); } else if (depth < 3) { - blockColor = dirtColor; + blockType = BlockType.Dirt; } else { - blockColor = stoneColor; + blockType = BlockType.Stone; } - - CreateCube(new Vector3(worldX, y, worldZ), blockColor, parent); + + chunkData.SetBlock(x, y, z, blockType); } - - // 生成水面 + + // Generate water int waterLevel = 3; if (intHeight < waterLevel) { - CreateCube(new Vector3(worldX, waterLevel, worldZ), waterColor, parent, true); + for (int y = intHeight + 1; y <= waterLevel; y++) + { + if (y >= 0 && y < ChunkData.HEIGHT) + { + chunkData.SetBlock(x, y, z, BlockType.Water); + } + } } - - // 随机生成树木 + + // Generate trees if (Random.value < 0.02f && intHeight > waterLevel) { - GenerateTree(new Vector3(worldX, intHeight + 1, worldZ), parent); + GenerateTreeData(chunkData, x, intHeight + 1, z); } } } } - + float GenerateHeight(int x, int z) { - // 使用多层柏林噪声生成自然地形 float scale = noiseScale; - float height = 0; height += Mathf.PerlinNoise((x + seed) / scale, (z + seed) / scale) * heightScale; - - // 添加一些小的细节变化 height += Mathf.PerlinNoise((x + seed) / (scale * 0.5f), (z + seed) / (scale * 0.5f)) * 2; - return height; } - - Color GetTerrainColor(int height, float exactHeight) + + BlockType GetTerrainBlockType(int height) { - // 根据高度返回不同的颜色 int waterLevel = 3; - + if (height < waterLevel - 1) - return stoneColor; // 水下石头 + return BlockType.Stone; else if (height < waterLevel) - return sandColor; // 沙滩 + return BlockType.Sand; else if (height < 8) - return grassColor; // 草地 + return BlockType.Grass; else if (height < 12) - return dirtColor; // 泥土 + return BlockType.Dirt; else - return stoneColor; // 山石 + return BlockType.Stone; } - - void GenerateTree(Vector3 position, Transform parent) + + void GenerateTreeData(ChunkData chunkData, int x, int baseY, int z) { - // 树干 int treeHeight = Random.Range(3, 6); + + // Tree trunk for (int y = 0; y < treeHeight; y++) { - CreateCube(position + new Vector3(0, y, 0), treeColor, parent); + int blockY = baseY + y; + if (blockY >= 0 && blockY < ChunkData.HEIGHT && + x >= 0 && x < ChunkData.SIZE && z >= 0 && z < ChunkData.SIZE) + { + chunkData.SetBlock(x, blockY, z, BlockType.Wood); + } } - - // 树叶 + + // Tree leaves int leafSize = Random.Range(2, 4); - for (int x = -leafSize; x <= leafSize; x++) + for (int lx = -leafSize; lx <= leafSize; lx++) { - for (int z = -leafSize; z <= leafSize; z++) + for (int lz = -leafSize; lz <= leafSize; lz++) { - for (int y = 0; y < leafSize; y++) + for (int ly = 0; ly < leafSize; ly++) { - // 创建球形树冠 - if (x*x + y*y + z*z <= leafSize*leafSize) + if (lx * lx + ly * ly + lz * lz <= leafSize * leafSize) { - Vector3 leafPos = position + new Vector3(x, treeHeight + y, z); - CreateCube(leafPos, leafColor, parent); + int leafX = x + lx; + int leafY = baseY + treeHeight + ly; + int leafZ = z + lz; + + // Only place within chunk bounds + if (leafX >= 0 && leafX < ChunkData.SIZE && + leafY >= 0 && leafY < ChunkData.HEIGHT && + leafZ >= 0 && leafZ < ChunkData.SIZE) + { + // Don't overwrite trunk + if (chunkData.GetBlock(leafX, leafY, leafZ) == BlockType.Air) + { + chunkData.SetBlock(leafX, leafY, leafZ, BlockType.Leaf); + } + } } } } } } - - // 缓存不同类型的材质 - private Material grassMaterial; - private Material dirtMaterial; - private Material stoneMaterial; - private Material waterMaterial; - private Material sandMaterial; - private Material treeMaterial; - private Material leafMaterial; - - // 材质缓存时间 - private float materialCacheTime = 3.0f; - private float lastMaterialRefreshTime = 0f; - - void CreateCube(Vector3 position, Color color, Transform parent, bool isTransparent = false) + + /// + /// Called when a block is changed (from BlockInteractionSystem) + /// + public void OnBlockChanged(Vector3Int worldPos) { - GameObject cube = Instantiate(cubePrefab, position, Quaternion.identity, parent); - - // 设置方块颜色 - Renderer renderer = cube.GetComponent(); - if (renderer != null) + var (chunkPos, localPos) = WorldData.WorldToChunk(worldPos); + + // Rebuild affected chunk + if (chunkRenderers.TryGetValue(chunkPos, out ChunkRenderer renderer)) { - // 根据颜色选择或创建共享材质 - Material material = GetMaterialForColor(color, isTransparent); - renderer.sharedMaterial = material; + renderer.SetDirty(); } + + // Check if at chunk boundary and mark neighbors dirty + if (localPos.x == 0) + MarkChunkDirty(chunkPos + Vector2Int.left); + if (localPos.x == ChunkData.SIZE - 1) + MarkChunkDirty(chunkPos + Vector2Int.right); + if (localPos.z == 0) + MarkChunkDirty(chunkPos + Vector2Int.down); + if (localPos.z == ChunkData.SIZE - 1) + MarkChunkDirty(chunkPos + Vector2Int.up); } - - // 根据颜色获取共享材质 - Material GetMaterialForColor(Color color, bool isTransparent) + + private void MarkChunkDirty(Vector2Int chunkPos) { - // 检查是否需要刷新材质缓存 - if (Time.time - lastMaterialRefreshTime > materialCacheTime) - { - // 清除所有材质缓存 - grassMaterial = null; - dirtMaterial = null; - stoneMaterial = null; - waterMaterial = null; - sandMaterial = null; - treeMaterial = null; - leafMaterial = null; - - // 更新刷新时间戳 - lastMaterialRefreshTime = Time.time; - } - - // 比较颜色,返回对应的共享材质 - if (color == grassColor) - { - if (grassMaterial == null) - { - grassMaterial = CreateMaterial(color, isTransparent); - } - return grassMaterial; - } - else if (color == dirtColor) - { - if (dirtMaterial == null) - { - dirtMaterial = CreateMaterial(color, isTransparent); - } - return dirtMaterial; - } - else if (color == stoneColor) - { - if (stoneMaterial == null) - { - stoneMaterial = CreateMaterial(color, isTransparent); - } - return stoneMaterial; - } - else if (color == waterColor) - { - if (waterMaterial == null) - { - waterMaterial = CreateMaterial(color, true); // 水总是透明的 - } - return waterMaterial; - } - else if (color == sandColor) + if (chunkRenderers.TryGetValue(chunkPos, out ChunkRenderer renderer)) { - if (sandMaterial == null) - { - sandMaterial = CreateMaterial(color, isTransparent); - } - return sandMaterial; + renderer.SetDirty(); } - else if (color == treeColor) - { - if (treeMaterial == null) - { - treeMaterial = CreateMaterial(color, isTransparent); - } - return treeMaterial; - } - else if (color == leafColor) - { - if (leafMaterial == null) - { - leafMaterial = CreateMaterial(color, isTransparent); - } - return leafMaterial; - } - - // 如果是其他颜色,创建一个新材质(这种情况应该很少发生) - return CreateMaterial(color, isTransparent); } - - // 创建材质的辅助方法 - Material CreateMaterial(Color color, bool isTransparent) + + /// + /// Get terrain height at world position (for spawning) + /// + public float GetTerrainHeightAt(float x, float z) { - Material material = new Material(Shader.Find("Standard")); - material.color = color; - - if (isTransparent) - { - material.SetFloat("_Mode", 3); // 透明模式 - material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); - material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); - material.SetInt("_ZWrite", 0); - material.DisableKeyword("_ALPHATEST_ON"); - material.EnableKeyword("_ALPHABLEND_ON"); - material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); - material.renderQueue = 3000; - } - - return material; + return GenerateHeight(Mathf.FloorToInt(x), Mathf.FloorToInt(z)); } } diff --git a/Assets/Scripts/World.meta b/Assets/Scripts/World.meta new file mode 100644 index 00000000..6745678e --- /dev/null +++ b/Assets/Scripts/World.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: XXNJ5i34BXOrfJFscl1xMTjleyd5iTAMBPMsHo1zst3aejoo9WGYdmg= +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/BlockType.cs b/Assets/Scripts/World/BlockType.cs new file mode 100644 index 00000000..6d31423c --- /dev/null +++ b/Assets/Scripts/World/BlockType.cs @@ -0,0 +1,126 @@ +using UnityEngine; + +/// +/// Block type enumeration - uses byte for memory efficiency +/// +public enum BlockType : byte +{ + Air = 0, + Grass = 1, + Dirt = 2, + Stone = 3, + Sand = 4, + Water = 5, + Wood = 6, + Leaf = 7 +} + +/// +/// Block type configuration - provides color and properties for each block type +/// +public static class BlockTypeConfig +{ + // Default colors (used if CubeGenerator not found) + private static Color[] defaultColors = new Color[] + { + new Color(0, 0, 0, 0), // Air - transparent + new Color(0.4f, 0.7f, 0.2f), // Grass - green + new Color(0.6f, 0.4f, 0.2f), // Dirt - brown + new Color(0.5f, 0.5f, 0.5f), // Stone - gray + new Color(0.9f, 0.8f, 0.5f), // Sand - yellow + new Color(0.2f, 0.4f, 0.8f, 0.7f), // Water - blue, semi-transparent + new Color(0.3f, 0.2f, 0.1f), // Wood - dark brown + new Color(0.2f, 0.5f, 0.1f) // Leaf - dark green + }; + + // Cached reference to CubeGenerator + private static CubeGenerator cachedGenerator; + + /// + /// Get colors from CubeGenerator Inspector settings + /// + public static Color[] Colors + { + get + { + // Try to get CubeGenerator reference + if (cachedGenerator == null) + { + cachedGenerator = Object.FindObjectOfType(); + } + + if (cachedGenerator != null) + { + return new Color[] + { + new Color(0, 0, 0, 0), // Air + cachedGenerator.grassColor, // Grass + cachedGenerator.dirtColor, // Dirt + cachedGenerator.stoneColor, // Stone + cachedGenerator.sandColor, // Sand + cachedGenerator.waterColor, // Water + cachedGenerator.treeColor, // Wood + cachedGenerator.leafColor // Leaf + }; + } + + return defaultColors; + } + } + + /// + /// Check if block type is solid (blocks light and collision) + /// + public static bool IsSolid(BlockType type) + { + return type != BlockType.Air && type != BlockType.Water; + } + + /// + /// Check if block type is transparent + /// + public static bool IsTransparent(BlockType type) + { + return type == BlockType.Air || type == BlockType.Water; + } + + /// + /// Get color for block type + /// + public static Color GetColor(BlockType type) + { + int index = (int)type; + if (index >= 0 && index < Colors.Length) + return Colors[index]; + return Color.magenta; // Error color + } + + /// + /// Find closest block type for a given color + /// + public static BlockType GetBlockTypeFromColor(Color color) + { + float minDistance = float.MaxValue; + BlockType closest = BlockType.Dirt; + + for (int i = 1; i < Colors.Length; i++) // Skip Air + { + BlockType type = (BlockType)i; + if (type == BlockType.Water) continue; // Skip water + + float dist = ColorDistance(color, Colors[i]); + if (dist < minDistance) + { + minDistance = dist; + closest = type; + } + } + + return closest; + } + + private static float ColorDistance(Color a, Color b) + { + return Mathf.Abs(a.r - b.r) + Mathf.Abs(a.g - b.g) + Mathf.Abs(a.b - b.b); + } +} diff --git a/Assets/Scripts/World/BlockType.cs.meta b/Assets/Scripts/World/BlockType.cs.meta new file mode 100644 index 00000000..e9ba6160 --- /dev/null +++ b/Assets/Scripts/World/BlockType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: Cn5Os3ikVS9jlsondSaBx6FWFLk407NE5/cXonhG17xQnz9+5kOJJyA= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/ChunkData.cs b/Assets/Scripts/World/ChunkData.cs new file mode 100644 index 00000000..d4ae058c --- /dev/null +++ b/Assets/Scripts/World/ChunkData.cs @@ -0,0 +1,106 @@ +using UnityEngine; + +/// +/// Stores block data for a single chunk using a 3D byte array +/// Memory efficient: 16 * 64 * 16 = 16,384 bytes per chunk +/// +public class ChunkData +{ + public const int SIZE = 16; // Chunk width and depth + public const int HEIGHT = 64; // Chunk height (supports tall worlds) + + // 3D array storing block types as bytes + private byte[,,] blocks; + + // Track if chunk has been modified (for save/load) + public bool IsDirty { get; private set; } + + public ChunkData() + { + blocks = new byte[SIZE, HEIGHT, SIZE]; + IsDirty = false; + } + + /// + /// Get block type at local coordinates + /// Returns Air for out-of-bounds coordinates + /// + public BlockType GetBlock(int x, int y, int z) + { + if (x < 0 || x >= SIZE || y < 0 || y >= HEIGHT || z < 0 || z >= SIZE) + return BlockType.Air; + return (BlockType)blocks[x, y, z]; + } + + /// + /// Set block type at local coordinates + /// + public void SetBlock(int x, int y, int z, BlockType type) + { + if (x >= 0 && x < SIZE && y >= 0 && y < HEIGHT && z >= 0 && z < SIZE) + { + blocks[x, y, z] = (byte)type; + IsDirty = true; + } + } + + /// + /// Check if block at position is solid (not air or water) + /// + public bool IsBlockSolid(int x, int y, int z) + { + return BlockTypeConfig.IsSolid(GetBlock(x, y, z)); + } + + /// + /// Check if block at position is transparent + /// + public bool IsBlockTransparent(int x, int y, int z) + { + return BlockTypeConfig.IsTransparent(GetBlock(x, y, z)); + } + + /// + /// Check if chunk is empty (all air) + /// + public bool IsEmpty() + { + for (int x = 0; x < SIZE; x++) + { + for (int y = 0; y < HEIGHT; y++) + { + for (int z = 0; z < SIZE; z++) + { + if (blocks[x, y, z] != (byte)BlockType.Air) + return false; + } + } + } + return true; + } + + /// + /// Clear dirty flag after saving or mesh rebuild + /// + public void ClearDirty() + { + IsDirty = false; + } + + /// + /// Get the highest non-air block at given x,z position + /// Returns -1 if column is empty + /// + public int GetHighestBlock(int x, int z) + { + if (x < 0 || x >= SIZE || z < 0 || z >= SIZE) + return -1; + + for (int y = HEIGHT - 1; y >= 0; y--) + { + if (blocks[x, y, z] != (byte)BlockType.Air) + return y; + } + return -1; + } +} diff --git a/Assets/Scripts/World/ChunkData.cs.meta b/Assets/Scripts/World/ChunkData.cs.meta new file mode 100644 index 00000000..868e9ff5 --- /dev/null +++ b/Assets/Scripts/World/ChunkData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: WygWt36tVXuVGPkcTrxFsZ2JNoOG4J7KJRNzqHAgJfO7ogJ82CzKSnA= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/ChunkMeshBuilder.cs b/Assets/Scripts/World/ChunkMeshBuilder.cs new file mode 100644 index 00000000..be80ff96 --- /dev/null +++ b/Assets/Scripts/World/ChunkMeshBuilder.cs @@ -0,0 +1,217 @@ +using UnityEngine; +using System.Collections.Generic; + +/// +/// Builds optimized mesh for a chunk with face culling +/// Only renders faces exposed to air/water, reducing vertex count significantly +/// +public class ChunkMeshBuilder +{ + // Face directions (order: Top, Bottom, Right, Left, Front, Back) + private static readonly Vector3Int[] FaceDirections = new Vector3Int[] + { + Vector3Int.up, // Top (Y+) + Vector3Int.down, // Bottom (Y-) + Vector3Int.right, // Right (X+) + Vector3Int.left, // Left (X-) + new Vector3Int(0, 0, 1), // Front (Z+) + new Vector3Int(0, 0, -1) // Back (Z-) + }; + + // Vertex offsets for each face (4 vertices per face, counter-clockwise) + private static readonly Vector3[][] FaceVertices = new Vector3[][] + { + // Top (Y+) + new Vector3[] { new Vector3(0,1,0), new Vector3(0,1,1), new Vector3(1,1,1), new Vector3(1,1,0) }, + // Bottom (Y-) + new Vector3[] { new Vector3(0,0,1), new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,0,1) }, + // Right (X+) + new Vector3[] { new Vector3(1,0,0), new Vector3(1,1,0), new Vector3(1,1,1), new Vector3(1,0,1) }, + // Left (X-) + new Vector3[] { new Vector3(0,0,1), new Vector3(0,1,1), new Vector3(0,1,0), new Vector3(0,0,0) }, + // Front (Z+) + new Vector3[] { new Vector3(1,0,1), new Vector3(1,1,1), new Vector3(0,1,1), new Vector3(0,0,1) }, + // Back (Z-) + new Vector3[] { new Vector3(0,0,0), new Vector3(0,1,0), new Vector3(1,1,0), new Vector3(1,0,0) } + }; + + // Normal vectors for each face + private static readonly Vector3[] FaceNormals = new Vector3[] + { + Vector3.up, + Vector3.down, + Vector3.right, + Vector3.left, + Vector3.forward, + Vector3.back + }; + + // Triangle indices for a quad (two triangles) + private static readonly int[] QuadTriangles = new int[] { 0, 1, 2, 0, 2, 3 }; + + private ChunkData chunkData; + private Vector2Int chunkPosition; + + // Mesh data lists + private List vertices = new List(); + private List triangles = new List(); + private List normals = new List(); + private List colors = new List(); + + // Separate lists for water (transparent) mesh + private List waterVertices = new List(); + private List waterTriangles = new List(); + private List waterNormals = new List(); + private List waterColors = new List(); + + public ChunkMeshBuilder(ChunkData data, Vector2Int chunkPos) + { + chunkData = data; + chunkPosition = chunkPos; + } + + /// + /// Build the chunk mesh with face culling optimization + /// + public void BuildMesh(out Mesh solidMesh, out Mesh waterMesh) + { + // Clear previous data + vertices.Clear(); + triangles.Clear(); + normals.Clear(); + colors.Clear(); + waterVertices.Clear(); + waterTriangles.Clear(); + waterNormals.Clear(); + waterColors.Clear(); + + // Iterate through all blocks in chunk + for (int x = 0; x < ChunkData.SIZE; x++) + { + for (int y = 0; y < ChunkData.HEIGHT; y++) + { + for (int z = 0; z < ChunkData.SIZE; z++) + { + BlockType blockType = chunkData.GetBlock(x, y, z); + if (blockType == BlockType.Air) + continue; + + bool isWater = (blockType == BlockType.Water); + Vector3 blockPos = new Vector3(x, y, z); + Color blockColor = BlockTypeConfig.GetColor(blockType); + + // Check each face + for (int face = 0; face < 6; face++) + { + Vector3Int neighborPos = new Vector3Int(x, y, z) + FaceDirections[face]; + + // Check if face should be rendered + if (ShouldRenderFace(neighborPos, isWater)) + { + if (isWater) + { + AddFace(waterVertices, waterTriangles, waterNormals, waterColors, + blockPos, face, blockColor); + } + else + { + AddFace(vertices, triangles, normals, colors, + blockPos, face, blockColor); + } + } + } + } + } + } + + // Create solid mesh + solidMesh = new Mesh(); + solidMesh.name = $"Chunk_{chunkPosition.x}_{chunkPosition.y}_Solid"; + if (vertices.Count > 0) + { + solidMesh.SetVertices(vertices); + solidMesh.SetTriangles(triangles, 0); + solidMesh.SetNormals(normals); + solidMesh.SetColors(colors); + solidMesh.RecalculateBounds(); + } + + // Create water mesh + waterMesh = new Mesh(); + waterMesh.name = $"Chunk_{chunkPosition.x}_{chunkPosition.y}_Water"; + if (waterVertices.Count > 0) + { + waterMesh.SetVertices(waterVertices); + waterMesh.SetTriangles(waterTriangles, 0); + waterMesh.SetNormals(waterNormals); + waterMesh.SetColors(waterColors); + waterMesh.RecalculateBounds(); + } + } + + /// + /// Check if a face should be rendered (neighbor is air or transparent) + /// + private bool ShouldRenderFace(Vector3Int localPos, bool isWaterBlock) + { + // Check bounds within chunk + if (localPos.x >= 0 && localPos.x < ChunkData.SIZE && + localPos.y >= 0 && localPos.y < ChunkData.HEIGHT && + localPos.z >= 0 && localPos.z < ChunkData.SIZE) + { + BlockType neighbor = chunkData.GetBlock(localPos.x, localPos.y, localPos.z); + + // Water blocks only render faces next to air, not next to other water + if (isWaterBlock) + { + return neighbor == BlockType.Air; + } + + // Solid blocks render faces next to air or water + return BlockTypeConfig.IsTransparent(neighbor); + } + + // At chunk boundary - check neighboring chunk + // For simplicity, render faces at chunk boundaries + // A more complete implementation would check the neighboring chunk + if (localPos.y < 0) + return false; // Don't render bottom of world + if (localPos.y >= ChunkData.HEIGHT) + return true; // Always render top + + // For horizontal boundaries, render the face + // This could be optimized by checking neighboring chunks + return true; + } + + /// + /// Add a face to the mesh data + /// + private void AddFace(List verts, List tris, List norms, List cols, + Vector3 blockPos, int faceIndex, Color color) + { + int vertexStart = verts.Count; + + // Add 4 vertices for this face + for (int i = 0; i < 4; i++) + { + verts.Add(blockPos + FaceVertices[faceIndex][i]); + norms.Add(FaceNormals[faceIndex]); + cols.Add(color); + } + + // Add 6 triangle indices (2 triangles = 1 quad) + foreach (int triIndex in QuadTriangles) + { + tris.Add(vertexStart + triIndex); + } + } + + /// + /// Get statistics about the built mesh + /// + public (int solidVerts, int solidTris, int waterVerts, int waterTris) GetStats() + { + return (vertices.Count, triangles.Count / 3, waterVertices.Count, waterTriangles.Count / 3); + } +} diff --git a/Assets/Scripts/World/ChunkMeshBuilder.cs.meta b/Assets/Scripts/World/ChunkMeshBuilder.cs.meta new file mode 100644 index 00000000..c3d82423 --- /dev/null +++ b/Assets/Scripts/World/ChunkMeshBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: Wn0Wt3uvAXoocaZeeN9YZN864D1JiINBjgwCNbVShXnvEdWR8q+jhj8= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/ChunkRaycast.cs b/Assets/Scripts/World/ChunkRaycast.cs new file mode 100644 index 00000000..343f6998 --- /dev/null +++ b/Assets/Scripts/World/ChunkRaycast.cs @@ -0,0 +1,174 @@ +using UnityEngine; + +/// +/// Performs raycast against voxel world using DDA (Digital Differential Analyzer) algorithm +/// More efficient than physics raycast for voxel worlds +/// +public static class ChunkRaycast +{ + /// + /// Cast a ray through the voxel world and find the first solid block hit + /// + /// Ray origin in world coordinates + /// Ray direction (will be normalized) + /// Maximum raycast distance + /// Output: world position of hit block + /// Output: normal of the face that was hit + /// True if a block was hit + public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, + out Vector3Int hitBlock, out Vector3 hitNormal) + { + hitBlock = Vector3Int.zero; + hitNormal = Vector3.zero; + + if (WorldData.Instance == null) + return false; + + direction = direction.normalized; + + // Handle zero direction components + if (Mathf.Approximately(direction.x, 0)) direction.x = 0.0001f; + if (Mathf.Approximately(direction.y, 0)) direction.y = 0.0001f; + if (Mathf.Approximately(direction.z, 0)) direction.z = 0.0001f; + + // Current block position + Vector3Int mapPos = new Vector3Int( + Mathf.FloorToInt(origin.x), + Mathf.FloorToInt(origin.y), + Mathf.FloorToInt(origin.z) + ); + + // Step direction (+1 or -1) + Vector3Int step = new Vector3Int( + direction.x >= 0 ? 1 : -1, + direction.y >= 0 ? 1 : -1, + direction.z >= 0 ? 1 : -1 + ); + + // Distance to travel along ray to cross one cell in each direction + Vector3 tDelta = new Vector3( + Mathf.Abs(1f / direction.x), + Mathf.Abs(1f / direction.y), + Mathf.Abs(1f / direction.z) + ); + + // Distance to next cell boundary + Vector3 tMax = new Vector3( + step.x > 0 ? (mapPos.x + 1 - origin.x) * tDelta.x : (origin.x - mapPos.x) * tDelta.x, + step.y > 0 ? (mapPos.y + 1 - origin.y) * tDelta.y : (origin.y - mapPos.y) * tDelta.y, + step.z > 0 ? (mapPos.z + 1 - origin.z) * tDelta.z : (origin.z - mapPos.z) * tDelta.z + ); + + float distance = 0f; + int lastAxis = -1; // Track which axis we last stepped along + + // Maximum iterations to prevent infinite loop + int maxIterations = Mathf.CeilToInt(maxDistance) * 3 + 10; + int iterations = 0; + + while (distance < maxDistance && iterations < maxIterations) + { + iterations++; + + // Skip the first check if we're inside the starting block + if (iterations > 1 || !IsInsideBlock(origin, mapPos)) + { + // Check if current block is solid + BlockType block = WorldData.Instance.GetBlock(mapPos); + if (BlockTypeConfig.IsSolid(block)) + { + hitBlock = mapPos; + + // Determine which face was hit based on last step + switch (lastAxis) + { + case 0: hitNormal = new Vector3(-step.x, 0, 0); break; + case 1: hitNormal = new Vector3(0, -step.y, 0); break; + case 2: hitNormal = new Vector3(0, 0, -step.z); break; + default: hitNormal = -direction; break; + } + + return true; + } + } + + // Step to next block + if (tMax.x < tMax.y && tMax.x < tMax.z) + { + distance = tMax.x; + tMax.x += tDelta.x; + mapPos.x += step.x; + lastAxis = 0; + } + else if (tMax.y < tMax.z) + { + distance = tMax.y; + tMax.y += tDelta.y; + mapPos.y += step.y; + lastAxis = 1; + } + else + { + distance = tMax.z; + tMax.z += tDelta.z; + mapPos.z += step.z; + lastAxis = 2; + } + } + + return false; + } + + /// + /// Raycast using Unity Ray struct + /// + public static bool Raycast(Ray ray, float maxDistance, out Vector3Int hitBlock, out Vector3 hitNormal) + { + return Raycast(ray.origin, ray.direction, maxDistance, out hitBlock, out hitNormal); + } + + /// + /// Check if a point is inside a block's bounding box + /// + private static bool IsInsideBlock(Vector3 point, Vector3Int blockPos) + { + return point.x >= blockPos.x && point.x < blockPos.x + 1 && + point.y >= blockPos.y && point.y < blockPos.y + 1 && + point.z >= blockPos.z && point.z < blockPos.z + 1; + } + + /// + /// Get the exact hit point on the block surface + /// + public static Vector3 GetHitPoint(Vector3 origin, Vector3 direction, Vector3Int hitBlock, Vector3 hitNormal) + { + direction = direction.normalized; + + // Calculate plane of the hit face + Vector3 planePoint = new Vector3(hitBlock.x, hitBlock.y, hitBlock.z); + + // Adjust plane point based on which face was hit + if (hitNormal.x > 0) planePoint.x = hitBlock.x; + else if (hitNormal.x < 0) planePoint.x = hitBlock.x + 1; + else if (hitNormal.y > 0) planePoint.y = hitBlock.y; + else if (hitNormal.y < 0) planePoint.y = hitBlock.y + 1; + else if (hitNormal.z > 0) planePoint.z = hitBlock.z; + else if (hitNormal.z < 0) planePoint.z = hitBlock.z + 1; + + // Ray-plane intersection + float denom = Vector3.Dot(hitNormal, direction); + if (Mathf.Abs(denom) < 0.0001f) + return planePoint; // Ray parallel to plane + + float t = Vector3.Dot(planePoint - origin, hitNormal) / denom; + return origin + direction * t; + } + + /// + /// Calculate the position where a block would be placed + /// + public static Vector3Int GetPlacePosition(Vector3Int hitBlock, Vector3 hitNormal) + { + return hitBlock + Vector3Int.RoundToInt(hitNormal); + } +} diff --git a/Assets/Scripts/World/ChunkRaycast.cs.meta b/Assets/Scripts/World/ChunkRaycast.cs.meta new file mode 100644 index 00000000..79f9b2ec --- /dev/null +++ b/Assets/Scripts/World/ChunkRaycast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: XHlLvS/7AS1yThQNzXqnVoAaOjwSD1lqW9RXBa/mKaG/2oVNpF1PrPc= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/ChunkRenderer.cs b/Assets/Scripts/World/ChunkRenderer.cs new file mode 100644 index 00000000..4d3ee4df --- /dev/null +++ b/Assets/Scripts/World/ChunkRenderer.cs @@ -0,0 +1,248 @@ +using UnityEngine; + +/// +/// Renders a single chunk using merged mesh +/// Handles mesh rebuilding when blocks change +/// +public class ChunkRenderer : MonoBehaviour +{ + private ChunkData chunkData; + private Vector2Int chunkPosition; + + // Solid mesh components + private MeshFilter meshFilter; + private MeshRenderer meshRenderer; + private MeshCollider meshCollider; + private Mesh solidMesh; + + // Water mesh components (separate for transparency) + private GameObject waterObject; + private MeshFilter waterMeshFilter; + private MeshRenderer waterMeshRenderer; + private Mesh waterMesh; + + // Materials + private static Material solidMaterial; + private static Material waterMaterial; + + // Dirty flag for deferred mesh rebuild + private bool isDirty = false; + private bool isInitialized = false; + + /// + /// Initialize the chunk renderer + /// + public void Initialize(ChunkData data, Vector2Int pos) + { + chunkData = data; + chunkPosition = pos; + + // Set world position + transform.position = new Vector3( + pos.x * ChunkData.SIZE, + 0, + pos.y * ChunkData.SIZE + ); + + gameObject.name = $"Chunk_{pos.x}_{pos.y}"; + + // Create materials if not exist + CreateMaterials(); + + // Setup solid mesh components + meshFilter = gameObject.AddComponent(); + meshRenderer = gameObject.AddComponent(); + meshCollider = gameObject.AddComponent(); + + meshRenderer.sharedMaterial = solidMaterial; + meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On; + meshRenderer.receiveShadows = true; + + // Setup water mesh (child object for separate rendering) + waterObject = new GameObject("Water"); + waterObject.transform.parent = transform; + waterObject.transform.localPosition = Vector3.zero; + + waterMeshFilter = waterObject.AddComponent(); + waterMeshRenderer = waterObject.AddComponent(); + + waterMeshRenderer.sharedMaterial = waterMaterial; + waterMeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + waterMeshRenderer.receiveShadows = true; + + isInitialized = true; + + // Build initial mesh + RebuildMesh(); + } + + /// + /// Create shared materials for all chunks + /// + private void CreateMaterials() + { + if (solidMaterial == null) + { + // Try shaders in order of preference + string[] shaderNames = new string[] + { + "Custom/VertexColor", + "Custom/VertexColorUnlit", // Simple unlit vertex color shader + "Particles/Standard Unlit", // Unity built-in, supports vertex colors + "Sprites/Default", // Unity built-in, supports vertex colors + "Legacy Shaders/Diffuse", + "Diffuse", + "Standard" + }; + + Shader shader = null; + foreach (string name in shaderNames) + { + shader = Shader.Find(name); + if (shader != null) break; + } + + solidMaterial = new Material(shader); + solidMaterial.enableInstancing = true; + + // For Particles/Standard Unlit, set color mode + if (shader.name.Contains("Particles")) + { + solidMaterial.SetFloat("_ColorMode", 1); // Multiply + } + } + + if (waterMaterial == null) + { + // Try transparent shaders + string[] waterShaderNames = new string[] + { + "Custom/VertexColorTransparent", + "Custom/VertexColorUnlit", + "Particles/Standard Unlit", + "Sprites/Default", + "Legacy Shaders/Transparent/Diffuse", + "Transparent/Diffuse", + "Standard" + }; + + Shader shader = null; + foreach (string name in waterShaderNames) + { + shader = Shader.Find(name); + if (shader != null) break; + } + + waterMaterial = new Material(shader); + waterMaterial.renderQueue = 3000; + + // Configure transparency for Standard shader + if (shader.name == "Standard") + { + waterMaterial.SetFloat("_Mode", 3); + waterMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + waterMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + waterMaterial.SetInt("_ZWrite", 0); + waterMaterial.EnableKeyword("_ALPHABLEND_ON"); + } + } + } + + /// + /// Mark chunk as needing mesh rebuild + /// + public void SetDirty() + { + isDirty = true; + } + + void LateUpdate() + { + // Deferred mesh rebuild + if (isDirty && isInitialized) + { + RebuildMesh(); + isDirty = false; + } + } + + /// + /// Rebuild the chunk mesh + /// + public void RebuildMesh() + { + if (chunkData == null) return; + + // Build new meshes + ChunkMeshBuilder builder = new ChunkMeshBuilder(chunkData, chunkPosition); + builder.BuildMesh(out Mesh newSolidMesh, out Mesh newWaterMesh); + + // Update solid mesh + if (solidMesh != null) + { + Destroy(solidMesh); + } + solidMesh = newSolidMesh; + meshFilter.sharedMesh = solidMesh; + + // Update collider + if (solidMesh.vertexCount > 0) + { + meshCollider.sharedMesh = solidMesh; + } + else + { + meshCollider.sharedMesh = null; + } + + // Update water mesh + if (waterMesh != null) + { + Destroy(waterMesh); + } + waterMesh = newWaterMesh; + waterMeshFilter.sharedMesh = waterMesh; + + // Enable/disable water object based on content + waterObject.SetActive(waterMesh.vertexCount > 0); + + // Clear dirty flag on chunk data + chunkData.ClearDirty(); + } + + /// + /// Get chunk position + /// + public Vector2Int GetChunkPosition() + { + return chunkPosition; + } + + /// + /// Clean up when chunk is unloaded + /// + public void Unload() + { + if (solidMesh != null) + { + Destroy(solidMesh); + } + if (waterMesh != null) + { + Destroy(waterMesh); + } + Destroy(gameObject); + } + + void OnDestroy() + { + if (solidMesh != null) + { + Destroy(solidMesh); + } + if (waterMesh != null) + { + Destroy(waterMesh); + } + } +} diff --git a/Assets/Scripts/World/ChunkRenderer.cs.meta b/Assets/Scripts/World/ChunkRenderer.cs.meta new file mode 100644 index 00000000..393b3029 --- /dev/null +++ b/Assets/Scripts/World/ChunkRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: XHhKsSKpWyp7LHxun/amxcf3BDzeENFt4jJvuCLJJFUTAY+UkX07T3M= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/World/WorldData.cs b/Assets/Scripts/World/WorldData.cs new file mode 100644 index 00000000..6a37a578 --- /dev/null +++ b/Assets/Scripts/World/WorldData.cs @@ -0,0 +1,142 @@ +using UnityEngine; +using System.Collections.Generic; + +/// +/// Manages all chunk data for the world +/// Provides world coordinate to chunk coordinate conversion +/// +public class WorldData : MonoBehaviour +{ + public static WorldData Instance { get; private set; } + + private Dictionary chunks = new Dictionary(); + + void Awake() + { + if (Instance == null) + { + Instance = this; + } + else + { + Destroy(gameObject); + } + } + + /// + /// Get existing chunk data or create new one + /// + public ChunkData GetOrCreateChunk(Vector2Int chunkPos) + { + if (!chunks.TryGetValue(chunkPos, out ChunkData data)) + { + data = new ChunkData(); + chunks[chunkPos] = data; + } + return data; + } + + /// + /// Get chunk data if it exists + /// + public ChunkData GetChunk(Vector2Int chunkPos) + { + chunks.TryGetValue(chunkPos, out ChunkData data); + return data; + } + + /// + /// Check if chunk exists + /// + public bool HasChunk(Vector2Int chunkPos) + { + return chunks.ContainsKey(chunkPos); + } + + /// + /// Remove chunk data (for unloading) + /// + public void RemoveChunk(Vector2Int chunkPos) + { + chunks.Remove(chunkPos); + } + + /// + /// Convert world position to chunk position and local position + /// + public static (Vector2Int chunkPos, Vector3Int localPos) WorldToChunk(Vector3Int worldPos) + { + Vector2Int chunkPos = new Vector2Int( + Mathf.FloorToInt(worldPos.x / (float)ChunkData.SIZE), + Mathf.FloorToInt(worldPos.z / (float)ChunkData.SIZE) + ); + + // Handle negative coordinates correctly + int localX = ((worldPos.x % ChunkData.SIZE) + ChunkData.SIZE) % ChunkData.SIZE; + int localZ = ((worldPos.z % ChunkData.SIZE) + ChunkData.SIZE) % ChunkData.SIZE; + + Vector3Int localPos = new Vector3Int(localX, worldPos.y, localZ); + return (chunkPos, localPos); + } + + /// + /// Convert world position to chunk position + /// + public static Vector2Int WorldToChunkPos(Vector3 worldPos) + { + return new Vector2Int( + Mathf.FloorToInt(worldPos.x / ChunkData.SIZE), + Mathf.FloorToInt(worldPos.z / ChunkData.SIZE) + ); + } + + /// + /// Convert chunk position and local position to world position + /// + public static Vector3Int ChunkToWorld(Vector2Int chunkPos, Vector3Int localPos) + { + return new Vector3Int( + chunkPos.x * ChunkData.SIZE + localPos.x, + localPos.y, + chunkPos.y * ChunkData.SIZE + localPos.z + ); + } + + /// + /// Get block at world coordinates + /// + public BlockType GetBlock(Vector3Int worldPos) + { + var (chunkPos, localPos) = WorldToChunk(worldPos); + ChunkData chunk = GetChunk(chunkPos); + if (chunk == null) + return BlockType.Air; + return chunk.GetBlock(localPos.x, localPos.y, localPos.z); + } + + /// + /// Set block at world coordinates + /// + public void SetBlock(Vector3Int worldPos, BlockType type) + { + var (chunkPos, localPos) = WorldToChunk(worldPos); + ChunkData chunk = GetOrCreateChunk(chunkPos); + chunk.SetBlock(localPos.x, localPos.y, localPos.z, type); + } + + /// + /// Check if block at world position is solid + /// + public bool IsBlockSolid(Vector3Int worldPos) + { + return BlockTypeConfig.IsSolid(GetBlock(worldPos)); + } + + /// + /// Get all loaded chunk positions + /// + public IEnumerable GetLoadedChunks() + { + return chunks.Keys; + } +} diff --git a/Assets/Scripts/World/WorldData.cs.meta b/Assets/Scripts/World/WorldData.cs.meta new file mode 100644 index 00000000..ed1e7402 --- /dev/null +++ b/Assets/Scripts/World/WorldData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: XnxLtCquVHNnN1O3iJhqkp6gdwuK4q43ZcuybvFs60CLWXlNuiQIu1A= +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders.meta b/Assets/Shaders.meta new file mode 100644 index 00000000..68c7e82b --- /dev/null +++ b/Assets/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: Dnsfti+kAC9eTytaDDkD/eD5wELUIP3p1Zri2Olt6/lTatOHqqHIzVk= +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/VertexColor.shader b/Assets/Shaders/VertexColor.shader new file mode 100644 index 00000000..2d3615c3 --- /dev/null +++ b/Assets/Shaders/VertexColor.shader @@ -0,0 +1,41 @@ +Shader "Custom/VertexColor" +{ + Properties + { + _Glossiness ("Smoothness", Range(0,1)) = 0.1 + _Metallic ("Metallic", Range(0,1)) = 0.0 + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 200 + + CGPROGRAM + #pragma surface surf Standard fullforwardshadows vertex:vert + #pragma target 3.0 + + struct Input + { + float4 vertexColor; + }; + + half _Glossiness; + half _Metallic; + + void vert(inout appdata_full v, out Input o) + { + UNITY_INITIALIZE_OUTPUT(Input, o); + o.vertexColor = v.color; + } + + void surf(Input IN, inout SurfaceOutputStandard o) + { + o.Albedo = IN.vertexColor.rgb; + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + o.Alpha = IN.vertexColor.a; + } + ENDCG + } + FallBack "Diffuse" +} diff --git a/Assets/Shaders/VertexColor.shader.meta b/Assets/Shaders/VertexColor.shader.meta new file mode 100644 index 00000000..e8f3f986 --- /dev/null +++ b/Assets/Shaders/VertexColor.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: C3wXtHz/VHlefHtKpeb3JfRidNuQ2KH5pJGtNVscx1Phm4QHm67ID08= +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/VertexColorTransparent.shader b/Assets/Shaders/VertexColorTransparent.shader new file mode 100644 index 00000000..217d9e06 --- /dev/null +++ b/Assets/Shaders/VertexColorTransparent.shader @@ -0,0 +1,41 @@ +Shader "Custom/VertexColorTransparent" +{ + Properties + { + _Glossiness ("Smoothness", Range(0,1)) = 0.8 + _Metallic ("Metallic", Range(0,1)) = 0.0 + } + SubShader + { + Tags { "Queue"="Transparent" "RenderType"="Transparent" } + LOD 200 + + CGPROGRAM + #pragma surface surf Standard fullforwardshadows alpha:fade vertex:vert + #pragma target 3.0 + + struct Input + { + float4 vertexColor; + }; + + half _Glossiness; + half _Metallic; + + void vert(inout appdata_full v, out Input o) + { + UNITY_INITIALIZE_OUTPUT(Input, o); + o.vertexColor = v.color; + } + + void surf(Input IN, inout SurfaceOutputStandard o) + { + o.Albedo = IN.vertexColor.rgb; + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + o.Alpha = IN.vertexColor.a; + } + ENDCG + } + FallBack "Transparent/Diffuse" +} diff --git a/Assets/Shaders/VertexColorTransparent.shader.meta b/Assets/Shaders/VertexColorTransparent.shader.meta new file mode 100644 index 00000000..a59dc0f1 --- /dev/null +++ b/Assets/Shaders/VertexColorTransparent.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: CylL4Hv7UHywzCUbcPE0U6QjRF8QqdjAzGxbCPttNjJfYp25vsRU8fo= +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/VertexColorUnlit.shader b/Assets/Shaders/VertexColorUnlit.shader new file mode 100644 index 00000000..c763d2ac --- /dev/null +++ b/Assets/Shaders/VertexColorUnlit.shader @@ -0,0 +1,51 @@ +Shader "Custom/VertexColorUnlit" +{ + Properties + { + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile_fog + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float4 color : COLOR; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float4 color : COLOR; + UNITY_FOG_COORDS(0) + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.color = v.color; + UNITY_TRANSFER_FOG(o, o.vertex); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + fixed4 col = i.color; + UNITY_APPLY_FOG(i.fogCoord, col); + return col; + } + ENDCG + } + } +} diff --git a/Assets/Shaders/VertexColorUnlit.shader.meta b/Assets/Shaders/VertexColorUnlit.shader.meta new file mode 100644 index 00000000..177fe3de --- /dev/null +++ b/Assets/Shaders/VertexColorUnlit.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: Xn9Jsnz4WyqbveYgcHR8M33qVP5irxi8+oLlY3m6/iuLQRDasvm7b6k= +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: From 976d9991028b19d5911a32974f58cdd9789407bb Mon Sep 17 00:00:00 2001 From: Bao Cao Date: Sun, 11 Jan 2026 20:44:18 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E5=8A=A8?= =?UTF-8?q?=E7=89=A9=E7=B3=BB=E7=BB=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 大幅扩展 Animal.cs 功能 - 优化 AnimalManager.cs 逻辑 --- Assets/Scripts/Animal.cs | 498 ++++++++++++++++++++++++++++++-- Assets/Scripts/AnimalManager.cs | 34 +-- 2 files changed, 496 insertions(+), 36 deletions(-) diff --git a/Assets/Scripts/Animal.cs b/Assets/Scripts/Animal.cs index 63912e19..a6fa4e53 100644 --- a/Assets/Scripts/Animal.cs +++ b/Assets/Scripts/Animal.cs @@ -1,5 +1,6 @@ using UnityEngine; using System.Collections; +using System.Collections.Generic; public class Animal : MonoBehaviour { @@ -9,18 +10,40 @@ public class Animal : MonoBehaviour public float jumpForce = 3f; public float idleTime = 2f; public float moveTime = 3f; - + [Header("颜色设置")] public Color mainColor = Color.white; public Color secondaryColor = Color.gray; - + + [Header("动画设置")] + public float animationSpeed = 1f; + protected bool isMoving = false; protected Vector3 moveDirection; protected float actionTimer; protected bool isJumping = false; protected float groundY; - protected float gravity = 20f; // 添加重力 - protected float verticalVelocity = 0f; // 垂直速度 + protected float gravity = 15f; // 降低重力,让跳跃更明显 + protected float verticalVelocity = 0f; + + // 动画相关 + protected float animTime = 0f; + protected List bodyParts = new List(); + protected Transform headPart; + protected Transform tailPart; + protected List legParts = new List(); + protected List earParts = new List(); + protected Vector3 originalScale; + protected Dictionary originalPositions = new Dictionary(); + protected Dictionary originalRotations = new Dictionary(); + protected float targetRotationY; + protected float currentRotationY; + + // 玩家交互 + protected Transform playerTransform; + protected float playerDetectRadius = 5f; + protected bool isAlerted = false; + protected float alertCooldown = 0f; public enum AnimalType { @@ -43,18 +66,332 @@ protected virtual void Start() transform.position = hit.point; groundY = hit.point.y; } - + + // 初始化动画系统 + InitializeAnimationParts(); + originalScale = transform.localScale; + currentRotationY = transform.eulerAngles.y; + targetRotationY = currentRotationY; + + // 查找玩家 + if (GameManager.Instance != null) + { + playerTransform = GameManager.Instance.playerTransform; + } + if (playerTransform == null) + { + GameObject player = GameObject.FindGameObjectWithTag("Player"); + if (player != null) playerTransform = player.transform; + } + StartCoroutine(ActionRoutine()); } - + protected virtual void Update() { ApplyGravity(); - + if (isMoving && !isJumping) { Move(); } + + // 更新动画 + UpdateAnimations(); + + // 平滑转向 + UpdateRotation(); + } + + /// + /// 初始化动画部件引用 + /// + protected virtual void InitializeAnimationParts() + { + // 遍历所有子物体,根据位置分类 + foreach (Transform child in transform) + { + // 记录原始位置和旋转 + originalPositions[child] = child.localPosition; + originalRotations[child] = child.localRotation; + bodyParts.Add(child); + + // 根据位置判断部件类型 + Vector3 localPos = child.localPosition; + + // 头部 - 通常在Y轴较高且Z轴偏前的位置 + if (localPos.y > 0.7f && localPos.z > 0) + { + if (headPart == null || localPos.y > headPart.localPosition.y) + { + headPart = child; + } + } + + // 尾巴 - Z轴负方向 + if (localPos.z < -0.3f && localPos.y > 0.2f) + { + if (tailPart == null || localPos.z < tailPart.localPosition.z) + { + tailPart = child; + } + } + + // 腿 - Y轴较低的位置 + if (localPos.y < 0.25f && Mathf.Abs(localPos.x) > 0.1f) + { + legParts.Add(child); + } + + // 耳朵 - Y轴较高,X轴有偏移(左右两边) + if (localPos.y > 1f && Mathf.Abs(localPos.x) > 0.15f) + { + earParts.Add(child); + } + } + } + + /// + /// 更新所有动画 + /// + protected virtual void UpdateAnimations() + { + animTime += Time.deltaTime * animationSpeed; + + // 检测玩家距离 + CheckPlayerProximity(); + + if (isMoving && !isJumping) + { + // 走路动画 + UpdateWalkAnimation(); + } + else if (!isJumping) + { + // 待机动画 - 呼吸 + UpdateIdleAnimation(); + } + + // 尾巴总是摇摆 + UpdateTailAnimation(); + + // 耳朵动画 + UpdateEarAnimation(); + } + + /// + /// 检测玩家距离并做出反应 + /// + protected virtual void CheckPlayerProximity() + { + if (playerTransform == null) return; + + float distance = Vector3.Distance(transform.position, playerTransform.position); + + // 更新警觉冷却 + if (alertCooldown > 0) alertCooldown -= Time.deltaTime; + + if (distance < playerDetectRadius) + { + if (!isAlerted && alertCooldown <= 0) + { + isAlerted = true; + OnPlayerNearby(); + } + + // 头部看向玩家 + if (headPart != null) + { + Vector3 dirToPlayer = (playerTransform.position - transform.position).normalized; + float angleToPlayer = Mathf.Atan2(dirToPlayer.x, dirToPlayer.z) * Mathf.Rad2Deg; + float relativeAngle = Mathf.DeltaAngle(currentRotationY, angleToPlayer); + relativeAngle = Mathf.Clamp(relativeAngle, -60f, 60f); // 限制头部转动范围 + + if (originalRotations.ContainsKey(headPart)) + { + Quaternion targetRot = originalRotations[headPart] * Quaternion.Euler(0, relativeAngle, 0); + headPart.localRotation = Quaternion.Lerp(headPart.localRotation, targetRot, Time.deltaTime * 3f); + } + } + } + else + { + if (isAlerted) + { + isAlerted = false; + alertCooldown = 3f; // 3秒冷却 + } + } + } + + /// + /// 玩家靠近时的反应 + /// + protected virtual void OnPlayerNearby() + { + // 根据动物类型有不同反应 + switch (animalType) + { + case AnimalType.Rabbit: + case AnimalType.Chicken: + // 胆小的动物:逃跑 + if (!isJumping && playerTransform != null) + { + Vector3 awayFromPlayer = (transform.position - playerTransform.position).normalized; + moveDirection = new Vector3(awayFromPlayer.x, 0, awayFromPlayer.z); + isMoving = true; + } + break; + case AnimalType.Dog: + // 狗:兴奋,摇尾巴 + StartCoroutine(ExcitedTailWag()); + break; + case AnimalType.Cat: + // 猫:停下来看 + isMoving = false; + break; + case AnimalType.Tiger: + case AnimalType.Lion: + // 猛兽:可能靠近玩家 + if (playerTransform != null) + { + Vector3 toPlayer = (playerTransform.position - transform.position).normalized; + moveDirection = new Vector3(toPlayer.x, 0, toPlayer.z); + isMoving = true; + } + break; + default: + break; + } + } + + /// + /// 耳朵动画 + /// + protected virtual void UpdateEarAnimation() + { + if (earParts.Count == 0) return; + + float earTwitch = 0f; + + // 警觉时耳朵更活跃 + if (isAlerted) + { + earTwitch = Mathf.Sin(animTime * 15f) * 10f; + } + else + { + // 偶尔抖动 + earTwitch = Mathf.Sin(animTime * 3f) * 5f; + } + + foreach (var ear in earParts) + { + if (ear != null && originalRotations.ContainsKey(ear)) + { + // 左右耳朵相反方向 + float side = ear.localPosition.x > 0 ? 1f : -1f; + Quaternion targetRot = originalRotations[ear] * Quaternion.Euler(earTwitch * side, 0, earTwitch * 0.5f); + ear.localRotation = Quaternion.Lerp(ear.localRotation, targetRot, Time.deltaTime * 5f); + } + } + } + + /// + /// 走路动画 + /// + protected virtual void UpdateWalkAnimation() + { + float walkCycle = animTime * 8f; // 走路频率 + + // 身体上下摆动 + float bodyBob = Mathf.Sin(walkCycle * 2f) * 0.03f; + transform.localScale = originalScale * (1f + bodyBob * 0.5f); + + // 身体左右摇摆 + float bodySway = Mathf.Sin(walkCycle) * 2f; + + // 腿部动画 + for (int i = 0; i < legParts.Count; i++) + { + if (legParts[i] != null && originalPositions.ContainsKey(legParts[i])) + { + // 交替抬腿 + float legPhase = walkCycle + (i % 2 == 0 ? 0 : Mathf.PI); + float legLift = Mathf.Max(0, Mathf.Sin(legPhase)) * 0.05f; + float legSwing = Mathf.Sin(legPhase) * 0.03f; + + Vector3 origPos = originalPositions[legParts[i]]; + legParts[i].localPosition = origPos + new Vector3(0, legLift, legSwing); + } + } + + // 头部轻微摆动 + if (headPart != null && originalRotations.ContainsKey(headPart)) + { + Quaternion origRot = originalRotations[headPart]; + float headBob = Mathf.Sin(walkCycle * 2f) * 3f; + headPart.localRotation = origRot * Quaternion.Euler(headBob, 0, 0); + } + } + + /// + /// 待机动画 - 呼吸 + /// + protected virtual void UpdateIdleAnimation() + { + float breathCycle = animTime * 2f; // 呼吸频率 + + // 身体呼吸起伏 + float breathScale = 1f + Mathf.Sin(breathCycle) * 0.02f; + transform.localScale = originalScale * breathScale; + + // 头部偶尔左右看 + if (headPart != null && originalRotations.ContainsKey(headPart)) + { + Quaternion origRot = originalRotations[headPart]; + float lookAround = Mathf.Sin(animTime * 0.5f) * 15f; + headPart.localRotation = origRot * Quaternion.Euler(0, lookAround, 0); + } + + // 重置腿部位置 + foreach (var leg in legParts) + { + if (leg != null && originalPositions.ContainsKey(leg)) + { + leg.localPosition = Vector3.Lerp(leg.localPosition, originalPositions[leg], Time.deltaTime * 5f); + } + } + } + + /// + /// 尾巴摇摆动画 + /// + protected virtual void UpdateTailAnimation() + { + if (tailPart == null || !originalRotations.ContainsKey(tailPart)) return; + + Quaternion origRot = originalRotations[tailPart]; + float wagSpeed = isMoving ? 12f : 3f; // 移动时摇得快 + float wagAmount = isMoving ? 25f : 10f; + + float wagAngle = Mathf.Sin(animTime * wagSpeed) * wagAmount; + tailPart.localRotation = origRot * Quaternion.Euler(0, wagAngle, 0); + } + + /// + /// 平滑转向面朝移动方向 + /// + protected virtual void UpdateRotation() + { + if (isMoving && moveDirection.sqrMagnitude > 0.01f) + { + targetRotationY = Mathf.Atan2(moveDirection.x, moveDirection.z) * Mathf.Rad2Deg; + } + + // 平滑旋转 + currentRotationY = Mathf.LerpAngle(currentRotationY, targetRotationY, Time.deltaTime * 5f); + transform.rotation = Quaternion.Euler(0, currentRotationY, 0); } protected void ApplyGravity() @@ -185,30 +522,153 @@ protected virtual void StartJump() protected virtual IEnumerator JumpRoutine() { isJumping = true; + + // 起跳前压扁 (蓄力) + float squashTime = 0.1f; + float elapsed = 0f; + while (elapsed < squashTime) + { + elapsed += Time.deltaTime; + float t = elapsed / squashTime; + // 压扁:X和Z变宽,Y变矮 + transform.localScale = new Vector3( + originalScale.x * (1f + t * 0.2f), + originalScale.y * (1f - t * 0.3f), + originalScale.z * (1f + t * 0.2f) + ); + yield return null; + } + verticalVelocity = jumpForce; // 设置初始向上速度 - - // 等待直到回到地面 + float jumpStartY = transform.position.y; + float minJumpHeight = 0.3f; // 至少跳这么高才检测落地 + bool reachedMinHeight = false; + + // 跳跃中拉伸 while (true) { verticalVelocity -= gravity * Time.deltaTime; transform.position += Vector3.up * verticalVelocity * Time.deltaTime; - - // 检查是否着陆 - RaycastHit hit; - if (Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, out hit, 0.2f)) + + // 检查是否达到最小跳跃高度 + if (transform.position.y > jumpStartY + minJumpHeight) { - transform.position = new Vector3( - transform.position.x, - hit.point.y, - transform.position.z + reachedMinHeight = true; + } + + // 根据垂直速度调整形状 + float stretchFactor = Mathf.Clamp(verticalVelocity / jumpForce, -1f, 1f); + if (stretchFactor > 0) + { + // 上升时拉伸 + transform.localScale = new Vector3( + originalScale.x * (1f - stretchFactor * 0.15f), + originalScale.y * (1f + stretchFactor * 0.25f), + originalScale.z * (1f - stretchFactor * 0.15f) ); + } + else + { + // 下落时轻微压扁 + transform.localScale = new Vector3( + originalScale.x * (1f - stretchFactor * 0.1f), + originalScale.y * (1f + stretchFactor * 0.15f), + originalScale.z * (1f - stretchFactor * 0.1f) + ); + } + + // 只有在达到最小高度后且开始下落时才检测落地 + if (reachedMinHeight && verticalVelocity < 0) + { + RaycastHit hit; + if (Physics.Raycast(transform.position + Vector3.up * 0.5f, Vector3.down, out hit, 0.6f)) + { + transform.position = new Vector3( + transform.position.x, + hit.point.y, + transform.position.z + ); + break; + } + } + + // 安全检查:防止无限下落 + if (transform.position.y < jumpStartY - 10f) + { + transform.position = new Vector3(transform.position.x, jumpStartY, transform.position.z); break; } - + yield return null; } - + + // 着陆压扁效果 + elapsed = 0f; + float landSquashTime = 0.15f; + while (elapsed < landSquashTime) + { + elapsed += Time.deltaTime; + float t = elapsed / landSquashTime; + // 先压扁再恢复 + float squash = Mathf.Sin(t * Mathf.PI) * 0.25f; + transform.localScale = new Vector3( + originalScale.x * (1f + squash), + originalScale.y * (1f - squash), + originalScale.z * (1f + squash) + ); + yield return null; + } + + transform.localScale = originalScale; verticalVelocity = 0; isJumping = false; } + + /// + /// 特殊动作 - 可以被子类重写 + /// + protected virtual void PlaySpecialAction() + { + // 不同动物可以有不同的特殊动作 + switch (animalType) + { + case AnimalType.Rabbit: + // 兔子:快速跳跃 + if (!isJumping) StartJump(); + break; + case AnimalType.Chicken: + // 鸡:扑腾翅膀 + StartCoroutine(FlapWings()); + break; + case AnimalType.Dog: + // 狗:摇尾巴更快 + StartCoroutine(ExcitedTailWag()); + break; + default: + break; + } + } + + protected IEnumerator FlapWings() + { + // 简单的翅膀扑腾效果 - 整体上下抖动 + float duration = 0.5f; + float elapsed = 0f; + while (elapsed < duration) + { + elapsed += Time.deltaTime; + float flap = Mathf.Sin(elapsed * 30f) * 0.05f; + transform.position += Vector3.up * flap; + yield return null; + } + } + + protected IEnumerator ExcitedTailWag() + { + // 兴奋时尾巴摇得更快 + float originalAnimSpeed = animationSpeed; + animationSpeed = 3f; + yield return new WaitForSeconds(2f); + animationSpeed = originalAnimSpeed; + } } diff --git a/Assets/Scripts/AnimalManager.cs b/Assets/Scripts/AnimalManager.cs index e0decd57..3100f618 100644 --- a/Assets/Scripts/AnimalManager.cs +++ b/Assets/Scripts/AnimalManager.cs @@ -26,9 +26,9 @@ public class AnimalManager : MonoBehaviour new AnimalData( new Color(0.95f, 0.95f, 0.95f), // 白色 new Color(1f, 0.6f, 0.6f), // 粉色内耳 - 1.2f, // 缩放 - 更大 - 8f, - 3f + 1.2f, // 缩放 + 3f, // 移动速度 + 8f // 跳跃力 - 兔子跳得最高 ) }, { @@ -37,8 +37,8 @@ public class AnimalManager : MonoBehaviour new Color(1f, 0.85f, 0.3f), // 黄色 new Color(1f, 0.2f, 0.1f), // 红色鸡冠 1.0f, // 缩放 - 2f, - 1.5f + 2f, // 移动速度 + 5f // 跳跃力 - 鸡也能跳 ) }, { @@ -47,8 +47,8 @@ public class AnimalManager : MonoBehaviour new Color(1f, 0.6f, 0.2f), // 橘猫 new Color(0.95f, 0.95f, 0.95f), // 白色 1.0f, // 缩放 - 6f, - 4f + 4f, // 移动速度 + 7f // 跳跃力 - 猫跳得高 ) }, { @@ -57,8 +57,8 @@ public class AnimalManager : MonoBehaviour new Color(0.65f, 0.45f, 0.25f), // 棕色 new Color(0.4f, 0.25f, 0.1f), // 深棕耳朵 1.2f, // 缩放 - 5f, - 3.5f + 3.5f, // 移动速度 + 6f // 跳跃力 ) }, { @@ -67,8 +67,8 @@ public class AnimalManager : MonoBehaviour new Color(1f, 1f, 1f), // 白羊毛 new Color(0.15f, 0.15f, 0.15f), // 黑脸 1.3f, // 缩放 - 3f, - 2f + 2f, // 移动速度 - 绵羊慢 + 5f // 跳跃力 ) }, { @@ -77,8 +77,8 @@ public class AnimalManager : MonoBehaviour new Color(1f, 0.6f, 0.1f), // 橙色 new Color(0.1f, 0.1f, 0.1f), // 黑条纹 1.4f, // 缩放 - 6f, - 5f + 5f, // 移动速度 - 老虎快 + 7f // 跳跃力 ) }, { @@ -87,8 +87,8 @@ public class AnimalManager : MonoBehaviour new Color(0.9f, 0.7f, 0.35f), // 金色身体 new Color(0.7f, 0.45f, 0.15f), // 棕色鬃毛 1.4f, // 缩放 - 6f, - 5f + 5f, // 移动速度 + 7f // 跳跃力 ) }, { @@ -97,8 +97,8 @@ public class AnimalManager : MonoBehaviour new Color(0.55f, 0.55f, 0.6f), // 灰色 new Color(0.7f, 0.5f, 0.5f), // 粉色内耳 1.5f, // 缩放 - 大象最大 - 2f, - 2f + 1.5f, // 移动速度 - 大象慢 + 4f // 跳跃力 - 大象跳得低但能跳 ) } };