Estudo 2
unknown
ini
10 months ago
26 kB
12
Indexable
Manual de Arquitetura e Implementação:
Ferramenta de Edição PSB para a Emote
Engine
Introdução: Engenharia de um Editor Sob Medida para
o Formato PSB da Emote Engine
Este documento serve como um guia de engenharia completo para o desenvolvimento de
uma ferramenta robusta e precisa para a visualização, edição e análise de arquivos no
formato .psb, especificamente aqueles gerados pela Emote Engine para o jogo Castlevania
the Adventure Rebirth. O objetivo do projeto é criar uma solução definitiva e de código aberto
que supere as limitações de scripts existentes, fornecendo uma interface intuitiva e, mais
importante, um ambiente de edição não destrutivo que garanta a integridade dos dados.
Os desafios centrais abordados neste projeto são multifacetados. Eles incluem a opacidade
inerente de um formato de arquivo binário proprietário, a precisão exigida para a edição de
parâmetros visuais através de uma interface gráfica (GUI), e as nuances críticas para replicar
com fidelidade o pipeline de renderização da engine original. Questões como espaços de
cores, modos de mesclagem (blend modes) e a ordem de sobreposição de camadas são
cruciais para a precisão visual e foram identificadas como pontos de falha em
implementações anteriores.1
Para superar esses desafios, a filosofia arquitetônica adotada é o padrão
Model-View-Controller (MVC). Esta escolha não é meramente uma convenção de boas
práticas de engenharia de software; é a fundação que permitirá a criação de um ambiente de
edição seguro e modular. O MVC separa rigorosamente a representação dos dados (o
Modelo), a interface do usuário (a Visão) e a lógica de controle (o Controlador), garantindo
que as modificações feitas pelo usuário sejam validadas e aplicadas de forma controlada,
protegendo o arquivo .psb original contra corrupção acidental.
Seção 1: Desconstrução da Estrutura de Dados PSB da
Emote Engine
Esta seção funciona como um manual de referência definitivo para o formato .psb, conforme
representado pela sua estrutura JSON. A abordagem é didática, traduzindo campos de dados
brutos em conceitos funcionais dentro do contexto de uma engine de jogo, de modo a ser
compreensível até mesmo para um leigo.
1.1 Os Quatro Pilares: Cabeçalho, Texturas, Camadas e Animações
A estrutura do arquivo JSON pode ser compreendida através de quatro componentes
principais que trabalham em conjunto. Uma análise inicial revela os seguintes objetos de alto
nível:
1. Cabeçalho (Header): Contém metadados sobre o arquivo, como versão e outras flags
de configuração.
2. Texturas (texture): Define o catálogo de todos os recursos visuais brutos. É uma lista de
"recortes" de uma imagem maior.
3. Camadas (layer): Organiza esses recortes de textura em uma cena hierárquica,
conhecida como grafo de cena (scene graph). É aqui que a composição visual é
estruturada.
4. Animações (motion): Contém as sequências que animam as propriedades das camadas
ao longo do tempo, dando vida à cena.
A relação entre eles é sequencial e lógica: o cabeçalho define o arquivo, as texturas fornecem
os "tijolos" visuais, as camadas constroem a "casa" com esses tijolos, e as animações
definem como as partes dessa casa se movem e mudam.
1.2 O Atlas de Texturas (array texture)
O array texture descreve o que é conhecido como um atlas de texturas ou sprite sheet. Esta é
uma técnica de otimização comum em jogos 2D, onde múltiplas imagens menores (sprites)
são combinadas em uma única imagem grande (o atlas). Isso reduz o uso de memória e
melhora o desempenho de renderização, pois a placa de vídeo só precisa carregar uma
textura grande em vez de dezenas ou centenas de pequenas.
Cada objeto dentro do array texture define um único sprite através dos seguintes campos:
● name: Um identificador único para o sprite (ex: "braço_personagem",
"efeito_explosao_01").
● x, y: As coordenadas do canto superior esquerdo do sprite dentro da imagem do atlas.
● width, height: A largura e a altura do retângulo que define o sprite.
É fundamental entender que o arquivo .psb.json não contém os pixels da imagem. Ele apenas
contém as coordenadas que "recortam" os sprites de um arquivo de imagem separado
(geralmente um .png) que deve acompanhar o .psb.
1.3 O Grafo de Cena (array layer)
O array layer implementa um grafo de cena, que é uma estrutura de dados em árvore usada
para representar as relações espaciais entre objetos em um jogo. Cada objeto layer é um "nó"
nesta árvore.
A hierarquia é estabelecida através do array children presente em cada objeto layer. Uma
camada pode conter outras camadas como "filhas". Isso é extremamente poderoso, pois
transformações (como posição, rotação e escala) aplicadas a uma camada "pai" são
herdadas por todas as suas camadas "filhas". Por exemplo, ao mover o "tronco" de um
personagem, seus "braços" e "cabeça" (que seriam camadas filhas) se movem junto,
permitindo a criação de movimentos articulados complexos.
Os parâmetros chave de uma camada são:
● name: O identificador único da camada, usado para que as animações possam
encontrá-la e modificar suas propriedades.
● texture_id: O índice que conecta esta camada a uma entrada específica no array texture,
definindo qual sprite será desenhado.
● center_x, center_y: O ponto de origem local da camada, ou "pivô". Este ponto é crucial
para que rotações e escalonamentos ocorram de forma correta (por exemplo, um braço
deve girar a partir do ombro, não do seu canto superior esquerdo).
1.3.1 Desmistificando Parâmetros em Japonês
A presença de termos em japonês no arquivo, como "レイヤー", não indica um sistema
fundamentalmente complexo ou desconhecido. Na prática, esses termos são frequentemente
traduções diretas ou transcrições fonéticas (usando o alfabeto katakana) de termos padrão
do desenvolvimento de jogos em inglês.
O termo "レイヤー" é pronunciado como "reiyā", que é a representação fonética japonesa da
palavra inglesa "layer" (camada). Portanto, este parâmetro não define um tipo especial de
camada; ele é a própria definição da camada. Essa lógica se aplica a outros termos que
possam ser encontrados. Isso significa que não é necessário aprender um novo conjunto de
conceitos de engine, mas apenas um pequeno vocabulário. A barreira para a compreensão do
formato é, portanto, significativamente menor do que parece à primeira vista.
1.4 A Engine de Animação (objeto motion)
O objeto motion é o coração da animação do arquivo. Ele contém todas as sequências de
animação, onde cada chave (ex: motion, motion1, special_attack) representa um clipe de
animação distinto (como "andar", "pular", "atacar").
Cada clipe de animação é composto por um array frames. Cada objeto dentro deste array
representa o estado completo de todas as camadas da cena em um único instante de tempo.
A animação é criada pela exibição sequencial desses frames, de forma análoga a um filme.
1.4.1 O Array priority: O Maestro da Orquestra de Renderização
Dentro de cada frame, o array priority é talvez o dado mais crítico para alcançar a paridade
de renderização com o jogo original. Este array contém uma lista de índices de camadas e
define a ordem global de desenho para aquele frame específico. Ele dita qual camada é
desenhada "por cima" de qual outra, resolvendo sobreposições complexas.
A análise do material de referência revela um erro comum em implementações anteriores:
aplicar essa ordem de prioridade apenas às camadas de nível superior, ignorando as
camadas aninhadas (filhas).1 O funcionamento correto da engine exige uma abordagem mais
sofisticada. O
priority não é apenas uma sugestão de ordenação; ele transforma o grafo de cena, que é uma
hierarquia espacial, em uma lista de comandos ordenada para o renderizador, para cada
frame individualmente. A hierarquia do grafo de cena continua sendo usada para calcular as
transformações (posição, rotação), mas o priority assume o controle absoluto da ordem de
pintura (Z-ordering).
Isso implica que o renderizador da ferramenta não pode simplesmente percorrer a árvore do
grafo de cena. Para cada frame, ele deve primeiro consultar a lista priority, construir um mapa
que associa o índice de cada camada à sua posição na ordem de desenho, e então usar este
mapa para ordenar as camadas filhas em todos os níveis da hierarquia antes de renderizá-las.
Esta é uma exigência arquitetônica fundamental e não óbvia para uma renderização precisa.
1.5 Diretivas de Renderização Avançadas e Propriedades de Frame
Esta subseção fornece uma análise granular das propriedades encontradas dentro de cada
objeto de frame, explicando como elas funcionam como comandos e modificadores para o
renderizador. A tabela a seguir consolida esses parâmetros, servindo como um guia de
implementação e um glossário funcional.
Parâmetro
color
Tipo de Dado
Array ou
Inteiro
Descrição e
Explicação
Didática
Tonalização
de Cor:
Notas de
Implementaçã
o
(referenciando
1
)
Crítico:
Modifica a cor
base da
textura da
camada,
agindo como
um filtro
colorido. Pode
ser um array
RGBA ``
(0-255) ou um
inteiro de 32
bits com sinal
(formato
ARGB, ex: -16713985).
Implementar
uma função
utilitária
parse_color(va
lue). Esta
função deve
detectar o tipo
de entrada. Se
for um inteiro,
deve usar
operações bit
a bit (ex: (val
>> 16) & 0xFF)
para extrair os
canais A, R, G
e B e retornar
uma tupla (R,
G, B, A)
padronizada.
Esta
normalização é
o primeiro
passo para
corrigir cores
"lavadas".1
bm Inteiro Modo de
Mesclagem
(Blend Mode):
Dita como os
pixels da
camada atual
se combinam
com os pixels
das camadas
já desenhadas
por baixo.
Mapear os
valores inteiros
para funções
de mesclagem
específicas. A
correspondênc
ia provável,
com base em 1,
é
0: Normal
(mesclagem
alfa padrão), 1:
Aditivo
(ilumina a
imagem, bom
para
brilhos/fogo),
2:
Multiplicativo
(escurece,
bom para
sombras).
zx, zy Float Escalonament
o: São
multiplicadore
s para a
largura (zx) e
altura (zy) da
camada. Um
valor de 1.0 é o
tamanho
original, 2.0 é
o dobro, 0.5 é
a metade.
Aplicar esta
transformação
antes da
rotação e
translação, em
relação à
origem da
camada (ox,
oy).
angle Float Rotação: A
rotação da
camada em
graus em torno
A rotação deve
ser realizada
em torno do
pivô definido
do seu ponto
de origem.
por ox e oy, e
não do canto
superior
esquerdo do
sprite.
ox, oy Float Origem/Pivô:
A coordenada
local dentro do
sprite que
serve como
centro para
todas as
operações de
escalonament
o e rotação.
(0, 0) seria o
canto superior
esquerdo.
Esta é a parte
mais crítica do
pipeline de
transformação
. A ordem
correta das
operações é: 1.
Transladar o
sprite para que
(ox, oy) fique
em (0,0). 2.
Escalonar. 3.
Rotacionar. 4.
Transladar de
volta. 5.
Transladar
para a posição
final na cena.
opa Inteiro Opacidade: A
transparência
geral da
camada,
variando de 0
(totalmente
transparente)
a 255
(totalmente
opaca).
Este valor deve
ser
normalizado
para um float
(opa / 255.0) e
então
multiplicado
pelo canal alfa
da cor da
camada e da
sua textura
base.1
mask,
inheritMask
Inteiro Mascarament
o: Um efeito
avançado
Requer
renderização
para um buffer
onde uma
camada é
usada como
um estêncil
para recortar
uma porção de
outra. mask
provavelmente
se refere a um
índice de
camada a ser
usada como
máscara.
fora da tela
(uma imagem
separada na
memória) para
usar o canal
alfa da
máscara para
controlar o
desenho da
camada
mascarada.
Pode ser
considerado
um
aprimorament
o futuro.
particle Objeto Sistema de
Partículas:
Indica que a
camada não é
um único
sprite, mas um
emissor de
partículas,
usando sua
textura como a
imagem da
partícula. O
objeto contém
parâmetros
para a
simulação (ex:
fmax, range,
vmax).
Requer a
implementaçã
o de um
sistema de
simulação de
partículas
separado, que
está além do
escopo da
renderização
simples de
sprites.
Também deve
ser sinalizado
como um
aprimorament
o futuro.1
Seção 2: Projeto Arquitetônico para a Ferramenta
Python/Tkinter
Esta seção detalha o design do software, focando na criação de uma estrutura de aplicação
limpa, manutenível e robusta.
2.1 Arquitetura Central: Uma Abordagem Model-View-Controller
(MVC)
A utilização do padrão MVC é justificada por sua capacidade de criar uma separação clara de
responsabilidades:
● Modelo (Model): Gerencia os dados da aplicação (o conteúdo do .psb) e a lógica de
negócios. Ele não tem conhecimento da interface do usuário. Esta camada é responsável
por carregar, salvar e modificar os dados de forma segura.
● Visão (View): A GUI (widgets Tkinter). É responsável apenas por exibir os dados do
Modelo e enviar os comandos do usuário para o Controlador. Ela é, em essência, "burra",
pois não contém lógica de dados.
● Controlador (Controller): O intermediário. Ele recebe a entrada do usuário vinda da
Visão (ex: "o usuário editou a célula X para o valor Y"), processa-a e instrui o Modelo a se
atualizar. Em seguida, ele informa à Visão para atualizar sua exibição a partir do Modelo
modificado.
Essa arquitetura não é apenas uma boa prática, mas a solução direta para a principal
preocupação do usuário: a integridade dos dados. A solicitação explícita era que as edições
na GUI deveriam modificar apenas os campos correspondentes no JSON, sem alterar outras
partes e arriscar a corrupção do arquivo. Uma implementação ingênua poderia fazer com que
os widgets da GUI lessem e escrevessem diretamente no dicionário JSON bruto, o que é frágil
e propenso a erros (como mudanças acidentais de tipo de dado ou modificação da chave
errada).
Ao criar um Modelo (um conjunto de classes Python como PSBTexture, PSBLayer), cria-se
uma interface segura e estruturada para os dados. A GUI só pode chamar métodos como
texture.set_x(novo_valor). Este método no Modelo é o único ponto de código que pode
modificar aquele dado específico, garantindo que o valor tenha o tipo correto e que apenas
aquele campo seja atualizado. Esta arquitetura previne inerentemente o tipo de corrupção de
dados que se teme, tornando a ferramenta mais segura e confiável.
2.2 O Modelo de Dados: Representação em Python da Estrutura PSB
Para implementar o Modelo, propõe-se um conjunto de classes Python que espelham a
estrutura do JSON:
● PSBFile: O contêiner de nível superior, que armazena listas de texturas, camadas e
animações. Ele lida com o carregamento de e o salvamento para JSON.
● Texture: Representa uma única entrada no array texture. Atributos: name, x, y, width,
height.
● Layer: Representa uma camada, incluindo sua lista de children.
● Motion: Representa uma animação, contendo uma lista de objetos Frame.
● Frame: Representa os dados de um único frame.
O fluxo de dados será o seguinte: Arquivo JSON -> json.load() -> método de fábrica
PSBFile.from_dict() que constrói recursivamente o modelo de objetos Python -> a GUI
interage com este modelo de objetos -> método PSBFile.to_dict() -> json.dump() -> Arquivo
JSON.
2.3 A Visão (Implementação da GUI com Tkinter)
2.3.1 Janela Principal e Layout
O layout da janela principal será construído usando um ttk.PanedWindow para criar três
painéis redimensionáveis, proporcionando uma experiência de usuário flexível:
1. Um painel para a Treeview de texturas.
2. Um painel para o Canvas interativo.
3. Um painel para os controles de animação e o player.
2.3.2 A Treeview de Texturas (ttk.Treeview)
A Treeview será configurada com colunas para Nome, X, Y, Largura e Altura. O principal
desafio é a implementação de células editáveis. Uma técnica robusta para simular isso em
Tkinter consiste em:
1. Detectar um duplo-clique em uma célula.
2. Criar dinamicamente um widget ttk.Entry posicionado exatamente sobre a caixa
delimitadora da célula clicada.
3. Pré-preencher o Entry com o valor atual da célula.
4. Vincular os eventos <Return> (Enter) e <FocusOut> (perda de foco) para confirmar a
alteração (via Controlador) ou cancelá-la.
2.3.3 O Canvas Interativo (tkinter.Canvas)
O Canvas será colocado dentro de um Frame para facilitar a implementação de barras de
rolagem e o gerenciamento do layout. Uma lista abrangente de eventos será vinculada para
fornecer a interatividade solicitada:
● <Control-MouseWheel>: Para zoom.
● <Shift-MouseWheel>: Para pan (deslocamento) horizontal.
● <ButtonPress-1>, <B1-Motion>, <ButtonRelease-1>: Para pan de clicar e arrastar.
● <Motion>: Para atualizar continuamente a exibição das coordenadas.
Seção 3: Implementação do Editor de Texturas
Esta seção fornece estratégias práticas e a nível de código para construir as funcionalidades
do editor de texturas.
3.1 Carregando e Exibindo o Atlas de Texturas
O processo começará utilizando a biblioteca Pillow (PIL.ImageTk) para carregar o arquivo de
imagem principal do atlas de texturas (ex: atlas.png). Esta imagem será então exibida no
Canvas usando o método canvas.create_image(). Para fornecer feedback visual imediato,
caixas delimitadoras para cada textura definida no Modelo serão desenhadas sobre a imagem
no canvas, possivelmente como retângulos semitransparentes.
3.2 Vinculação e Sincronização de Dados: A Via de Mão Dupla
A comunicação entre a Visão (GUI) e o Modelo (dados) deve ser bidirecional e robusta.
● Da Visão para o Modelo: O fluxo para a edição na Treeview será:
1. O usuário dá um duplo-clique em uma célula.
2. O código da Visão identifica o ID do item na Treeview e a coluna.
3. Ele recupera o objeto Texture correspondente do Modelo.
4. Cria o widget ttk.Entry temporário.
5. Quando o usuário pressiona Enter, o método update_texture_property do
Controlador é chamado com o objeto Texture, o nome da propriedade (ex: 'x') e o
novo valor.
6. O Controlador instrui o Modelo a se atualizar.
7. O Modelo valida e atualiza seu estado interno.
● Do Modelo para a Visão: Para refletir as mudanças de dados na UI, um padrão
observador simples, mas eficaz, será implementado. Após qualquer alteração nos dados,
o Modelo notifica o Controlador. O Controlador, por sua vez, chama um método como
View.refresh_treeview(), que reconstrói completamente o conteúdo da Treeview a partir
do estado atual e preciso do Modelo.
3.3 Implementando a Interatividade do Canvas: A Matemática e a
Lógica
● Exibição de Coordenadas: O manipulador de eventos para <Motion> obterá as
coordenadas do mouse relativas ao widget do canvas. Em seguida, ele precisará aplicar
a transformação inversa do pan e do zoom atuais para converter essas coordenadas de
tela em coordenadas do espaço da imagem. As coordenadas finais serão então
"clampadas" (restringidas) às dimensões da imagem (max(0, min(coord, max_dim)))
antes de serem exibidas na interface.
● Zoom e Pan: Todas as operações de zoom e pan são, fundamentalmente, modificações
em uma matriz de transformação afim. O código de renderização usará esta matriz para
calcular onde desenhar a imagem no canvas.
○ Zoom: Modifica os componentes de escala da matriz. Para uma sensação natural, o
ponto de zoom deve ser a posição atual do cursor do mouse.
○ Pan: Modifica os componentes de translação da matriz.
● Guias Visuais: Será implementada a lógica para desenhar uma borda ao redor da
imagem principal do atlas no canvas, para delinear claramente seus limites, e a resolução
da imagem será exibida em um rótulo na interface.
Seção 4: Implementação do Player de Animação
Esta seção é uma resposta direta e ponto a ponto aos problemas de renderização delineados
no material de referência 1, fornecendo um projeto completo para um pipeline de
renderização preciso.
4.1 O Loop de Animação e Gerenciamento de Frames
O loop de reprodução principal será projetado usando root.after() do Tkinter. Este método
agenda a execução de uma função após um determinado tempo, permitindo atualizações de
frame a uma taxa consistente (ex: 30 ou 60 FPS) sem bloquear o loop de eventos da GUI. O
estado da animação (animação atual, índice do frame atual, status de reprodução e looping)
será gerenciado de forma centralizada.
4.2 O Pipeline de Renderização: Um Guia Passo a Passo para um
Frame Perfeito
Esta é a parte mais detalhada do projeto, descrevendo a sequência exata de operações
necessárias para renderizar um único frame, utilizando a biblioteca Pillow para manipulação
de imagens.
1. Configuração do Frame: Para o índice do frame atual, recuperar o objeto de frame
correspondente do Modelo. Criar um buffer de imagem RGBA em branco e transparente
(uma imagem PIL.Image) com a resolução de saída desejada.
2. Construção da Fila de Renderização (A Solução priority):
○ Obter a lista priority para o frame atual.
○ Criar o priority_pos_map, conforme descrito em 1: um dicionário que mapeia o índice
de cada camada à sua posição na lista
priority.
○ Definir uma função de renderização recursiva que recebe um objeto layer como
entrada.
○ Dentro desta função, antes de iterar sobre os children da camada, ordenar os filhos
usando uma função chave que consulta o índice deles no priority_pos_map. Este é o
passo crítico para corrigir a ordem de renderização.
3. Renderização Recursiva de Camadas: Chamar a função recursiva começando com as
camadas raiz. Para cada camada, na ordem agora corretamente ordenada:
○ a. Obter Recursos: Obter os dados da Texture da camada (x, y, w, h) e recortar seu
sprite da imagem principal do atlas.
○ b. Aplicar Cor e Opacidade: Obter os valores color e opa específicos do frame.
Usar o utilitário parse_color para obter uma tupla RGBA normalizada. Criar uma
imagem de cor sólida com PIL.Image.new() do tamanho do sprite e compô-la sobre o
sprite usando o valor opa. Isso lida com a tonalização e a opacidade.
○ c. Aplicar Transformações: Usando ox, oy, zx, zy e angle do frame, aplicar as
transformações à imagem do sprite com PIL.Image.transform() ou PIL.Image.rotate()
e PIL.Image.resize(). A ordem de operações detalhada na Seção 1.5 é fundamental.
○ d. Compor no Buffer: Obter a posição final a partir dos dados do frame. Pegar o
sprite transformado e tonalizado e compô-lo (alpha-composite) no buffer de frame
principal, usando o bm (modo de mesclagem) correto.
4. Exibição: Uma vez que todas as camadas foram renderizadas, converter o buffer de
imagem final da Pillow para um PIL.ImageTk.PhotoImage e atualizar o item
correspondente no Canvas.
4.3 Implementando Efeitos Visuais Avançados
● Utilitário parse_color: Um trecho de código Python concreto para esta função será
fornecido, mostrando a lógica bit a bit para lidar com inteiros ARGB negativos e
diferenciá-los de arrays RGBA.
● Funções de Modo de Mesclagem: Serão fornecidas funções Python que recebem duas
imagens Pillow (base e topo) e um modo ('add', 'multiply') e retornam o resultado
mesclado. Para desempenho, isso envolverá a manipulação de pixels em nível de array
usando NumPy (numpy.array(image)) para realizar a adição ou multiplicação, seguido
pelo recorte dos valores de volta para o intervalo 0-255.
Conclusão e Aprimoramentos Futuros
A arquitetura MVC proposta e o pipeline de renderização detalhado fornecem uma base
sólida para a construção de uma ferramenta de edição de .psb que é ao mesmo tempo
poderosa e segura. Este design aborda diretamente os problemas declarados de integridade
de dados e precisão de renderização, garantindo que as edições do usuário sejam granulares
e que a saída visual corresponda fielmente à da engine original.
Com a base estabelecida, um roteiro para o desenvolvimento futuro pode evoluir a ferramenta
de um visualizador/editor para um conjunto de criação abrangente:
● Implementação Completa de Mascaramento: Um guia breve sobre o uso de buffers
fora da tela na Pillow para implementar a funcionalidade mask.
● Simulador de Sistema de Partículas: Esboçar os requisitos para um sistema de
partículas básico que possa interpretar os parâmetros do objeto particle.1
● Importação/Exportação de Texturas: Adicionar funcionalidade para clicar com o botão
direito em uma textura na Treeview e substituir sua imagem no atlas ou exportá-la para
um arquivo.
● Criação de Animações: Projetar uma UI para criar novos objetos motion do zero,
incluindo um editor de linha do tempo para gerenciar keyframes.
Referências citadas
1. CONSIDERAÇÕES PSB_MOTION_TOOL.txt Editor is loading...
Leave a Comment