Verifique se o componente de um objeto de jogo pode ser destruído


11

Ao desenvolver um jogo no Unity, estou fazendo uso liberal [RequireComponent(typeof(%ComponentType%))]para garantir que os componentes tenham todas as dependências atendidas.

Agora estou implementando um sistema de tutorial que destaca vários objetos da interface do usuário. Para fazer o realce, estou fazendo uma referência à GameObjectcena, clono-a com Instantiate () e retiro recursivamente todos os componentes que não são necessários para exibição. O problema é que, com o uso liberal RequireComponentdesses componentes, muitos não podem ser simplesmente removidos em nenhuma ordem, pois dependem de outros componentes, que ainda não foram excluídos.

Minha pergunta é: Existe uma maneira de determinar se um Componentpode ser removido do GameObjectque está atualmente anexado.

O código que estou postando funciona tecnicamente, no entanto, gera erros do Unity (não capturados no bloco try-catch, como visto na imagem

insira a descrição da imagem aqui

Aqui está o código:

public void StripFunctionality(RectTransform rt)
{
    if (rt == null)
    {
        return;
    }

    int componentCount = rt.gameObject.GetComponents<Component>().Length;
    int safety = 10;
    int allowedComponents = 0;
    while (componentCount > allowedComponents && safety > 0)
    {
        Debug.Log("ITERATION ON "+rt.gameObject.name);
        safety--;
        allowedComponents = 0;
        foreach (Component c in rt.gameObject.GetComponents<Component>())
        {
            //Disable clicking on graphics
            if (c is Graphic)
            {
                ((Graphic)c).raycastTarget = false;
            }

            //Remove components we don't want
            if (
                !(c is Image) &&
                !(c is TextMeshProUGUI) &&
                !(c is RectTransform) &&
                !(c is CanvasRenderer)
                )
            {
                try
                {
                    DestroyImmediate(c);
                }
                catch
                {
                    //NoOp
                }
            }
            else
            {
                allowedComponents++;
            }
        }
        componentCount = rt.gameObject.GetComponents<Component>().Length;
    }

    //Recursive call to children
    foreach (RectTransform childRT in rt)
    {
        StripFunctionality(childRT);
    }
}

Portanto, a maneira como esse código gerou as últimas três linhas do log de depuração acima é o seguinte: Ele tomou como entrada a GameObjectcom dois componentes: Buttone PopupOpener. PopupOpenerrequer que um Buttoncomponente esteja presente no mesmo GameObject com:

[RequireComponent(typeof(Button))]
public class PopupOpener : MonoBehaviour
{
    ...
}

A primeira iteração do loop while (denotada pelo texto "ITERATION ON Button Invite (clone)") tentou excluir a Buttonprimeira, mas não pôde, pois depende do PopupOpenercomponente. isso gerou um erro. Em seguida, tentou excluir o PopupOpenercomponente, o que foi feito, pois não é dependente de mais nada. Na próxima iteração (indicada pelo segundo texto "ITERATION ON Button Invite (clone)"), ele tentou excluir o Buttoncomponente restante , o que agora poderia fazer, pois PopupOpenerfoi excluído na primeira iteração (após o erro).

Portanto, minha pergunta é se é possível verificar antecipadamente se um componente especificado pode ser removido de sua corrente GameObject, sem chamar Destroy()ou DestroyImmediate(). Obrigado.


Sobre o problema original - o objetivo é fazer uma cópia não interativa (= visual) do (s) elemento (s) da GUI?
— Wondra

Sim. O objetivo é fazer uma cópia idêntica, não interativa, na mesma posição, dimensionada da mesma maneira, mostrando o mesmo texto que o objeto de jogo real embaixo. A razão pela qual segui esse método é que o tutorial não precisaria de modificações se a interface do usuário fosse editada posteriormente (o que aconteceria se eu mostrasse capturas de tela).
— Liam Lime

Não tenho experiência com o Unity, mas estou imaginando se, em vez de clonar a coisa toda e remover coisas, você poderia apenas copiar as partes necessárias?
— Zan Lynx

Eu não testei, mas Destroyremove o componente no final do quadro (ao contrário de Immediately). DestroyImmediateé considerado um estilo ruim, mas acho que mudar para Destroypode realmente eliminar esses erros.
— CAD97

Eu tentei os dois, começando com Destroy (já que esse é o caminho correto) e depois mudei para DestroyImmediate para ver se funcionaria. A destruição ainda parece ocorrer na ordem especificada, o que causa as mesmas exceções, apenas no final do quadro.
— Liam Lime

Respostas:


9

Definitivamente, é possível, mas exige bastante trabalho: você pode verificar se há um Componentanexo ao GameObjectqual possui um Attributetipo RequireComponentque possui um de seus m_Type#campos atribuíveis ao tipo de componente que você está prestes a remover. Tudo envolvido em um método de extensão:

static class GameObjectExtensions
{
    private static bool Requires(Type obj, Type requirement)
    {
        //also check for m_Type1 and m_Type2 if required
        return Attribute.IsDefined(obj, typeof(RequireComponent)) &&
               Attribute.GetCustomAttributes(obj, typeof(RequireComponent)).OfType<RequireComponent>()
               .Any(rc => rc.m_Type0.IsAssignableFrom(requirement));
    }

    internal static bool CanDestroy(this GameObject go, Type t)
    {
        return !go.GetComponents<Component>().Any(c => Requires(c.GetType(), t));
    }
}

depois de soltar GameObjectExtensionsem qualquer lugar do projeto (ou alternativamente torná-lo um método regular no script), você pode verificar se um componente pode ser removido, por exemplo:

rt.gameObject.CanDestroy(c.getType());

No entanto, pode haver outros meios para criar um objeto GUI não interativo - por exemplo, renderizando-o em textura, cobrindo-o com um painel quase transparente que interceptará cliques ou desativará (em vez de destruir) todos os Componentderivados de MonoBehaviourou IPointerClickHandler.


Obrigado! Eu queria saber se uma solução pronta é fornecida com o Unity, mas acho que não :) Obrigado pelo seu código!
— Liam Lime

@LiamLime Bem, eu realmente não posso confirmar que não há nada embutido, mas é improvável, pois o paradigma típico do Unity está tornando o código tolerante a falhas (ou seja catch, log, continue) ao invés de evitar falhas (ou seja if, possível). No entanto, isso não é muito estilo C # (como um nesta resposta). No final, seu código original pode ter sido mais próximo de como as coisas geralmente são feitas ... e as melhores práticas são uma questão de preferência pessoal.
— Wondra

7

Em vez de remover os componentes no clone, você pode apenas desativá-los, definindo .enabled = false-os. Isso não acionará as verificações de dependência, porque um componente não precisa ser ativado para atender a dependência.

  • Quando um Comportamento está desativado, ele ainda estará no objeto e você poderá alterar suas propriedades e chamar seus métodos, mas o mecanismo não chamará mais seu Updatemétodo.
  • Quando um Renderer é desativado, o objeto fica invisível.
  • Quando um colisor é desativado, ele não causa nenhum evento de colisão.
  • Quando um componente da interface do usuário é desativado, o player não pode mais interagir com ele, mas ainda será renderizado, a menos que você também desative o renderizador.

Os componentes não têm uma noção de ativado ou desativado. Comportamentos, colisores e alguns outros tipos sim. Portanto, isso exigiria que o programador fizesse várias chamadas para GetComponent para as várias subclasses.
— DubiousPusher
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.