A quick beginner's guide to ACS
Mais ações
Este tutorial vai guiar você na criação dos seus primeiros scripts ACS, e espera-se que seja útil se você é novo em ACS básico. Presume-se que você já conhece alguns fundamentos de mapeamento e que esteja usando o formato de mapa Hexen ou UDMF.
Criando um script
Abra o Doom Builder e crie um novo mapa (chame-o de MAP01) no formato ZDoom (Doom in Hexen). Crie um setor e coloque um Player 1 Start.
Para GZDoom Builder:
Procure por "Script Editor", encontrado na barra de ferramentas principal/no menu "View", ou pressionando F10.
Para versões mais antigas do Doom Builder:
Vá ao menu "Scripts" e escolha "Edit BEHAVIOR Lump". Você deve ver um botão chamado "Make New Script". Clique nele.
A estrutura de um script
Aqui está a estrutura de um script básico:
#include "zcommon.acs"
script <identificador do script> <tipo de script> (<argumentos do script>)
{
<instrução>;
}
Vamos quebrar os componentes textuais do script, ou seja, sua sintaxe:
- #include "zcommon.acs"
- Coloque isso como a primeira linha do script, copiado exatamente. Isso importa dados ACS para o script compilar e executar com sucesso.
- <identificador do script>
- Cada script tem um identificador, que será um número ou um "nome entre aspas" (chamado de string). Nota: todos os scripts numerados devem estar entre 1 - 32767.
- <tipo de script>
- Cada script tem um tipo, que em geral determina quando ele será executado automaticamente. Veja Script types para a lista de tipos disponíveis.
- (<argumentos do script>)
- Scripts podem aceitar valores chamados argumentos quando executados. Isso é como passar valores para uma função. Nem todos os tipos de script precisam de argumentos. Eles devem ficar entre parênteses.
- {
- Todo o código do script deve ficar {entre chaves}.
- <instrução>;
- Isso seria o código que o script executa. Cada linha de código precisa terminar com ponto-e-vírgula; caso contrário, o compilador vai gerar erro. Ele não “lê” quebras de linha como um humano.
- }
- Lembre-se de colocar a chave de fechamento para marcar o fim do script.
Primeiro script de exemplo
Aqui está o primeiro exemplo de script que vamos criar, que exibirá a mensagem "Hello World!" na tela do jogador quando ele entrar no mapa pela primeira vez:
#include "zcommon.acs" script 1 ENTER { print(s:"Hello World!"); }
Este script será identificado como script 1. Ele tem o tipo ENTER, que executa quando um jogador entra no mapa pela primeira vez. Scripts ENTER não precisam de argumentos.
A instrução print()
No exemplo acima, é usada a instrução/função print(). A instrução print permite exibir uma mensagem na tela do jogador.
A sintaxe do print é assim:
print(<tipo de cast>:<expressão>);
Quebra da sintaxe:
- print(
printé o nome da função a ser executada (chamada) nesta linha, e toda chamada de função deve ser seguida por parênteses. O print exige argumentos dentro dos parênteses.
- <tipo de cast>:
- O conteúdo da mensagem precisa ser interpretado com um código de letra. Por enquanto, você pode usar
s:para texto (strings) oud:para exibir números de variáveis (explicado mais adiante). Veja print para todas as possibilidades.
- O conteúdo da mensagem precisa ser interpretado com um código de letra. Por enquanto, você pode usar
- <expressão>
- Contém o conteúdo da mensagem a exibir. Texto deve ficar entre "aspas".
- );
- Certifique-se de colocar o parêntese final e o ponto-e-vírgula.
Novas linhas
Para colocar uma nova linha dentro de uma string, adicione o texto \n dentro das aspas. Exemplo:
print(s:"Hello\nWorld!");
Isso coloca a palavra "World!" na linha abaixo de "Hello". O compilador não vai pegar a quebra de linha do seu editor e imprimir como você talvez espere; você precisa usar o código especial \n.
Múltiplas expressões
Você também pode fornecer múltiplas expressões para o print, separadas por vírgulas. Exemplo:
print(s:"Hello", s:" World!");
Essa é uma forma alternativa de imprimir "Hello World!" na tela. Garanta que o espaço esteja incluído nas aspas antes da palavra "World!".
Segundo script de exemplo
Aqui está outro script que você pode adicionar depois do script 1:
script 2 (void)
{
print(s:"Bye World!");
}
Diferente do script ENTER do exemplo 1, este script não executa automaticamente quando o jogador entra no mapa e precisa ser executado explicitamente. void dentro dos parênteses indica ao compilador que não há argumentos para este script; essa palavra-chave é obrigatória para qualquer script que não seja de um tipo especial e que não receba argumentos.
Um script do tipo (void) provavelmente é o tipo mais comum. Você também pode usar scripts com argumentos, mas isso será discutido mais adiante.
Para ativar um script (void), você precisa ter um thing com o special do thing configurado para 80: ACS_Execute (monstro morre, item/arma/powerup/chave é pego), ou um linedef cujo special esteja configurado para 80: ACS_Execute (ao cruzar a linha, ao usar, ou quando um tiro acerta ou passa), ou ainda ativar a partir de outro script (você vai aprender isso depois).
O primeiro argumento do special 80: ACS_Execute deve ser 2, que executará o script 2 acima. Você pode deixar os outros argumentos como 0.
É bem comum entre mappers colocar linedefs que você não percebe e que não bloqueiam o caminho. Essas linhas são usadas para scripting.
Variáveis
Esta seção vai ensinar variáveis e uso básico. Variáveis servem para armazenar dados (como um número) para uso posterior no script, semelhante à representação simbólica de números em álgebra.
Exemplo:
script 3 (void)
{
int a = 9;
print(s:"a is ", d:a);
}
Este script faz duas coisas:
- Declara
acomo inteiro e define seu valor como 9. - Imprime a string "a is " seguida do valor de a. Neste caso, o resultado seria "a is 9".
Quebra de sintaxe para declaração de variável:
<tipo de dado> <nome da variável> = <valor>;
- <tipo de dado>
- O tipo de dado da variável.
inté o mais comum, e representa um número inteiro. Existem outros tipos, listados em Data Types.
- O tipo de dado da variável.
- <nome da variável>
- O nome que a variável declarada vai usar. É melhor respeitar a capitalização do nome em cada lugar em que for usado.
- =
- Um valor inicial pode ser atribuído com o sinal de igual, embora não seja obrigatório.
- Exemplo sem valor padrão: "int a;"
- Um valor inicial pode ser atribuído com o sinal de igual, embora não seja obrigatório.
- <valor>
- O valor inicial da variável. Para
int, um número.
- O valor inicial da variável. Para
- ;
- Não esqueça o ponto-e-vírgula ao final de todas as instruções!
Com inteiros, você pode fazer matemática básica (e também matemática mais avançada se programar corretamente).
script 3 (void)
{
int a = 9;
int b = 17;
print(d:a + b);
}
Este script é um exemplo de soma básica. Ele declara a com valor 9, declara b com valor 17 e imprime o resultado de a + b. O cast d: é usado para exibir um valor numérico no print. Como a é 9 e b é 17, ele imprime 26. Se você ativar o script durante o jogo, verá o número 26.
Se você quisesse imprimir explicitamente o texto "9 + 17", e não "26", você poderia fazer assim:
script 3 (void)
{
print(s:"9 + 17");
}
O cast s: é usado aqui porque ele imprime uma string em vez do valor de uma variável.
Operadores úteis de inteiro
(Nos exemplos abaixo, a é 9 e b é 17)
- Atribuição:
a = b- Define a variável
acom o valor deb, entãoavira 17. Um único sinal de igual NÃO representa verificação de igualdade.
- Define a variável
- Igualdade:
a == b- Verifica se o valor de a é igual ao valor de b. Resulta em 1 se for verdadeiro, ou 0 se for falso. Neste caso, 0. Lembre-se: é necessário usar dois sinais de igual.
- Diferença:
a != b- Igual ao de igualdade, mas resulta em 1 se os valores forem diferentes e 0 se forem iguais.
- Adição:
a + b== 26 - Subtração:
a - b== -8 - Multiplicação:
a * b== 153 - Divisão:
a / b== 0- O resultado não é 0,5294, porque divisão de inteiros “arredonda para baixo” e retorna outro inteiro.
- Dividir por 0 resulta em erro de script.
- Módulo:
a % b== 9- Dá o resto inteiro ao tentar dividir 9 por 17.
- Incremento unário:
a++== 10- Soma 1 à variável. a + 1 == 10.
- Decremento unário:
a--== 8 - Parênteses:
(a + 1) * b== 170- Valores podem ser agrupados entre parênteses. O que estiver entre parênteses é avaliado primeiro.
Veja Operators para todos os operadores e mais informações.
Ações e parâmetros de script
Claro que existem outras possibilidades além de imprimir texto e números.
Suponha que colocamos uma chave vermelha no mapa. No Doom vanilla, pegá-la só te dá a chave vermelha. Com ACS do ZDoom, você pode fazer a chave vermelha fazer muito mais! Você pode fazer a chave matar o jogador, dar a BFG9000, não fazer nada, ou levantar monstros mortos! Se você tiver duas chaves vermelhas, pode fazer cada uma fazer algo totalmente diferente!
Crie um setor e coloque um Player Start e uma chave vermelha. Agora, dê à chave a ação especial 80: ACS_Execute com o número do script 1.
Digite o código abaixo (note que ele não compilará porque faltam parâmetros de propósito):
#include "zcommon.acs"
script 1 (void)
{
Thing_Damage();
}
Agora, para a instrução Thing_Damage, precisamos de parâmetros:
- tid: o TID do thing que você quer causar dano.
- amount: a quantidade de dano que o thing vai receber.
- mod: meio de morte. Determina a mensagem de óbito se um jogador morrer. Tipos de dano relevantes estão em Damage_types.
O primeiro parâmetro é quem queremos danificar. Como queremos danificar o jogador, precisamos de um jeito de identificar o jogador para o script: um Thing ID (aka TID). Muitas funções ACS não têm como referenciar um jogador sem TID definido, então vamos definir um TID no jogador com Thing_ChangeTID.
Use este script:
script 2 ENTER
{
Thing_ChangeTID(0, 1000);
}
Quando o jogador entrar no mapa, nós vamos atribuir a ele um TID de 1000 (o segundo parâmetro). O primeiro parâmetro de Thing_ChangeTID é de quem mudar o TID; usamos 0 para referir ao ativador do script, que num script ENTER é o jogador que entrou. Usar 0 como TID do ativador é comum em várias funções ACS, então vale guardar isso.
- Nota: TIDs de jogador começando em 1000 é um padrão bem comum, mas nem todos os mods seguem essa convenção (infelizmente)...
Agora que o jogador tem um TID, podemos completar a chamada de Thing_Damage:
#include "zcommon.acs"
script 1 (void)
{
Thing_Damage(1000, 2, 0);
}
Estamos causando dano ao jogador com TID 1000, com 2 de dano, e o meio de morte padrão 0 (gera um óbito genérico "Player died.").
Teste seu nível, pegue a chave vermelha e observe a vida. Ela vai diminuir ao pegar a chave.
Veja esta página para mais ações utilizáveis: Action specials
Comentando seu código
Você pode adicionar notas ao lado do código do script (chamadas comentários). Você pode usá-las como quiser, mas elas são especialmente recomendadas para anotar comportamentos complexos ou o motivo de certas decisões.
Você pode adicionar um comentário após uma linha de código com um espaço e duas barras, seguido por texto. Tudo depois das barras é ignorado pelo compilador e não é tratado como código até a próxima linha.
Adicionando comentários a um exemplo anterior:
script 3 (void)
{
// Sou um comentário. Aqui é onde inicializamos as variáveis:
int a = 9;
int b = 17;
print(d:a + b); // Imprimir a soma de a e b
}
Usando funções para fazer mudanças simples no mapa
Você pode usar funções ACS e ações de linedef para alterar um mapa, não só things.
Água simples e nadável
Para começar, abra o Doom Builder e crie um novo mapa no formato ZDoom (Doom in Hexen). Crie um setor quadrado de 512x512. Vamos chamar esse de setor "grande". Coloque um Player 1 Start em algum lugar dentro dele.
Crie um segundo setor "pequeno" de 64x64 dentro do primeiro:
- Abaixe o piso para
-64para parecer uma piscina vazia. - Não esqueça de texturizar as bordas da piscina.
- Atribua a tag 1 a esse setor.
- Defina a textura do piso como
FWATER1.
Depois disso, crie outro setor separado fora do setor grande, sem encostar nele. Esse será o setor "de controle". O tamanho não importa (você pode mantê-lo pequeno para ficar mais fácil de mexer).
- Garanta que o teto desse setor tenha a mesma altura dos outros.
- Defina o piso para
-8 - Defina a textura do teto como
FWATER1.
Edite qualquer linedef no setor de controle e atribua a ela a ação 209:Transfer_Heights. Transfer_Heights aceita dois parâmetros (tag do setor, efeito). Defina o primeiro parâmetro como 1 (tag 1) e o segundo como 8 (a parte submersa é nadável).
Salve e rode o mapa. Se tudo estiver certo, você verá que a piscina está "cheia"... ou melhor, que a textura FWATER1 agora fica apenas 8 unidades abaixo do piso em vez de 64. Isso acontece porque a *altura* do setor de controle foi *transferida* para a piscina. Se você pular na piscina, deve conseguir nadar (ande para frente e mova o mouse para cima e para baixo. Você vai flutuar na água em vez de só olhar para cima e para baixo). Saia do mapa e volte ao Doom Builder.
Agora queremos criar um script que adicione cor e neblina no setor de controle. Para o script funcionar, o setor de controle precisa de uma tag, então vamos dar a ele a tag 2. No script ACS, precisamos de duas funções: Sector_SetColor() e Sector_SetFade(). Ambas aceitam os mesmos 4 argumentos (tag do setor, vermelho, verde, azul). Cada cor vai de 0 a 255, dependendo da intensidade. Exemplo: azul = 0 significa sem azul; azul = 255 significa azul no máximo.
#include "zcommon.acs"
// O tipo de script OPEN executa uma vez quando o mapa carrega script 1 OPEN { // vamos usar a tag do setor de controle, 2 Sector_SetColor(2, 0, 0, 205); // isso tinge o setor com azul Sector_SetFade(2, 0, 0, 205); // isso cria um efeito de neblina azul }
Agora, quando você nadar na piscina, a parte submersa ficará azul escura e turva.
Porta abrindo sob condição
O próximo tutorial vai permitir criar uma porta que abre quando você mata um inimigo.
Para isso, crie um novo mapa e faça duas salas ligadas por uma porta. A primeira sala deve ter um monstro, por exemplo um zombieman, e o Player 1 Start. Na outra sala pode haver um item como recompensa. A porta que conecta as duas salas não deve ter ações de linha (para você não conseguir abrir manualmente), mas precisa ter uma tag de setor. Defina a tag do setor da porta como 1.
Vamos usar a função ACS Door_Open() para abrir a porta. Door_Open() aceita três argumentos:
- tag: tag do setor afetado; no nosso caso,
1 - speed: a velocidade de abertura; use
64para abrir rápido - lighttag: tag do setor para efeito gradual de luz; pode deixar
0
script 1 (void)
{
print(s:"You killed a zombieman!");
Door_Open(1, 64, 0); // Abrir o setor tag 1 como porta, velocidade 64
}
Agora, para isso funcionar, precisamos dar ao zombieman um thing special. Neste caso, use 80:ACS_Execute(). Defina o primeiro argumento como 1 (script 1) e deixe os demais como 0.
Teste o mapa: a porta deve abrir ao matar o zombieman.
Controle de fluxo
Execução condicional (if / else)
Podemos fazer uma verificação lógica num script para decidir entre diferentes trechos de código usando if e else.
Exemplo simples de if:
script 1 ENTER
{
int condition = 1;
if(condition)
{
print(s:"The condition was true.");
}
}
O if usa seu próprio par de chaves para delimitar o bloco de código que ele controla. Garanta que cada chave de abertura tenha uma de fechamento e não confunda com as chaves do script.
Isso vai executar o print porque definimos condition como 1, que é “verdadeiro”. Qualquer valor numérico diferente de 0 conta como verdadeiro; 0 conta como falso.
Se você mudar condition para 0, o print não executa.
Exemplo de else encadeado com if:
script 1 ENTER
{
int condition = 0;
if(condition == 0)
{
print(s:"The condition was 0.");
}
else if(condition == 1)
{
print(s:"The condition was 1.");
}
else
{
print(s:"The condition was something else entirely...");
}
}
Tente mudar condition para 0, depois 1, depois 123, e veja qual ramo executa.
Loops condicionais de repetição (while)
Um loop "while" faz a mesma coisa que um if, exceto que ele repete quando termina. O script para de repetir quando a(s) condição(ões) deixam de ser satisfeitas.
NOTA SOBRE LOOPS INFINITOS: Um loop que deve rodar para sempre PRECISA conter pelo menos um Delay() com parâmetro 1 ou maior. Caso contrário, o ZDoom vai encerrar o script (um loop eterno sem pausas impede que o resto do mapa rode). Se isso acontecer, você verá uma mensagem como "Runaway script # terminated" no topo da tela.
script 1 OPEN
{
int i = 5;
while(i > 0)
{
comando ou série de comandos que mudam i;
}
}
Esse while roda enquanto i for maior que 0.
Se você quiser um while que nunca termine (por exemplo, para criar um efeito contínuo), pode usar uma expressão que nunca vira falsa:
script 1 OPEN
{
while(Predefinição:Const) // Ou while(1), ou while(4 == 4), etc.
{
comandos
; // esperar 1 tic entre execuções do loop
}
}
Nota: o operador de negação ! pode ser usado para checar a falsidade de uma condição, exemplo: !(true) (não verdadeiro), ou seja, falso.
Loops controlados por contagem (for)
Um loop "for" repete um trecho do script um número definido de vezes:
script 1 (void)
{
for(int i = 0; i < 10; i++)
{
log(d:i);
}
}
Esse script imprime 0 a 9 no log do console.
ACS usa o chamado for de três expressões, por causa das três partes:
- Inicializador
int i = 0 - Teste do loop
i < 10 - Contador
i++
Cada expressão é separada por ponto-e-vírgula.
Você não precisa declarar uma variável no inicializador, e dá para variar as expressões, mas esse exemplo é o uso típico.
Na primeira iteração, i == 0, o teste i < 10 é verdadeiro e o código dentro do loop executa. Depois i é incrementado em 1 (por causa do i++) e a próxima iteração começa. Isso continua até i == 10, quando o teste i < 10 falha e o bloco interno não executa. A execução então segue após o for.
Referência adicional
- Action specials de ACS: Action_specials
- Funções de ACS: Built-in_ACS_functions