Quando usar cenas versus scripts

Já abordamos como cenas e scripts são diferentes. Scripts definem uma extensão de classe de mecanismo com código imperativo, cenas com código declarativo.

Como resultado, as capacidades de cada sistema são diferentes. As cenas podem definir como uma classe estendida é inicializada, mas não o que seu comportamento realmente é. As cenas são frequentemente usadas em conjunto com um script para que a cena atue como uma extensão do código declarativo do script.

Tipos anônimos

É possível definir completamente o conteúdo de uma cena usando apenas um script. Isto é, em essência, o que o Editor do Godot faz, apenas no construtor em C++ des seus objetos.

Mas, escolher qual deles usar pode ser um dilema. A criação de instâncias de script é idêntica à criação de classes no motor, enquanto o manuseio de cenas requer uma mudança na API:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call
var my_scene = MyScene.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
using System;
using Godot;

public class Game : Node
{
    public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
    public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
    public Node ANode;
    public Node MyNode;
    public Node MyScene;
    public Node MyInheritedScene;

    public Game()
    {
        ANode = new Node();
        MyNode = new MyNode(); // Same syntax
        MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
        MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
    }
}

Além disso, os scripts irão operar um pouco mais lentamente do que as cenas devido às diferenças de velocidade entre o motor e o código do script. Quanto maior e mais complexo o nó, mais razão haverá para construí-lo como uma cena.

Tipos nomeados

Em alguns casos, um usuário pode registrar um script como um novo tipo dentro do próprio editor. Isso o exibe como um novo tipo na caixa de diálogo de criação de nó ou recurso com um ícone opcional. Nestes casos, a capacidade do usuário de usar o script é muito mais simplificada. Em vez de ter que...

  1. Conhecer o tipo base do script que eles gostariam de usar.

  2. Criar uma instância desse tipo base.

  3. Incluir o script ao nó.

    1. (Método arrastar e soltar)

      1. Encontrar o script na dock Arquivos.

      2. Arrastar e soltar o script no nó na dock Cena.

    2. (Método de propriedade)

      1. Role até a parte inferior do Inspetor para encontrar a propriedade script e selecione-a.

      2. Selecione "Carregar" no menu suspenso.

      3. Selecione o script na caixa de diálogo de arquivo.

Com um script registrado, o tipo com script se torna uma opção de criação como os outros nós e recursos no sistema. Não é necessário fazer nenhum dos trabalhos acima. A caixa de diálogo de criação tem até uma barra de pesquisa para pesquisar o tipo por nome.

Existem dois sistemas para registrar tipos...

  • Tipos Personalizados

    • Apenas editor. Os nomes de tipo não são acessíveis durante a execução.

    • Não suporta tipos personalizados herdados.

    • Uma ferramenta de inicialização. Cria o nó com o script. Nada mais.

    • O editor não reconhece o tipo do script ou seu relacionamento com outros tipos do motor ou scripts.

    • Permite que os usuários definam um ícone.

    • Funciona para todas as linguagens de scripting pois lida com recursos de Script em abstrato.

    • Configurado usando EditorPlugin.add_custom_type.

  • Classes de Script

    • Acessíveis pelo editor e pela execução.

    • Exibe relações de herança na íntegra.

    • Cria o nó com o script, mas também pode alterar os tipos ou estender o tipo a partir do editor.

    • O editor está ciente das relações de herança entre scripts, classes de script e classes C++ do motor.

    • Permite que os usuários definam um ícone.

    • Os desenvolvedores do motor devem adicionar suporte a linguagens manualmente (tanto a exposição do nome quanto a acessibilidade durante a execução).

    • Apenas em Godot 3.1+.

    • O Editor verifica pastas do projeto e registra todos os nomes expostos para todas as linguagens de scripting. Cada linguagem de scripting deve implementar seu próprio suporte para expor estas informações.

Ambas as metodologias adicionam nomes à caixa de diálogo de criação, mas as classes de script, em particular, também permitem que os usuários acessem o nome do tipo sem carregar o recurso de script. Criar instâncias e acessar constantes ou métodos estáticos é viável de qualquer lugar.

Com funcionalidades como estas, pode-se desejar que seu tipo seja um script sem uma cena devido à facilidade de uso que ele proporciona aos usuários. Aqueles que desenvolvem plug-ins ou criam ferramentas internas para os designers usarem acharão mais fácil as coisas desta forma.

No lado negativo, também significa ter de utilizar uma programação imperativa.

Desempenho do Script vs PackedScene

Um último aspecto a considerar ao escolher cenas e scripts é a velocidade de execução.

Conforme o tamanho dos objetos aumenta, o tamanho necessário dos scripts para criá-los e inicializá-los fica muito maior. A criação de hierarquias de nós demonstra isso. A lógica de cada Nó pode ter várias centenas de linhas de código.

O exemplo de código abaixo cria um novo Node, muda seu nome, atribui um script a ele, define seu futuro pai como seu proprietário para que seja salvo no disco junto com ele e, finalmente, o adiciona como filho do nó Main:

# Main.gd
extends Node

func _init():
    var child = Node.new()
    child.name = "Child"
    child.script = preload("Child.gd")
    child.owner = self
    add_child(child)
using System;
using Godot;

public class Main : Resource
{
    public Node Child { get; set; }

    public Main()
    {
        Child = new Node();
        Child.Name = "Child";
        Child.Script = ResourceLoader.Load<Script>("child.gd");
        Child.Owner = this;
        AddChild(Child);
    }
}

Um código de script como este é muito mais lento do que o código C++ do lado do motor. Cada instrução faz uma chamada para a API de scripting que leva a muitas "pesquisas" no back-end para encontrar a lógica a ser executada.

Cenas ajudam a evitar este problema de desempenho. PackedScene, o tipo base do qual as cenas herdam, define recursos que utilizam dados serializados para criar objetos. O motor pode processar cenas em lotes no back-end e fornecer um desempenho muito melhor do que os scripts.

Conclusão

No final, a melhor abordagem é considerar o seguinte:

  • Se alguém deseja criar uma ferramenta básica que será reutilizada em vários projetos diferentes e que pessoas de todos os níveis de habilidade provavelmente usarão (incluindo aqueles que não se rotulam como "programadores"), então é provável que deva ser um script, provavelmente um com um nome/ícone personalizado.

  • Se alguém deseja criar um conceito que seja específico para seu jogo, então deve ser sempre uma cena. As cenas são mais fáceis de rastrear/editar e fornecem mais segurança do que os scripts.

  • Se alguém quiser dar um nome a uma cena, então eles ainda podem fazer isso no 3.1, declarando uma classe de script e dando a ela uma cena como uma constante. O script se torna, de fato, um namespace:

    # game.gd
    extends Reference
    class_name Game # extends Reference, so it won't show up in the node creation dialog
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instance())