Этот проект находится в Unity, является 2D и представляет собой базовую многопользовательскую игру про змей. В настоящее время я пытаюсь исправить проблему, из-за которой ориентация сегментов тела змей обращена в правильном направлении.
У меня есть два скрипта, Snake.cs и SnakeBodySprites.cs.
Внутри игрового объекта «SnakeHead», который содержит скрипт Snake.cs и имеет префаб под названием «SnakeBody», который содержит скрипт «SnakeBodySprites». Моя цель — получить список преобразований сегментов, объявленных в скрипте Snake.cs, чтобы я мог использовать его в своем скрипте SnakeBodySprites для сравнения того, находятся ли преобразования до и после в определенных координатах X и Y. Предоставление этому префабу определенного спрайта в зависимости от результата.
Скрипт змеебодиспрайтс.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SnakeBodySprites : MonoBehaviour
{
[SerializeField] private Snake SnakeScript;
//sprites for snake body
public Sprite SnakeBodyHorizontal;
public Sprite SnakeBodyVertical;
public Sprite SnakeBodyCornerLeftDown;
public Sprite SnakeBodyCornerRightDown;
public Sprite SnakeBodyCornerLeftUp;
public Sprite SnakeBodyCornerRightUp;
private int orientation = 7;
//Snake myScript = SnakeBodySprites.GetComponentInParent<Snake>();
//myScript.myVariable = whatever // set variable
//myScript.myFucntion() // runs function
//GameObject.GetComponentInParent
//If there is an object to the left and to the right, if yes, you're a horizontal piece
//If there is a piece above and below you, you are a vertical piece
//If there is a piece above and to the left, you're a corner piece
//if there is a piece above and to the right, you're a corner piece
//if there is a piece below and to the right, you're a corner piece
//if there is a piece below and to the left, you're a corner piece
// Update is called once per frame
void Update()
{
//Debug.Log();
if (orientation == 1){
Debug.Log("horizontal");
} else if (orientation == 2){
Debug.Log("vertical");
}
else if (orientation == 3){
Debug.Log("CornerLeftDown");
}
else if (orientation == 4){
Debug.Log("CornerRightDown");
}
else if (orientation == 5){
Debug.Log("CornerLeftUp");
}
else if (orientation == 6){
Debug.Log("CornerRightUp");
}
}
}
Скрипт змеи.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
private KeyCode lastHitKey = KeyCode.D;
public Transform segmentPrefab;
public int initialSize = 3;
public Sprite SnakeHeadUp;
public Sprite SnakeHeadRight;
public Sprite SnakeHeadLeft;
public Sprite SnakeHeadDown;
public List<Transform> _segments = new List<Transform>();
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.
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();
}
}
}
Я работал над этим решением какое-то время, и вы можете увидеть много комментариев по отладке/тестированию в скрипте snakebodysprites.cs. Частная ориентация int - это просто проверка ориентации, я намеревался изменить ее на другое значение, и чтобы команды if устанавливали сборный компонент в спрайт, когда я мог получить необходимую информацию.
P.S. Если у вас есть небольшие советы по улучшению практики кодирования, также не стесняйтесь упоминать их, пытаясь улучшить свои навыки программирования :)





Если игровой объект SnakeBody является дочерним элементом игрового объекта SnakeHead, то вы должны просто использовать transform.parent.GetComponent<Snake>(); в скрипте SnakeBodySprites. Тем не менее, вы закомментировали GetComponentInParent(), что заставляет меня подозревать, что вы пытались это сделать, но SnakeBody на самом деле был не дочерним элементом SnakeHead, а отдельным, независимым игровым объектом.
Если вы посмотрите на список объектов в редакторе, вы должны увидеть объект SnakeBody, вложенный в объект SnakeHead, чтобы убедиться, что он действительно является дочерним элементом SnakeHead. В качестве альтернативы, если вы забыли указать тип для GetComponentInParent(), вы можете попробовать GetComponentInParent<Snake>();.
Отвечая на ваш заголовок, смотрите это.
Однако, отвечая на ваши фактические проблемы, я бы предпочел установить это сверху вниз / иметь в виду, что скрипт Snake знает и контролирует SnakeBodySprites, а не наоборот, проверяя опрос на предмет каких-либо изменений состояния. Тогда я бы также правильно назвал это SnakeSegment вместо этого.
В первую очередь для этого я бы сделал ваш префаб уже нужного типа
public SnakeSegment segmentPrefab;
// I know originally you als stored the snake itself here
// we will skip that and rather update the first segment after the head explicitly - see bottom
public List<SnakeSegment> _segments = new ();
затем добавьте общедоступное свойство, чтобы фактически изменить значение направления, я бы использовал перечисление вместо простого int:
public enum SnakeSegmentType
{
horizontal,
vertical,
CornerLeftDown,
CornerRightDown,
CornerLeftUp,
CornerRightUp
}
public class SnakeSegment : MonoBehaviour
{
// Just assuming that's what you are using
[SerializeField] private SpriteRenderer SpriteRenderer;
[SerializeField] private Sprite SnakeBodyHorizontal;
[SerializeField] private Sprite SnakeBodyVertical;
[SerializeField] private Sprite SnakeBodyCornerLeftDown;
[SerializeField] private Sprite SnakeBodyCornerRightDown;
[SerializeField] private Sprite SnakeBodyCornerLeftUp;
[SerializeField] private Sprite SnakeBodyCornerRightUp;
private SnakeSegmentType type;
// Need to have a public getter anyway to properly inherit the value
public SnakeSegmentType Type
{
get => type;
set =>
{
type = value;
// in the setter also update the visuals accordingly
SpriteRenderer.sprite = value switch
{
vertical => SnakeBodyVertical,
CornerLeftDown => SnakeBodyCornerLeftDown,
CornerRightDown => SnakeBodyCornerRightDown,
CornerLeftUp => SnakeBodyCornerLeftUp,
CornerRightUp => SnakeBodyCornerRightUp,
_ => SnakeBodyHorizontal
};
}
}
}
SnakeSegment не нуждается в дополнительных знаниях, даже в том, откуда берутся значения. Snake скорее справится с этим и передаст.
Затем в Snake сначала перейдите к измененным типам
private void ResetState()
{
//emptying the snakelist
// foreach is fine now since the head (this) is not in here anymore
foreach(var segment in _segments)
{
Destroy(segment.gameObject);
}
_segments.Clear();
// initializing with initialSize including the head
for(int i = 1; i < initialSize; i++)
{
var segment = Instantiate(segmentPrefab);
_segment.transform.position = 50 * i * Vector3.left;
_segments.Add(segment);
}
transform.position = 5 * Vector3.left;
}
а затем всегда наследуйте и позицию, и тип
private void Grow()
{
var segment = Instantiate(segmentPrefab);
// inherit last position and orientation
segment.transform.position = _segments[_segments.Count - 1].transform.position;
segment.Type = _segments[_segments.Count - 1].Type;
_segments.Add(segment);
}
и, наконец, вам нужно только один раз проверить и установить ориентацию, а именно, куда вы двигаете голову => Вам нужно только установить тип для самого первого сегмента - нет необходимости проверять положение сегментов вокруг него, все, что вам нужно, это предыдущее и текущее направление головы и применить его к самому первому сегменту после обновления всех остальных.
Итак, что-то вроде, например.
// again assuming that is what you re using
[SerializeField] private SpriteRenderer spriteRenderer;
private void FixedUpdate()
{
// Update all segments except first (after head)
for (int i = _segments.Count - 1; i > 0; i--)
{
_segments[i].position = _segments[i - 1].position;
_segments[i].Type = _segments[i - 1].Type;
}
// also update position of first segment (after head)
// the type is more complex see below
_segments[0].position = transform.position;
// Update head position
transform.position = new Vector3(
Mathf.Round(transform.position.x + _direction.x),
Mathf.Round(transform.position.y + _direction.y)
);
// Update head orientation
// here someone might come up with a more beautiful solution ^^
var previousSprite = spriteRenderer.sprite;
var newSprite = previousSprite;
if (_direction.x < 0) newSprite = SnakeHeadLeft;
else if (_direction.x > 0) newSprite = SnakeHeadRight;
else if (_direction.y < 0) newSprite = SnakeHeadDown;
else if (_direction.y > 0) newSprite = SnakeHeadUp;
else Debug.LogError("Snake isn't moving?!", this);
// finally update first segment (after head) orientation
if (previousSprite == newSprite)
{
// snake is moving straight => no corner
_segments[0].Type = newSprite == SnakeHeadLeft || newSprite == SnakeHeadRight
? SegmentType.horizontal : SementType.vertical;
}
// Otherwise there now can be 8 changes in direction resulting in 4 corners
else if (previousSprite == SnakeHeadUp && newSprite == SnakeHeadLeft
|| previousSprite == SnakeHeadRight && newSprite == SnakeHeadDown)
{
_segments[0].Type = CornerLeftDown;
}
else if (previousSprite == SnakeHeadUp && newSprite == SnakeHeadRight
|| previousSprite == SnakeHeadLeft && newSprite == SnakeHeadDown)
{
_segments[0].Type = CornerRightDown;
}
else if (previousSprite == SnakeHeadDown && newSprite == SnakeHeadLeft
|| previousSprite == SnakeHeadRight && newSprite == SnakeHeadUp)
{
_segments[0].Type = CornerLeftUp;
}
else if (previousSprite == SnakeHeadDown && newSprite == SnakeHeadRight
|| previousSprite == SnakeHeadLeft && newSprite == SnakeHeadUp)
{
_segments[0].Type = CornerRightUp;
}
else
{
Debug.LogError("An impossible direction change has occurred! From {previousSprite} to {newSprite}", this);
}
}
Ух ты! Это такой подробный и удивительный ответ! Хотел бы я проголосовать дважды, но да, это полностью решает мою проблему! Невероятный. Спасибо за этот ответ. :)
Если я правильно понимаю ваше объяснение, когда вы создаете экземпляр нового сегмента (в
Grow), можете ли вы получить сценарий SnakeBodySprites для этого сегмента (используяsegment.GetComponent<SnakeBodySprites>, а затем передать экземпляр сценария Snake в экземпляр SnakeBodySprites через функцию или поле?