Close
    Search Search

    Tutorial: Criando um Sistema de Colocação de Móveis

    Página do tutorial; este artigo é um tutorial intermediário.Todos os tutoriais · Tutoriais de script

    Esta postagem foi escrita originalmente por EgoMoose em Scripting Helpers. O tutorial original pode ser encontrado aqui.

    Uma das solicitações mais comuns que recebo para tópicos de postagem no blog é para um sistema de colocação de decoração que economiza. Eu evitei esse tópico no passado, pois não existe uma maneira “correta” única de fazê-lo. Dito isso, acho que é um bom exercício para todos os desenvolvedores de jogos fazerem e, potencialmente, colocar uma função em um futuro post que pretendo escrever.



    Além de mudar as coisas, também irei dedicar um tempo para explicar e usar a Programação Orientada a Objetos (OOP), pois não é apenas meu estilo preferido, mas também algo que a Roblox usa em seus scripts principais.

    Tudo bem, vamos entrar nisso!

    Programação Orientada a Objetos

    OOP tem tudo a ver com escrever classes que, neste contexto, é um sinônimo muito sofisticado para a palavra “blueprint”. Podemos então usar esses blueprints para criar objetos que possuem certas propriedades e métodos.

    Se algumas dessas palavras lhe parecem familiares, bem, é porque são. Os objetos com os quais você interage, como Tutorial: Criando um Sistema de Colocação de MóveisPeças, Tutorial: Criando um Sistema de Colocação de MóveisHumanóides, Tutorial: Criando um Sistema de Colocação de MóveisMotor6Ds e assim por diante são designados pelo paradigma OOP. Esses objetos têm propriedades e métodos que, quando combinados, definem como eles interagem com o nosso jogo.

    Propriedades são usadas para definir “características” de um objeto. Por exemplo, uma parte tem uma propriedade chamada Size que, como o próprio nome sugere, define o quão grande ou pequeno o objeto é. Por sua vez, essas propriedades frequentemente desempenham um papel no comportamento e nas ações associadas ao referido objeto, que é definido por métodos. Por exemplo, o método : GetMass () retorna a massa da peça que, entre outras coisas, varia com o tamanho. Assim, podemos ver um exemplo de uma conexão clara aqui entre métodos e propriedades.



    Agora que já conhecemos um pouco da terminologia e um exemplo, gostaria de discutir mais a fundo a distinção entre classes e objetos. Uma classe define as propriedades e métodos que um objeto terá. Por exemplo, sabemos que todas as partes terão uma propriedade de posição, então a definimos em nossa classe. O valor real da propriedade position irá variar entre os objetos individuais, mas o conceito abrangente de uma parte que conhecemos terá uma propriedade chamada Posição com um Vector3 como seu valor. De forma semelhante, ao escrever os métodos de nossa classe, podemos não saber os valores literais de cada propriedade, mas como sabemos que esses valores existirão, podemos tratá-los quase como parâmetros de função.

    A diferença entre OOP e uma abordagem mais funcional para a mesma tarefa pode ser vista no exemplo de código abaixo.

    parte local = Instance.new ("Part") part.Size = Vector3.new (1, 2, 10) part.Material = Enum.Material.Wood print (part: GetMass ()) - 6.9999998807907 - vs: local função getMass (tamanho, material) - massa = volume * densidade volume local = size.x * size.y * size.z densidade local = PhysicalProperties.new (material). Volume de retorno de densidade * impressão final de densidade (getMass (part. Tamanho, parte.Material)) - 6.9999998807907

    Ignorando o fato de que o método é embutido e, portanto, não precisava ser definido, a única diferença era que na abordagem funcional tínhamos que inserir as propriedades da parte como argumentos manualmente. No caso do método, não precisamos inserir nenhum argumento porque Lua sabia que estávamos chamando um método em um objeto específico e, portanto, poderíamos obter as propriedades necessárias diretamente dele. Um método é apenas o nome que damos às funções aplicadas a um objeto específico. Um exemplo de como isso poderia ser:



    função Parte: GetMass () volume local = self.Size.x * self.Size.y * self.Size.z densidade local = PhysicalProperties.new (self.Material). Volume de retorno de densidade * fim de densidade

    Você pode notar que o código acima está se referindo a algo chamado eu e, compreensivelmente, parece que está saindo do ar. Em Lua, quando você chama um método, o primeiro argumento transmitido é SEMPRE o objeto no qual o método foi chamado. Ao definir um método com a função de forma de sintaxe Class: MyMethod (param1, param2, ...) o parâmetro que representará o objeto é forçosamente dado o nome self e não deve ser definido entre colchetes como qualquer outro parâmetro extra. Portanto, se eu executasse somePart: GetMass (), o argumento que substituiria self seria somePart.

    Como uma observação lateral, devido à preferência pessoal ou familiaridade com outros idiomas que usam outra palavra-chave diferente eu tais como isto, alguns programadores podem se perguntar se há uma maneira de usar um nome de parâmetro diferente. Isso é possível e o código equivalente seria o seguinte:

    - o argumento próprio ainda é passado, mas seu nome de parâmetro não está mais oculto e pode ser alterado pela função Part.GetMass (self) local volume = self.Size.x * self.Size.y * self.Size.z densidade local = PhysicalProperties.new (self.Material). Volume de retorno de densidade * fim de densidade - ainda chamado como um método normal de impressão (somePart: GetMass ())

    No entanto, seria minha recomendação pessoal que você não faça isso, pois pode ser confuso para os outros do ponto de vista da legibilidade.

    Tudo bem, então como escrevemos uma aula? A última peça do quebra-cabeça é algo chamado de construtor. Um construtor cria um objeto da classe e o retorna para nós com um conjunto de propriedades preenchidas. Um construtor muito comum que eu acho (?) Que todas as classes integradas têm é .novo() mas outros exemplos podem ser Vector3.FromNormalId or CFrame.Angles. Uma classe pode ter vários construtores e eles podem ser nomeados praticamente qualquer coisa. Às vezes, quando escrevemos esses construtores, eles têm parâmetros que nos ajudam a preencher as propriedades e outras vezes não. Depende totalmente de você, como programador, e depende de para que serve a classe.



    Vejamos como a equipe da Roblox pode escrever um construtor em Lua e decomporemos as partes a partir daí. Aqui está um exemplo de como alguém pode copiar o Vector3 construtor de classe.

    local Vector3 = {} Vector3 .__ index = Vector3 - função do construtor Vector3.new (x, y, z) local self = setmetatable ({}, Vector3) self.x = x ou 0 self.y = y ou 0 self. z = z ou 0 retorna fim próprio

    Para alguns de vocês isso já pode fazer todo o sentido e para alguns de vocês pode não. A principal diferença entre quem entende e quem não entende é a familiaridade com meta-tabelas. Este é um grande tópico por si só, mas, felizmente, só precisamos realmente entender um aspecto do metamétodo __index para entender esse código.

    A melhor explicação “simplificada” que já ouvi sobre metatabelas é “eventos, mas para tabelas” e isso é particularmente aplicável ao metamétodo __index. O metamétodo __index é “disparado” quando uma chave não existente na tabela é indexada, significando lida, não escrita.

    local t = {gatos = 10; cães = 32; } gatos locais = t.cats - não disparado o valor b / c existe para a chave local dogs = t.dogs - não disparado o valor b / c existe para a chave t.turtles = 60 - não disparado b / c que estamos escrevendo print (t.hamsters) - o valor b / c disparado não existe para a chave

    Agora, normalmente, os metamétodos irão "disparar" uma função e o metamétodo __index também pode funcionar dessa maneira. No entanto, se em vez de definir uma função para o metamétodo __index você definir outra tabela, quando o metamétodo __index for "disparado", ele tratará o processo como tal:

    • A tabela foi indexada com chave => A chave corresponde a um valor nulo na tabela?
      • Sim => A chave corresponde a um valor não nulo na tabela no metamétodo __index?
        • Sim => Retorne esse valor
        • Não => Retorno nulo

    Isso é muito útil para nós, pois nos permite definir valores padrão para as chaves e não ter que nos redefinir e repetir constantemente ao fazer cópias. No caso do código acima, usamos isso de forma que self, a tabela que retornamos de nosso construtor, terá acesso a todos os construtores e métodos que anexamos à tabela Vector3.

    Digamos que adicionemos o seguinte método ao código acima, em seguida, criamos um objeto e executamos o método nele:

    função Vector3: Magnitude () local x, y, z = self.x, self.y, self.z return math.sqrt (x * x + y * y + z * z) end local v = Vector3.new (1 , 2, 3) imprimir (v: Magnitude ())

    O processo é tratado como:

    • v foi indexado com a chave Magnitude => A chave corresponde a um valor nulo em v?
      • Sim => A chave corresponde a um valor não nulo em Vector3?
        • Sim => Retorne esse valor (o método de magnitude)

    Assim, o método: Magnitude () é chamado em v, que possui valores reais para as propriedades x, y e z e, como tal, obtemos o resultado correspondente.

    Há muito mais a ser dito sobre OOP e o que eu expliquei mal arranhou a superfície. Algumas outras linguagens o forçam a usar OOP e têm um conjunto de recursos muito mais rico em comparação com Lua. Se você quiser explorar mais a OOP em Lua, recomendo que leia a seguinte postagem nos devforums.

    Dito isso, a pergunta que ainda não respondi é “Por que usar OOP?”. Minha resposta: eu pessoalmente gosto disso, pois me força a organizar meu código de uma forma modular e reutilizável que pode ser combinada para uma variedade de tarefas complexas. Dito isso, há pontos positivos e negativos para tudo, então use o que funciona para você.

    Adicione um comentário do Tutorial: Criando um Sistema de Colocação de Móveis
    Comentário enviado com sucesso! Vamos analisá-lo nas próximas horas.