Получить переменную из родительского скрипта внутри сборного скрипта

Этот проект находится в 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. Если у вас есть небольшие советы по улучшению практики кодирования, также не стесняйтесь упоминать их, пытаясь улучшить свои навыки программирования :)

Если я правильно понимаю ваше объяснение, когда вы создаете экземпляр нового сегмента (в Grow), можете ли вы получить сценарий SnakeBodySprites для этого сегмента (используя segment.GetComponent<SnakeBodySprites>, а затем передать экземпляр сценария Snake в экземпляр SnakeBodySprites через функцию или поле?

avariant 20.04.2023 23:53
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если игровой объект 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);
    }
}

Ух ты! Это такой подробный и удивительный ответ! Хотел бы я проголосовать дважды, но да, это полностью решает мою проблему! Невероятный. Спасибо за этот ответ. :)

raining 21.04.2023 17:50

Другие вопросы по теме