【问题标题】:Can only spawn one object at once in unity一次只能生成一个物体
【发布时间】:2019-09-03 11:10:34
【问题描述】:

在我的游戏中,我有一个名为 ExclamationMark 的游戏对象,我想在玩家进入范围并且他们变得“警觉”时在敌人头顶上方生成它。

我制作了这个简单的脚本来执行此操作,但由于某种原因,它只能在一个游戏对象上运行。

我的敌人脚本:

void CheckForPlayer()
{
    // Define player and get position
    var player = GameObject.FindWithTag("Player");
    var playerPos = (int)player.transform.position.x;

    if (transform.Find("Graphics"))
    {
        // Define gameobject position
        var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;

        // Define range to spawn tiles in
        var range = 5;
        var rangeInfront = enemyPos + range;
        var rangeBehind = enemyPos - range;

        if (playerPos >= rangeBehind && playerPos <= rangeInfront)
        {
            enemyIsActive = true;

            if (transform.Find("ExclamationMark"))
            {
                var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();

                exMark.SpawnExclamationMark();
            }
        }
        else
        {
            enemyIsActive = false;
        }
    }
}

我的! 脚本:

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

public class ExclamationMarkSpawn : MonoBehaviour {

    public GameObject spawnPos;
    public GameObject exclamationMark;
    public GameObject exclamationMarkAudio;

    public void SpawnExclamationMark()
    {
        StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
        Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
        if (exclamationMarkAudio)
            Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
        StartCoroutine(DestroyExclamationMark());
    }

    IEnumerator DestroyExclamationMark()
    {
        yield return new WaitForSeconds(1);
        var children = new List<GameObject>();
        foreach (Transform child in transform) children.Add(child.gameObject);
        children.ForEach(child => Destroy(child));
    }
}

【问题讨论】:

  • 您要查找的 transform.Find("Graphics") 是什么?如果你有所有的敌人对象调用图形,统一只会采取第一个
  • 不是解决方案,而是:代替transform.Find("Graphics").gameObject.transform.position.x;,您可以简单地写transform.Find("Graphics").position.x;,因为transform.Find 已经返回Transform。同样的方法你可以在Find("ExclamationMark")之后省略gameObject
  • 整个区块var range = 5; var rangeInfront = enemyPos + range; var rangeBehind = enemyPos - range; if (playerPos &gt;= rangeBehind &amp;&amp; playerPos &lt;= rangeInfront)可以简化为if(Mathf.Abs(playerPos - enemyPos) &lt;= 5);)
  • 一般来说,你应该强烈避免所有这些Find 调用.. 它们效率很低!而是存储参考文献之一,例如在Awake 中,然后进一步重复使用它们
  • CheckForPlayer 在哪里调用?在Update?

标签: c# unity3d


【解决方案1】:

为了确定:我假设每个玩家都有自己附加的两个脚本实例(有些可能进一步嵌套在他们自己的层次结构中)。

我假设由于您使用的是transform.Find,它会按名称查找对象在它自己的孩子中


一般来说,一遍又一遍地使用FindGetComponent 是非常低效的!您应该在这两个类中将它们存储到字段并重新使用它们。如果您实际上已经可以通过 Inspector 引用它们并且根本不使用 FindGetComponent,那最好。

通常,按名称查找内容总是容易出错。你确定它们都被正确调用了吗?或者其他人可能会进一步嵌套?

注意:Find 确实执行递归下降到Transform 层次结构。

我更喜欢使用附加组件。你说它有例如一个RigidBody。如果这是对象下方层次结构中唯一的 Rigidbody 组件(通常应该是这种情况),那么您可以改为简单地使用

// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);

ExclamationMarkSpawn 也一样

// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;

private void Awake()
{
    if(!player) player = GameObject.FindWithTag("Player");
    if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
    if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}

private void CheckForPlayer()
{
    // If really needed you can also after Awake still use a lazy initialization
    // this adds a few later maybe unnecessary if checks but is still 
    // cheaper then using Find over and over again
    if(!player) player = FindWithTag("Player");
    if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
    if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);

    var playerPos = (int)player.position.x;

    // always if making such a check also give a hint that something might be missing
    if (!graphics)
    {
        // by adding "this" you can now simply click on the message
        // in the console and it highlights the object where this is happening in the hierarchy
        Debug.LogWarning("graphics is missing here :'( ", this);
        return;
    }

    // Define gameobject position
    var enemyPos = graphics.transform.position.x;

    // Define range to spawn tiles in
    // this entire block can be shrinked down to
    if (Mathf.Abs(playerPos - enemyPos) <= 5)
    {
        enemyIsActive = true;

        if (exclamationMark) exclamationMark.SpawnExclamationMark();
    }
    else
    {
        enemyIsActive = false;
    }
}

ExclamationMarkSpawn.cs 也是如此。

我还只允许同时显示 1 个感叹号。例如,当玩家在远处抖动时,特别是假设玩家和敌人,我会将整个实例化移动到例程并使用标志。尤其是因为在玩家停留在范围内时,Update 中的每一帧都会调用它!

还要重新检查并确保你的敌人没有引用相同的spawnPos,因此所有实例都将它们的感叹号重叠在一起。

public class ExclamationMarkSpawn : MonoBehaviour 
{
    public Transform spawnPos;
    public GameObject exclamationMark;
    public GameObject exclamationMarkAudio;

    [SerializeField] private CameraShake cameraShake;

    // only serialized for debug
    [SerializeField] private bool isShowingExclamation;

    private void Awake()
    {
        if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();

        // or assuming this component exists only once in the entire scene anyway
        if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
    }

    public void SpawnExclamationMark()
    {
        StartCoroutine(ShowExclamationMark());
    }

    private IEnumerator ShowExclamationMark()
    {
        // block concurrent routine call
        if(isShowingExclamation) yield brake;

        // set flag blocking concurrent routines
        isShowingExclamation = true;

        // NOTE: Also for this one you might want to rather have a flag
        // multiple enemy instances might call this so you get concurrent coroutines also here
        StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
        Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
        if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);

        yield return new WaitForSeconds(1);
        var children = new List<GameObject>();
        foreach (var child in transform.ToList()) children.Add(child.gameObject);
        children.ForEach(child => Destroy(child));

        // give the flag free
        isShowingExclamation = false;
    }
}

【讨论】:

    【解决方案2】:

    试试这个;

    if (transform.Find("ExclamationMark"))
    {
        var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();        
        exMark.SpawnExclamationMark(transform.position); //Add transform.position here
    }
    
    public void SpawnExclamationMark(Vector3 EnemyPos)
    {
        StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
        Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
        if (exclamationMarkAudio)
            Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
        StartCoroutine(DestroyExclamationMark());
    }
    

    【讨论】:

    • 这并不能解决任何问题..transform.position 可能与spawnPos.transform.position 不同...否则 OP 可能不会添加用于引用另一个对象的专用字段
    • 他想要“!”标记在靠近玩家的任何敌人的顶部。他的代码真的很糟糕。如果他的算法是正确的,这可能就是他想要的。(+从敌人的位置偏移)。 spawnPos 无法存储所有敌人的位置。
    • 好吧,spawnPos 可能是敌人预制件本身内的专用空游戏对象,所以是的,它可以存储每个单独敌人的位置
    • 想想看,你会发现这是不可能的。假设有 10 个敌人预制件,它们都在 ExclamationMarkSpawn 脚本中设置为 spawnPos。当你尝试在 ExclamationMarkSpawn 脚本中获取 spawnPos 值时,它会得到哪个敌方阵地?
    • 好吧,我很确定 OP 这样做了,因为正如所说的,第一个脚本在其子项 (transform.Find) 中明确搜索 spawn 脚本,如果将其放置在场景中的某个地方,这将毫无意义
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-26
    • 2021-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多