Unity多人贪吃蛇项目:蛇身朝向匹配预制件问题求助
问题描述
- 正在开发趣味多人贪吃蛇项目,Unity中已创建两个蛇身预制件:
SnakeBodyHorizontal(水平蛇身)、SnakeBodyVertical(垂直蛇身) - 需求:蛇身根据移动方向切换预制件——上下移动时使用水平预制件,左右移动时使用垂直预制件,默认蛇身为水平状态
- 当前问题:无法让所有蛇身段始终保持正确朝向(如图所示),曾尝试通过前后段X值是否相同来旋转预制件,但存在严重bug,急需更优解决方案

当前代码
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Snake : MonoBehaviour { private Vector2 _direction = Vector2.right; private List<Transform> _segments = new List<Transform>(); private KeyCode lastHitKey = KeyCode.D; public Transform segmentPrefab; public int initialSize = 3; //sprites for snake head public Sprite SnakeHeadUp; public Sprite SnakeHeadRight; public Sprite SnakeHeadLeft; public Sprite SnakeHeadDown; private void Start() { ResetState(); } // Update: Updated Once Per Time (Edit -> Project Settings -> Time (0.09) to change difficulty!) // This function is controlling the direction our snake moves. void Update() { if(Input.GetKeyDown(KeyCode.W) && lastHitKey != KeyCode.S) { this.gameObject.GetComponent<SpriteRenderer>().sprite = SnakeHeadUp; lastHitKey = KeyCode.W; _direction = Vector2.up; } else if (Input.GetKeyDown(KeyCode.S) && lastHitKey != KeyCode.W) { this.gameObject.GetComponent<SpriteRenderer>().sprite = SnakeHeadDown; lastHitKey = KeyCode.S; _direction = Vector2.down; } else if (Input.GetKeyDown(KeyCode.A) && lastHitKey != KeyCode.D) { this.gameObject.GetComponent<SpriteRenderer>().sprite = SnakeHeadLeft; lastHitKey = KeyCode.A; _direction = Vector2.left; } else if(Input.GetKeyDown(KeyCode.D) && lastHitKey != KeyCode.A) { this.gameObject.GetComponent<SpriteRenderer>().sprite = SnakeHeadRight; lastHitKey = KeyCode.D; _direction = Vector2.right; } } //This transforms the position of our snake head, rounded to a whole number. This should force our snake to move in a grid. //if a segment has the same x value as the previous segment, it must be cos turnt!!! then change segments sprite public void FixedUpdate() { for (int i = _segments.Count - 1; i > 0; i--){ _segments[i].position = _segments[i - 1].position; } this.transform.position = new Vector3( Mathf.Round(this.transform.position.x) + _direction.x, Mathf.Round(this.transform.position.y) + _direction.y, 0.0f ); } //Function handles the logic of iterating in reverse from a list of prefabs of our snake segments, to create a new snake segment. private void Grow() { Transform segment = Instantiate(this.segmentPrefab); segment.position = _segments[_segments.Count - 1].position; //each segment inherits position _segments.Add(segment); } //Function handles the logic of resetting the state of our game upon collision with objects other than food private void ResetState(){ //emptying the snakelist for(int i = 1; i < _segments.Count; i++) { Destroy(_segments[i].gameObject); } _segments.Clear(); _segments.Add(this.transform); //resetting our snake from 1 back to initialsize (3) for(int i = 1; i < this.initialSize; i++) { _segments.Add(Instantiate(this.segmentPrefab)); //initalizing the original segments _segments[i].position = new Vector3( -50.0f, 0.0f, 0.0f ); } this.transform.position = new Vector3( -5.0f, 0.0f, 0.0f ); } private void OnTriggerEnter2D(Collider2D other) { if (other.tag == "Food") { Grow(); } else if (other.tag == "Obstacle") { ResetState(); } } }
解决方案
核心思路:放弃通过位置判断方向的逻辑,改为给每个蛇身段记录自身相对于前一段的移动方向,以此准确切换水平/垂直预制件(或Sprite)。
1. 新增方向列表维护段的方向
在Snake脚本中添加一个与_segments对应的方向列表,存储每个段相对于前一段的方向:
private List<Vector2> _segmentDirections = new List<Vector2>();
2. 同步更新位置与方向
修改FixedUpdate方法,在移动蛇身段时,同步记录每个段的方向:
// 在脚本顶部添加两个Sprite变量,用于赋值水平/垂直蛇身 public Sprite horizontalBodySprite; public Sprite verticalBodySprite; public void FixedUpdate() { // 记录头部移动前的位置,用于计算头部方向 Vector3 headOldPos = transform.position; // 更新头部位置 transform.position = new Vector3( Mathf.Round(headOldPos.x) + _direction.x, Mathf.Round(headOldPos.y) + _direction.y, 0.0f ); // 更新头部的方向(就是当前移动方向) _segmentDirections[0] = _direction; // 从后往前更新所有身体段的位置和方向 for (int i = _segments.Count - 1; i > 0; i--) { Vector3 segmentOldPos = _segments[i].position; // 更新当前段位置为前一段的旧位置 _segments[i].position = _segments[i - 1].position; // 计算当前段相对于前一段的方向(前一段旧位置到当前段旧位置的反向) _segmentDirections[i] = (segmentOldPos - _segments[i].position).normalized; } // 遍历身体段,根据方向切换Sprite for (int i = 1; i < _segments.Count; i++) { SpriteRenderer sr = _segments[i].GetComponent<SpriteRenderer>(); Vector2 dir = _segmentDirections[i]; if (dir == Vector2.left || dir == Vector2.right) { // 左右方向:蛇身垂直排列,使用垂直预制件的Sprite sr.sprite = verticalBodySprite; } else { // 上下方向:蛇身水平排列,使用水平预制件的Sprite sr.sprite = horizontalBodySprite; } } }
3. 初始化与生长逻辑适配
修改ResetState和Grow方法,同步初始化方向列表:
private void ResetState(){ // 先销毁旧段 for(int i = 1; i < _segments.Count; i++) { Destroy(_segments[i].gameObject); } _segments.Clear(); _segmentDirections.Clear(); _segments.Add(this.transform); _segmentDirections.Add(_direction); // 头部初始方向为right // 初始化初始长度的蛇身 for(int i = 1; i < this.initialSize; i++) { Transform segment = Instantiate(this.segmentPrefab); segment.position = new Vector3(-50.0f, 0.0f, 0.0f); _segments.Add(segment); // 初始段方向默认与头部一致(水平) _segmentDirections.Add(Vector2.right); } this.transform.position = new Vector3(-5.0f, 0.0f, 0.0f); } private void Grow() { Transform segment = Instantiate(this.segmentPrefab); segment.position = _segments[_segments.Count - 1].position; _segments.Add(segment); // 新增段的方向与最后一个段方向一致 _segmentDirections.Add(_segmentDirections[_segmentDirections.Count - 1]); }
优化说明
- 用方向列表替代位置判断:彻底避免位置重叠、未移动等情况导致的判断错误
- 切换Sprite替代预制件替换:比销毁再实例化预制件性能更高,逻辑更稳定
- 方向与段一一绑定:每个段的方向信息实时同步,确保朝向准确
内容的提问来源于stack exchange,提问作者raining
相关产品推荐
相关产品推荐

