diff --git a/Assets/Scenes/SampleScene.scene b/Assets/Scenes/SampleScene.scene index 5b96e1f8..84ef7c8a 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} @@ -677,12 +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: 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} @@ -690,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/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 c5fe49ad..3100f618 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, // 缩放 + 3f, // 移动速度 + 8f // 跳跃力 - 兔子跳得最高 ) }, { 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, // 移动速度 + 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, // 缩放 + 4f, // 移动速度 + 7f // 跳跃力 - 猫跳得高 ) }, { 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, // 缩放 + 3.5f, // 移动速度 + 6f // 跳跃力 ) }, { 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, // 缩放 + 2f, // 移动速度 - 绵羊慢 + 5f // 跳跃力 ) }, { 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, // 缩放 + 5f, // 移动速度 - 老虎快 + 7f // 跳跃力 ) }, { 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, // 缩放 + 5f, // 移动速度 + 7f // 跳跃力 ) }, { 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, // 缩放 - 大象最大 + 1.5f, // 移动速度 - 大象慢 + 4f // 跳跃力 - 大象跳得低但能跳 ) } }; @@ -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 new file mode 100644 index 00000000..9e121a17 --- /dev/null +++ b/Assets/Scripts/BlockHighlight.cs @@ -0,0 +1,236 @@ +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; + + // 破坏进度相关 + private float destroyProgress = 0f; + private GameObject progressOverlay; + private Material progressMaterial; + + void Start() + { + CreateHighlightCube(); + CreateOutline(); + CreateProgressOverlay(); + 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); + } + + /// + /// 设置高亮目标位置 (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); + } + + /// + /// 设置可见性 + /// + 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); + } + 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/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 b/Assets/Scripts/BlockInteractionSystem.cs new file mode 100644 index 00000000..374ebfa4 --- /dev/null +++ b/Assets/Scripts/BlockInteractionSystem.cs @@ -0,0 +1,408 @@ +using UnityEngine; + +/// +/// Block interaction system - handles block placement and destruction +/// Uses optimized math-based raycast instead of physics raycast +/// +public class BlockInteractionSystem : MonoBehaviour +{ + [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("Audio")] + public AudioClip destroySound; + public AudioClip placeSound; + + private Camera playerCamera; + private BlockHighlight blockHighlight; + private SimpleInventory inventory; + private AudioSource audioSource; + private CubeGenerator cubeGenerator; + + // Current target info + private Vector3Int targetBlockPos; + private Vector3 targetNormal; + private bool hasTarget; + + // Destroy progress + private float destroyProgress = 0f; + 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(); + } + + // Get CubeGenerator reference + cubeGenerator = FindObjectOfType(); + + // Create highlight + if (showBlockHighlight) + { + CreateBlockHighlight(); + } + + // Setup audio + audioSource = GetComponent(); + if (audioSource == null) + { + audioSource = gameObject.AddComponent(); + audioSource.spatialBlend = 0f; + audioSource.playOnAwake = false; + } + + Debug.Log("BlockInteractionSystem: Initialized with optimized raycast"); + } + + void CreateBlockHighlight() + { + GameObject highlightObj = new GameObject("BlockHighlight"); + blockHighlight = highlightObj.AddComponent(); + blockHighlight.highlightColor = highlightColor; + 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)); + + // Try math raycast first + hasTarget = ChunkRaycast.Raycast(ray, interactionRange, out targetBlockPos, out targetNormal); + + // Fallback to physics raycast if math raycast fails + if (!hasTarget) + { + RaycastHit hit; + if (Physics.Raycast(ray, out hit, interactionRange)) + { + 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; + } + } + + // Update highlight + 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) + { + // Check if target changed + if (targetBlockPos != lastDestroyPos) + { + destroyProgress = 0f; + lastDestroyPos = targetBlockPos; + } + + isDestroying = true; + destroyProgress += Time.deltaTime; + + // Update highlight progress + if (blockHighlight != null) + { + blockHighlight.SetDestroyProgress(destroyProgress / destroyTime); + } + + // Destroy complete + if (destroyProgress >= destroyTime) + { + DestroyBlock(); + destroyProgress = 0f; + } + } + else + { + // Reset progress when released + if (isDestroying) + { + isDestroying = false; + destroyProgress = 0f; + if (blockHighlight != null) + { + blockHighlight.SetDestroyProgress(0f); + } + } + } + + // Place block (click) + if (Input.GetKeyDown(placeKey) && hasTarget) + { + PlaceBlock(); + } + } + + /// + /// Destroy the target block + /// + void DestroyBlock() + { + // Get block type for inventory + BlockType blockType = BlockType.Dirt; // Default + Color blockColor = BlockTypeConfig.GetColor(BlockType.Dirt); + + if (WorldData.Instance != null) + { + blockType = WorldData.Instance.GetBlock(targetBlockPos); + if (blockType != BlockType.Air) + { + blockColor = BlockTypeConfig.GetColor(blockType); + } + // Update world data + WorldData.Instance.SetBlock(targetBlockPos, BlockType.Air); + } + + // Add to inventory + inventory.AddBlock(blockColor); + + // Play sound + PlaySound(destroySound); + + // Create effect + CreateDestroyEffect(targetBlockPos, blockColor); + + // Notify chunk system to rebuild mesh + if (cubeGenerator != null) + { + cubeGenerator.OnBlockChanged(targetBlockPos); + } + + Debug.Log($"BlockInteractionSystem: Destroyed block at {targetBlockPos}"); + } + + /// + /// Place a block + /// + void PlaceBlock() + { + // Check inventory + if (!inventory.HasBlocks()) + { + Debug.Log("BlockInteractionSystem: No blocks in inventory"); + return; + } + + // Calculate place position + Vector3Int placePos = ChunkRaycast.GetPlacePosition(targetBlockPos, targetNormal); + + // Check if position is occupied by player + if (IsPositionOccupiedByPlayer(placePos)) + { + Debug.Log("BlockInteractionSystem: Cannot place block at player position"); + return; + } + + // 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: Position already occupied"); + return; + } + + // Get block from inventory + Color blockColor = inventory.RemoveBlock(); + BlockType blockType = BlockTypeConfig.GetBlockTypeFromColor(blockColor); + + // Update world data + if (WorldData.Instance != null) + { + WorldData.Instance.SetBlock(placePos, blockType); + } + + // Notify chunk system to rebuild mesh + if (cubeGenerator != null) + { + cubeGenerator.OnBlockChanged(placePos); + } + + // Play sound + PlaySound(placeSound); + + // Create effect + CreatePlaceEffect(placePos, blockColor); + + Debug.Log($"BlockInteractionSystem: Placed block at {placePos}"); + } + + /// + /// Check if position is occupied by player + /// + bool IsPositionOccupiedByPlayer(Vector3Int position) + { + Vector3 playerPos = transform.position; + float playerHeight = 2f; + + 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) + { + if (clip != null && audioSource != null) + { + audioSource.PlayOneShot(clip); + } + } + + /// + /// Create destroy particle effect + /// + void CreateDestroyEffect(Vector3Int position, Color color) + { + GameObject effectObj = new GameObject("DestroyEffect"); + 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; + + 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(Vector3Int position, Color color) + { + GameObject effectObj = new GameObject("PlaceEffect"); + 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; + + 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; + + 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 new Vector3(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z); + } + + /// + /// Get current place position + /// + public Vector3 GetPlacePosition() + { + Vector3Int placePos = ChunkRaycast.GetPlacePosition(targetBlockPos, targetNormal); + return new Vector3(placePos.x, placePos.y, placePos.z); + } + + /// + /// Check if has target + /// + public bool HasTarget() + { + return hasTarget; + } +} 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/CubeGenerator.cs b/Assets/Scripts/CubeGenerator.cs index 7a8dc39d..869b9e97 100644 --- a/Assets/Scripts/CubeGenerator.cs +++ b/Assets/Scripts/CubeGenerator.cs @@ -1,19 +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 = 3; - - [Header("地形设置")] + public int renderDistance = 2; + + [Header("Terrain Settings")] public float noiseScale = 20f; public float heightScale = 10f; public int seed; - - [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); @@ -21,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; @@ -104,314 +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 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; } - - void GenerateChunk(Vector2Int chunkPos, Transform parent) + + /// + /// 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); - - // 生成地面方块 - CreateCube(new Vector3(worldX, intHeight, worldZ), GetTerrainColor(intHeight, height), parent); - - // 生成水面 + + // Generate terrain layers + for (int depth = 0; depth < terrainDepth; depth++) + { + int y = intHeight - depth; + if (y < 0 || y >= ChunkData.HEIGHT) continue; + + BlockType blockType; + if (depth == 0) + { + blockType = GetTerrainBlockType(intHeight); + } + else if (depth < 3) + { + blockType = BlockType.Dirt; + } + else + { + blockType = BlockType.Stone; + } + + 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 (sandMaterial == null) - { - sandMaterial = CreateMaterial(color, isTransparent); - } - return sandMaterial; - } - else if (color == treeColor) - { - if (treeMaterial == null) - { - treeMaterial = CreateMaterial(color, isTransparent); - } - return treeMaterial; - } - else if (color == leafColor) + if (chunkRenderers.TryGetValue(chunkPos, out ChunkRenderer renderer)) { - if (leafMaterial == null) - { - leafMaterial = CreateMaterial(color, isTransparent); - } - return leafMaterial; + renderer.SetDirty(); } - - // 如果是其他颜色,创建一个新材质(这种情况应该很少发生) - 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/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..01eb1f86 --- /dev/null +++ b/Assets/Scripts/SimpleInventory.cs @@ -0,0 +1,349 @@ +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 GUIStyle slotNumberStyle; + 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.5f, 0.5f, 0.5f, 0.95f)); + selectedSlotStyle.alignment = TextAnchor.MiddleCenter; + + countStyle = new GUIStyle(GUI.skin.label); + 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; + } + + 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 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, startY - 18, slotSize, 16); + GUI.color = (i == selectedSlot) ? Color.yellow : Color.white; + GUI.Label(numRect, (i + 1).ToString(), slotNumberStyle); + GUI.color = Color.white; + } + + // 绘制准星 + 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; + } +} 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/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: 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** - 触发闪电 ---