Unity(C#)无网格模式下蛇身跟随蛇头实现方法咨询
无网格Unity贪吃蛇:蛇身无重叠跟随实现方案
我在Unity中重制无网格版贪吃蛇(Snake)游戏以学习引擎,目前卡在实现蛇身无重叠跟随蛇头的功能上。现有教程均基于网格实现,找不到适配无网格场景的方案,以下是我改编后的代码:
using System.Collections; using System.Collections.Generic; using System.Net.WebSockets; using UnityEngine; public class Snake : MonoBehaviour { public Rigidbody2D myRigidbody; public float SVelocity; private List<Transform> _segments; public Transform segmentPreFab; public Vector3 SnakePosition; public Vector3 SnakeVelocity; public Vector3 SnakeHead; void Start() { _segments = new List<Transform>(); _segments.Add(this.transform); myRigidbody.velocity = Vector2.down * SVelocity; } // Update is called once per frame void Update() { if (myRigidbody.velocity == Vector2.up * SVelocity) { if (Input.GetKeyDown(KeyCode.D)) myRigidbody.velocity = Vector2.right * SVelocity; SnakePosition = myRigidbody.position; if (Input.GetKeyDown(KeyCode.A)) myRigidbody.velocity = Vector2.left * SVelocity; SnakePosition = myRigidbody.position; } if (myRigidbody.velocity == Vector2.down * SVelocity) { if (Input.GetKeyDown(KeyCode.D)) myRigidbody.velocity = Vector2.right * SVelocity; SnakePosition = myRigidbody.position; if (Input.GetKeyDown(KeyCode.A)) myRigidbody.velocity = Vector2.left * SVelocity; SnakePosition = myRigidbody.position; } if (myRigidbody.velocity == Vector2.right * SVelocity) { if (Input.GetKeyDown(KeyCode.W)) myRigidbody.velocity = Vector2.up * SVelocity; SnakePosition = myRigidbody.position; if (Input.GetKeyDown(KeyCode.S)) myRigidbody.velocity = Vector2.down * SVelocity; SnakePosition = myRigidbody.position; } if (myRigidbody.velocity == Vector2.left * SVelocity) { if (Input.GetKeyDown(KeyCode.W)) myRigidbody.velocity = Vector2.up * SVelocity; SnakePosition = myRigidbody.position; if (Input.GetKeyDown(KeyCode.S)) myRigidbody.velocity = Vector2.down * SVelocity; SnakePosition = myRigidbody.position; } } private void FixedUpdate() { for (int i = _segments.Count - 1; i > 0; i--) { _segments[i].position = _segments[i - 1].position; } SnakeVelocity = myRigidbody.velocity; } private void Grow() { Transform segment = Instantiate(this.segmentPreFab); segment.position = _segments[_segments.Count - 1].position; _segments.Add(segment); } private void OnTriggerEnter2D(Collider2D other) { if (other.tag == "Food") { Grow(); } } }
问题分析
现有代码的核心问题在于FixedUpdate中直接将蛇身段的位置设置为前一段的位置——这种方式在网格场景中可行(移动是步进式的),但在无网格自由移动场景下,蛇头持续移动,蛇身段会瞬间瞬移到前一段位置,导致重叠,且无法保持段与段之间的固定距离。同时原输入逻辑缺少大括号,导致部分代码会无条件执行,存在逻辑错误。
解决方案
要实现无网格下的蛇身无重叠跟随,需要让每个蛇身段平滑跟随前一段的移动轨迹,并保持固定间距。以下是修改后的完整代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Snake : MonoBehaviour { public Rigidbody2D myRigidbody; public float SVelocity; public Transform segmentPrefab; // 蛇身段之间的间距,建议设置为蛇身预制体的宽度/高度 public float segmentSpacing = 0.5f; private List<Transform> _segments; // 记录每个段的目标位置,用于轨迹跟随 private List<Vector3> _targetPositions; void Start() { _segments = new List<Transform>(); _targetPositions = new List<Vector3>(); _segments.Add(transform); _targetPositions.Add(transform.position); myRigidbody.velocity = Vector2.down * SVelocity; } void Update() { Vector2 currentVelocity = myRigidbody.velocity; if (currentVelocity == Vector2.up * SVelocity) { if (Input.GetKeyDown(KeyCode.D)) { myRigidbody.velocity = Vector2.right * SVelocity; } if (Input.GetKeyDown(KeyCode.A)) { myRigidbody.velocity = Vector2.left * SVelocity; } } else if (currentVelocity == Vector2.down * SVelocity) { if (Input.GetKeyDown(KeyCode.D)) { myRigidbody.velocity = Vector2.right * SVelocity; } if (Input.GetKeyDown(KeyCode.A)) { myRigidbody.velocity = Vector2.left * SVelocity; } } else if (currentVelocity == Vector2.right * SVelocity) { if (Input.GetKeyDown(KeyCode.W)) { myRigidbody.velocity = Vector2.up * SVelocity; } if (Input.GetKeyDown(KeyCode.S)) { myRigidbody.velocity = Vector2.down * SVelocity; } } else if (currentVelocity == Vector2.left * SVelocity) { if (Input.GetKeyDown(KeyCode.W)) { myRigidbody.velocity = Vector2.up * SVelocity; } if (Input.GetKeyDown(KeyCode.S)) { myRigidbody.velocity = Vector2.down * SVelocity; } } } private void FixedUpdate() { // 更新蛇头的目标位置 _targetPositions[0] = transform.position; // 从后往前更新每个蛇身段的位置 for (int i = 1; i < _segments.Count; i++) { Transform currentSegment = _segments[i]; Vector3 prevTarget = _targetPositions[i - 1]; // 计算当前段到前一段目标位置的方向和距离 Vector3 direction = prevTarget - currentSegment.position; float distance = direction.magnitude; if (distance > segmentSpacing) { // 让当前段以蛇头速度向目标位置移动 currentSegment.position += direction.normalized * SVelocity * Time.fixedDeltaTime; _targetPositions[i] = prevTarget; } else { // 距离不足时,保持段间间距,避免重叠 currentSegment.position = prevTarget - direction.normalized * segmentSpacing; } } } private void Grow() { Transform segment = Instantiate(segmentPrefab); // 新段初始位置设置为最后一段的后方,避免生成时重叠 Vector3 lastSegmentPos = _segments[_segments.Count - 1].position; Vector3 direction = lastSegmentPos - _segments[_segments.Count - 2].position; segment.position = lastSegmentPos - direction.normalized * segmentSpacing; _segments.Add(segment); _targetPositions.Add(segment.position); } private void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Food")) { Grow(); Destroy(other.gameObject); } } }
关键修改点
- 修复输入逻辑:为if语句添加大括号,保证方向切换逻辑的正确性,避免无关代码无条件执行。
- 新增段间距控制:通过
segmentSpacing变量精准控制蛇身段之间的距离,从根源避免重叠。 - 轨迹跟踪机制:用
_targetPositions列表记录每个段的目标位置,让蛇身段跟随前一段的移动轨迹,而非直接瞬移。 - 平滑跟随逻辑:在
FixedUpdate中计算段与前一段的距离,当距离超过设定值时,让段以蛇头速度向目标位置移动,保证跟随过程平滑自然。 - 生长逻辑优化:新增段时将其放置在最后一段的后方,避免刚生成就与现有段重叠。
内容的提问来源于stack exchange,提问作者João Pestana Teixeira
相关产品推荐
相关产品推荐

