【问题标题】:How to detect the player's position in a grid?如何检测玩家在网格中的位置?
【发布时间】:2019-09-12 00:03:02
【问题描述】:

我正在开发一款战术角色扮演游戏,我有一个名为 Map 的空游戏对象,它由 36 个图块组成。

地图对象附有这个脚本:

using System.Collections.Generic;
using UnityEngine;

public class GridMap : MonoBehaviour
{
    private GameObject[] MyVector = new GameObject[36];
    public GameObject[,] GridMatrix = new GameObject[6,6];

    public GameObject Map;

    // Start is called before the first frame update
    void Awake()
    {
        for (int i = 0; i < 36; i++)
        {
            try
            {
                MyVector[i] = Map.transform.GetChild(i).gameObject;
            }
            catch
            {
                print("Something is wrong!");
            }
        }
    }

    void Start()
    {
        int Counter = 0;

        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < 6; j++)
            {
                GridMatrix[i, j] = MyVector[Counter];
                Counter;
            }
        }
    }

    // Update is called once per frame
    void Update()
    {

    }
}

每个图块都有这个脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tile : MonoBehaviour
{
    public bool Current = false;
    public bool Selectable = false;

    public bool MouseOver = false;
    public bool Clicked = false;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }

    void OnMouseOver()
    {
        MouseOver = true;
        if (Clicked == false)
        {
            GetComponent<Renderer>().material.color = Color.red;

        }

        if (Input.GetMouseButton(0))
        {
            Clicked = true;
            GetComponent<Renderer>().material.color = Color.green;


        }
    }

    void OnMouseExit()
    {
        MouseOver = false;
        Clicked = false;

        GetComponent<Renderer>().material.color = Color.white;
    }


    private void OnTriggerStay(Collider other)
    {
        if (other.tag == "Player")
        {
            GetComponent<Renderer>().material.color = Color.magenta;
            Current = true;

        }


    }

    private void OnTriggerExit(Collider other)
    {
        GetComponent<Renderer>().material.color = Color.white;
        Current = false;

    }

}

我想检测玩家在哪个图块中,例如:如果玩家在矩阵中位置为 0,0 的图块中,我想在控制台中显示如下内容: “玩家在位置 0,0'。

我已经创建了这个脚本并将它附加到播放器上,以便获取它在地图(网格)中的位置,但是它不起作用:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GetPlayerPosition : MonoBehaviour
{
    private GameObject mapgrid;
    GridMap map;



    float tileX, tileY;
    // Start is called before the first frame update
    void Start()
    {

        mapgrid = GameObject.FindGameObjectWithTag("map");
        map = mapgrid.GetComponent<GridMap>();

        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < 6; j++)
            {
                Tile tile = map.GridMatrix[i, j].GetComponent<Tile>();

                if (tile.Current)
                {
                    tileX = i;
                    tileY = j;

                    Debug.Log(tileX);
                    Debug.Log(tileY);
                }
            }
        }
    }

    // Update is called once per frame
    void Update()
    {

    }
}

我能做些什么来解决这个问题? Print of the project. The black cylinder is the player

【问题讨论】:

  • 我宁愿使用OnTriggerEnter .. 使用GetComponent 每一帧都相当昂贵... 你能进一步定义however is not working 吗? ..据我所知,除了Start 之外,它什么也没做...
  • 脚本 GetPlayerPostion 没有做任何事情。我认为这行代码会起作用,但事实并非如此。 Tile tile = map.GridMatrix[i, j].GetComponent&lt;Tile&gt;(); if (tile.Current) { tileX = i; tileY = j; Debug.Log(tileX); Debug.Log(tileY); } } 它显示消息“对象引用未设置为对象的实例”
  • 查看我的答案.. 错误很可能是由于GetPlayerPositionStart 称为之前 GridMap 的,所以数组尚未填充但只有null 值。在回答中也提到了

标签: c# unity3d


【解决方案1】:
  1. 首先我会做类型

    private Tile[] MayVector = new Tile[36];
    public Tile[,] GridMatrix = new Tile[6,6];
    

    这样您就可以避免所有昂贵的GetComponent&lt;Tile&gt; 电话,并且只需拨打一次。

  2. 我也宁愿使用只调用一次的OnTriggerEnter,而不是每帧调用一次的OnTriggerStayOnMouseEnterOnMouseOver 的第一部分,因为只需设置一次就足够了。

  3. 除了使用 GameObject.FindGameObjectWithTagGetComponent 之外,我还将使用 FindObjectOfType 甚至更好(如果可能的话)通过检查器引用所有内容!

  4. 您还应该在Awake 中填充GridMatrix 数组。否则GetPlayerPositionStart 可能会在GridMapStart 方法之前执行之前,因此尝试在数组尚未填充且仅包含@987654337 时访问该数组@元素 &rightarrow; NullReferenceException

    我对@9​​87654339@ 和Start 的一般经验法则:

    • Awake:做一切不依赖他人的事情,设置自己的引用和值,使用GetComponent等查找场景引用

    • Start: 做一些依赖于你已经设置好的引用的事情。像其他人的访问字段等。

  5. 然后我会让整个事情更受事件驱动,而不是让玩家一直循环通过瓷砖,而是让瓷砖设置玩家的位置:


类似

public class Tile : MonoBehaviour
{
    // maybe not needed anymore
    public bool Current = false;

    public bool Selectable = false;

    public bool MouseOver = false;
    public bool Clicked = false;

    // Will be assigned by the GridMap script
    // so every tile will know it's own position
    // you could also assign this via the Inspector
    // but maybe a lot overhead for 36 tiles ;)
    public Vector2Int TilePosition;

    // The player will register itself here
    // you could even already reference this via the Inspector
    // select all tiles and simply drag the player object here
    public GetPlayerPosition getPlayerPosition;

    // would even be better to already reference this via the Inspector
    // but maybe a bit overhead for 36 tiles 
    // except this is a prefab (what it probably should be ;) )
    [SerializeField] private Renderer renderer;

    private void Awake ()
    {
        // do this only once
        if(!renderer) renderer = GetComponent<Renderer>();
    }

    private void OnMouseEnter()
    {
        // do this only once .. no need to do it every frame
        MouseOver = true;
        renderer.material.color = Color.red;
    }

    private void OnMouseOver()
    {
        // skip further API calls if already Clicked
        if(Clicked) return;

        if (Input.GetMouseButton(0))
        {
            Clicked = true;
            renderer.material.color = Color.green;
       }
    }

    private void OnMouseExit()
    {
        MouseOver = false;
        Clicked = false;

        renderer.material.color = Color.white;
    }

    private void OnTriggerEnter(Collider other)
    {
        // do all these only once .. no need to do it every frame

        if(other.tag != "Player") return;

        renderer.material.color = Color.magenta;
        // Tell the player it's current position
        playerPosition.GridPosition = TilePosition;
        // probably not needed anymore
        Current = true;
    }

    private void OnTriggerExit(Collider other)
    {
        if(other.tag != "Player") return;

        renderer.material.color = Color.white;
        // probably not needed anymore
        Current = false;
    }
}

然后在GridMap中告诉Tile他们的位置

public class GridMap : MonoBehaviour
{
    // just for clean coding reasons
    public const int GRID_WIDTH = 6;
    public const int GRID_HEIGHT = 6;

    public Transform Map;

    public Tile[,] GridMatrix = new Tile[GRID_WIDTH, GRID_HEIGHT];
    private Tile[] MyVector = new Tile[GRID_WIDTH * GRID_HEIGHT];

    // I would move it all to Awake
    //   1. it is possible so why wait until Start
    //   2. Makes also sure that the Start method of GetPlayerPosition
    //      will definitely be executed after all this
    //      => makes sure the array will actually be filled already! 
    private void Awake()
    {
        for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT; i++)
        {
            try
            {
                MyVector[i] = Map.GetChild(i).GetComponent<Tile>();
            }
            catch
            {
                print("Something is wrong!");
                return;
            }
        }

        for (var x = 0; x < GRID_WIDTH; x++)
        {
            for (var y = 0; y < GRID_HEIGHT; y++)
            {
                // This depends of course on how you arranged the tiles in the hierarchy
                GridMatrix[x, y] = MyVector[x * GRID_WIDTH + y];
                GridMatrix[x, y].TilePosition = new Vector2Int (x, y);
            }
        }
    }
}

现在剩下的就是玩家必须注册到 Tiles:

public class GetPlayerPosition : MonoBehaviour
{
    // would be better if you already reference this via the Inspector
    [SerilaizeField] private GridMap map;

    // Will be assigned when entering a Tile
    // since you want integer values I would also store them as such
    // and Vector2Int is already made for storing a value pair x and y ;)
    public Vector2Int GridPosition;

    private void Awake()
    {
        if(!map) map = FindObjectOfType<GridMap>();
    }

    private void Start()
    {  
        // if you don't have to know the indices within the loop
        // you can iterate a multidimensional array simply via foreach
        // because internally it is actually saved and treated as flat array
        foreach (var tile in map.GridMatrix)
        {
            tile.playerPosition = this;               
        }
    }
}

【讨论】:

  • 非常感谢@derHugo 的帮助。现在,它运行良好。你的解释太棒了。解释每一步真是太好了。再次感谢你!我刚刚开始编程,理解一些概念对我来说有点困难,但我会尽力做到最好。
猜你喜欢
  • 2014-03-17
  • 1970-01-01
  • 2019-06-26
  • 2018-11-13
  • 2021-09-07
  • 1970-01-01
  • 2014-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多