No tutorial anterior, foi apresentado ao leitor o PROGS - programa do jogo. Ficou como 'tema de casa' ler os tutoriais de Introdução ao Quakec da The Gaming Architect;
Pré-requisitos para este tutorial: Estar familiarizado com a compilação do PROGS.DAT a partir dos arquivos *.QC e PROGS.SRC
Este tutorial irá mostar como adicionar um monstro novo ao jogo!
Uma das inovações mais impressionantes introduzidas pelo Quake foi o uso de modelos 3D animados. Antes, os jogos 3D para PC usavam sprites (imagens 2D) para representar monstros. Em grandes distâncias, os sprites até enganam, mas basta chegar perto de qualquer monstro do DooM e ver que eles são 2D, sem profundidade. Com modelos 3D, os objetos ficam muito mais realistas, pois são realmente tridimensionais.
Praticamente tudo o que se mexe no Quake é um modelo 3D animado. Um modelo 3D é um conjunto de vértices que se conectam formando triângulos. Com vários triângulos você faz qualquer coisa, desde um Rocketlauncher até um Shambler.
O que é importante saber é que cada MDL possui um ou vários frames (quadros) de animação (numeradas a partir de 0) que são basicamente conjuntos de posições diferentes para os vértices e triângulos do modelo, para fazer a animação.
Os MDLs também possuem uma ou mais skins ('peles'), que são imagens 2D (bitmaps) que são usados para dar textura à superfície do Objeto. A armadura, por exemplo, é um único MDL com 3 skins: uma verde, uma amarela e uma vermelha.
Agora será mostrado como animar um MDL. No caso, um coelhinho que vai substituir os Grunts do Quake!
Você irá colocar um monstro novo no jogo: um coelhinho! Você precisará do Modelo 3D dele;
OBS: Não consegui descobrir daonde veio esse modelo de coelhinho! Ele estava no meu HD junto com um monte de tralhas... Se alguém conseguir descobrir o autor, me avise para que eu coloque nos créditos!!
Você criará um diretório para este projeto chamado \quake\coelho. Instale o "kit" de QuakeC neste diretório, e copie o MDL incluido no ZIP para \quake\coelho\progs\coelho.mdl. Pronto!
Você precisará de um visualizador de arquivos MDL. Se não possuir nenhum, a PunisheR 3D Models tem uns legais para download, lembrando também da Warlock's Games Files que tem tudo!
Abra o MDL do coelho e veja as suas frames de animação. Você verá que as frames tem nomes ("stand1", "stand2".. "death1", "death2"..). Frames com mesmo prefixo significam que pertencem à mesma animação ('death': animação de morte, 'stand': animação de ficar parado etc) e o número no final indica a ordem da frame naquela sequência de animação. O que importa realmente é a ordem numérica das frames no MDL, que é a seguinte:
Os monstros de Quake geralmente tem duas animações para locomoção, uma "run" (correndo atrás do inimigo) e uma "walk" (andando devagar, fazendo ronda). O coelho só tem uma sequência de locomoção, então ela será usada para ambos os casos, só que o coelho irá correr mais rápido quando estiver em "run".
Uma coisa interessante (e importante) é que todas as animações do Quake são sempre de 10Hz! (dez frames por segundo) logo, 10 frames representam 1 segundo de animação; note como a animação de morte do coelho é a mais longa: 15 frames = 1,5 segundos...
Iremos substituir os 'grunts' do Quake pelo coelho. Substituir é mais fácil, mas na próxima parte iremos olhar como é fácil fazê-lo como um monstro novo, quando iremos dar uma olhada em mapas e entidades;
void() monster_army =
{
monstro_coelho();
return;
if (deathmatch)
...
Isto é simplesmente para chamar outra função que definirá um coelho nos lugares aonde deveria ficar um grunt.
Esta função, 'monster_army', é uma função especial, chamada de spawn function (função de spawn). Se classifica uma função como 'spawn function' quando seu nome é igual ao 'classname' de algum objeto (objetos serão chamados de 'entidades' daqui pra frente) do Quake. Veremos isso mais a fundo no próximo tutorial, mas se você já editou mapas, deve estar lembrado que os itens, monstros, inícios de jogador, luzes, etc. (tudo que você mexe para um ponto no espaço, as entidades) tem um nome do tipo 'item_shells' (caixa de shells), 'monster_shambler' (um shambler), 'info_player_start' (um ponto de início de jogadores), 'weapon_rocketlauncher' (um Rocket Launcher), 'light' (uma fonte de luz), 'trigger_teleport' (um teleporter) etc. Pois bem, estes são chamados os classname ('nome de classe') das entidades. E no QuakeC, há uma função de spawn definida para cada uma, com o nome igual ao seu classname!
Estas funções de spawn são chamadas automaticamente pelo Quake ao iniciar uma fase. No arquivo do mapa (BSP) propriamente dito, não há informação nenhuma do que seja um "monster_shambler", isto está definido em QuakeC. Se você trocar os nomes das funções "monster_shambler" e "monster_zombie" (nos arquivos shambler.qc e zombie.qc, respectivamente) você irá trocar os zumbis por shamblers e os shamblers por zumbis!
Ok, mas o quê acabamos de fazer ali em cima? Bom, para cada ponto no mapa aonde tem um grunt ('monster_army'), o Quake irá procurar esta função 'monster_army' e executá-la, para cada grunt. Ela efetivamente 'coloca' o grunt ali. O que fizemos foi mandar ele chamar outra função ('monstro_coelho()') que irá colocar um coelho ao invés de um grunt, e será programada no passo 3. O comando 'return' simplesmente manda ele abortar a função, impedindo que se execute o resto que está em baixo. (poderíamos ter apagado o resto da função.. mas o 'return;' fica mais elegante)
progs.dat defs.qc subs.qc fight.qc ai.qc combat.qc items.qc weapons.qc world.qc client.qc player.qc monsters.qc doors.qc buttons.qc triggers.qc plats.qc misc.qc coelho.qc ogre.qc demon.qc shambler.qc knight.qc soldier.qc wizard.qc dog.qc zombie.qc boss.qc tarbaby.qc // registered hknight.qc // registered fish.qc // registered shalrath.qc // registered enforcer.qc // registered oldone.qc // registered
(na verdade você pode botar ele em qualquer linha entre o monsters.qc e o soldier.qc... Tente colocar o coelho.qc em outras linhas, para descobrir o que acontece... :)
O que fizemos, foi incluir um novo arquivo QC na lista de compilação do PROGS.DAT. Quando você for fazer seus patches, tente colocar o máximo possivel de inclusões em arquivos .QC separados, assim fica muito mais fácil de se juntar patches diferentes de QC.
Bom, agora falta o principal... o resto do código, que define o coelho propriamente dito!
Início coelho.qc
/*
==============================================================================
COELHO
==============================================================================
*/
//
// animacao do coelho parado
//
void() coelho_stand1 =[ 0, coelho_stand2 ] { ai_stand(); };
void() coelho_stand2 =[ 1, coelho_stand3 ] { ai_stand(); };
void() coelho_stand3 =[ 2, coelho_stand4 ] { ai_stand(); };
void() coelho_stand4 =[ 3, coelho_stand5 ] { ai_stand(); };
void() coelho_stand5 =[ 4, coelho_stand1 ] { ai_stand(); };
//
// animacao do coelhinho pulando alegremente...
//
void() coelho_walk1 =[ 33, coelho_walk2 ]
{
ai_walk(1);
// toca um som de vez em quando
if (random() < 0.2)
sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE);
};
void() coelho_walk2 =[ 34, coelho_walk3 ] { ai_walk(4); };
void() coelho_walk3 =[ 35, coelho_walk4 ] { ai_walk(6); };
void() coelho_walk4 =[ 36, coelho_walk5 ] { ai_walk(6); };
void() coelho_walk5 =[ 37, coelho_walk6 ] { ai_walk(2); };
void() coelho_walk6 =[ 38, coelho_walk1 ] { ai_walk(1); };
//
// animacao do coelho correndo
//
void() coelho_run1 =[ 33, coelho_run2 ]
{
ai_run(5);
// toca um som de vez em quando
if (random() < 0.2)
sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE);
};
void() coelho_run2 =[ 34, coelho_run3 ] { ai_run(10); };
void() coelho_run3 =[ 35, coelho_run4 ] { ai_run(20); };
void() coelho_run4 =[ 36, coelho_run5 ] { ai_run(30); };
void() coelho_run5 =[ 37, coelho_run6 ] { ai_run(10); };
void() coelho_run6 =[ 38, coelho_run1 ] { ai_run(5); };
//
// funcao de disparo (fire) do coelho
// (simplesmente e' uma copia do tiro do grunt...
// por enquanto! :)
//
void() coelho_fire =
{
local vector dir;
local entity en;
ai_face();
// som de tiro
sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM);
// calcula direcao de disparo
en = self.enemy;
dir = en.origin - en.velocity*0.2;
dir = normalize (dir - self.origin);
// atira balas (funcao pronta)
FireBullets (4, dir, '0.1 0.1 0');
};
//
// animacao de ataque do coelho
//
void() coelho_atk1 =[ 27, coelho_atk2 ] { ai_face(); };
void() coelho_atk2 =[ 28, coelho_atk3 ]
{
ai_face();
// chama a funcao de tiro do coelho
// note que isto foi feito na segunda frame de animacao
// do coelho, porque se voce ver o modelo do coelho,
// notara' que e' na segunda frame do ataque que
// acontece o "bum" pra tras (faltou so' o foguinho na
// frente da arma)
coelho_fire();
// "flash" do tiro
self.effects = self.effects | EF_MUZZLEFLASH;
};
void() coelho_atk3 =[ 29, coelho_atk4 ] { ai_face(); };
void() coelho_atk4 =[ 30, coelho_atk5 ] { ai_face(); };
void() coelho_atk5 =[ 31, coelho_atk6 ] { ai_face(); };
void() coelho_atk6 =[ 32, coelho_run1 ]
{
ai_face();
// isto aqui e' para o nightmare (skill 3), quando
// os monstros nao param de atirar
SUB_CheckRefire (coelho_atk1);
};
//
// animacao de pain ("dor") do coelho
// ("pain" e' aquela cara feia que os monstros
// fazem quando levam porrada)
//
void() coelho_pain1 =[ 5, coelho_pain2 ] { ai_pain(4); };
void() coelho_pain2 =[ 6, coelho_pain3 ] { ai_pain(4); };
void() coelho_pain3 =[ 7, coelho_pain4 ] { ai_pain(2); };
void() coelho_pain4 =[ 8, coelho_pain5 ] { ai_pain(2); };
void() coelho_pain5 =[ 9, coelho_pain6 ] { ai_pain(1); };
void() coelho_pain6 =[ 10, coelho_pain7 ] { ai_pain(1); };
void() coelho_pain7 =[ 11, coelho_run1 ] { ai_pain(1); };
//
// funcao de pain do coelho
//
void() coelho_pain =
{
// se o coelho tomar duas porradas em menos de 0.7
// segundos, na segunda ele nao reinicia a animacao
// de novo... senao eh soh acertar uma sequencia de
// shotgun nele q ele nao tem como reagir...
if (self.pain_finished > time)
return;
self.pain_finished = time + 0.7;
// som "ai"
sound (self, CHAN_VOICE, "soldier/pain1.wav", 1, ATTN_NORM);
// inicia a animacao de "dor"
coelho_pain1 ();
};
//
// animacao de morte (death) do coelho
//
void() coelho_die1 =[ 13, coelho_die2 ] {};
void() coelho_die2 =[ 14, coelho_die3 ] {};
void() coelho_die3 =[ 15, coelho_die4 ] {};
void() coelho_die4 =[ 16, coelho_die5 ] {};
void() coelho_die5 =[ 17, coelho_die6 ] {};
void() coelho_die6 =[ 18, coelho_die7 ] {};
void() coelho_die7 =[ 19, coelho_die8 ] {};
void() coelho_die8 =[ 20, coelho_die9 ] {};
void() coelho_die9 =[ 21, coelho_die10 ] {};
void() coelho_die10 =[ 22, coelho_die11 ]
{
// faz o corpo ficar nao-solido
// assim nao bloqueia quem passar por cima do corpo
self.solid = SOLID_NOT;
// joga uma backpack com 5 shells
self.ammo_shells = 5;
DropBackpack();
};
void() coelho_die11 =[ 23, coelho_die12 ] {};
void() coelho_die12 =[ 24, coelho_die13 ] {};
void() coelho_die13 =[ 25, coelho_die14 ] {};
void() coelho_die14 =[ 26, coelho_die14 ] {};
// note que a animacao de death fica "presa" na frame 26...
// ele morreu... logo, ele fica paradinho, estirado no chao! >:)
//
// funcao de morte (death) do coelho
// (chamada automaticamente quando a energia (health)
// do coelho for para zero)
//
void() coelho_die =
{
// se a energia do coelho ficou menor
// que -35, explode o coitado em pedacinhos...
if (self.health < -35)
{
// som de gib.. ehehe.. gib rulez..
sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM);
// cabeca
ThrowHead ("progs/gib2.mdl", self.health);
// gibz
ThrowGib ("progs/gib3.mdl", self.health);
ThrowGib ("progs/gib3.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
return;
}
// ...senao, morte normal
// "uaaaargh!"
sound (self, CHAN_VOICE, "soldier/death1.wav", 1, ATTN_NORM);
// inicia animacao de morte
coelho_die1 ();
};
//
// funcao de spawn do coelho
//
void() monstro_coelho =
{
// isto serve para remover o monstro se o
// jogo for deathmatch
if (deathmatch)
{
remove(self);
return;
}
// chamadas de "precache". as funcoes "precache_*"
// so' podem ser chamadas por funcoes de spawn!
// elas servem para dizer ao Quake quais arquivos
// de som, MDLs etc. serao usados por esta entidade.
// assim, logo que comeca a fase, o Quake sabe exatamente
// quais arquivos ira' usar, sem precisar desperdicar
// memoria (por exemplo, se uma fase nao tem shamblers,
// para que carregar os sons e MDLs do shambler na memoria?)
// modelo 3D do coelho
precache_model ("progs/coelho.mdl");
// modelos de gib padrao
precache_model ("progs/gib1.mdl");
precache_model ("progs/gib2.mdl");
precache_model ("progs/gib3.mdl");
// sons do 'soldier'... voce pode fazer sons
// para o coelhinho e usa'-los, ao inves deste!
precache_sound ("soldier/death1.wav");
precache_sound ("soldier/idle.wav");
precache_sound ("soldier/pain1.wav");
precache_sound ("soldier/pain2.wav");
precache_sound ("soldier/sattck1.wav");
precache_sound ("soldier/sight1.wav");
precache_sound ("player/udeath.wav"); // gib death
// define alguns campos do monstro
self.solid = SOLID_SLIDEBOX; // entidade solida (nao da pra atravessar)
self.movetype = MOVETYPE_STEP; // MOVETYPE_STEP: todos os monstros usam
// este tipo de movimentacao, que e' a movimentacao
// 'discreta' (por pontos ao inves de por tempo
// real como os jogadores)
self.health = 55; // energia do coelho = 55...
// setmodel: define o modelo do coelho
setmodel (self, "progs/coelho.mdl");
// setsize: define o tamanho da "bounding box"
// todas as entidades tem uma 'caixa de contorno' que
// indica o seu tamanho. os dois vetores abaixo indicam o
// canto traseiro esquerdo inferior e o canto
// frontal direito superior da caixa
setsize (self, '-16 -16 -24', '16 16 40');
// define qual funcao sera' chamada para
// quando o monstro "comecar a ficar parado"
self.th_stand = coelho_stand1;
self.th_walk = coelho_walk1; // idem, para comecar a andar
self.th_run = coelho_run1; // idem, para comecar a correr
// funcao para quando o monstro for atacar com uma
// "missile weapon" (arma de alcance longo)
self.th_missile = coelho_atk1;
// funcao que deve ser chamada quando o monstro for atingido
self.th_pain = coelho_pain;
// funcao que deve ser chamada quando a energia do monstro
// ficar menor ou igual a zero
self.th_die = coelho_die;
// faz o resto do trabalho generico (ie, igual para
// todo mundo) para monstros que andam...
walkmonster_start ();
};
Dica! Vá para a E4M1 e digite NOTARGET no console ao iniciar a fase, assim você pode observar os coelhos pulando, e sem que eles te ataquem! :)
Se você prestar atencão no código QuakeC, irá idendificar facilmente os trechos de animação... São aquelas linhas todas parecidas, marcadas com um comentário "Alô, Isto É Uma Animação!" ;-)
Vamos dar uma olhada na animação de stand (quando o coelho está parado):
//
// animacao do coelho parado
//
void() coelho_stand1 =[ 0, coelho_stand2 ] { ai_stand(); };
void() coelho_stand2 =[ 1, coelho_stand3 ] { ai_stand(); };
void() coelho_stand3 =[ 2, coelho_stand4 ] { ai_stand(); };
void() coelho_stand4 =[ 3, coelho_stand5 ] { ai_stand(); };
void() coelho_stand5 =[ 4, coelho_stand1 ] { ai_stand(); };
Cada linha define 'o que acontece' em cada frame de animação. Lembre-se que elas tem um intervalo de 0,1s entre si!
As frames são definidas da seguinte forma:
void() Nome_da_Frame_de_animação = [ Nº_da_Frame_do_MDL, Nome_da_próxima_Frame_de_animação ]
{
Comandos para serem executados quando "acontecer" esta frame
};
Lembra que as frames da animação de 'stand' no MDL do coelho são as de 0 à 4? Por isso que são 5 frames de animação definidas para o coelho: uma para cada frame do MDL. Veja os números no campo "Nº_da_Frame_do_MDL" - 0,1,2,3 e 4...
As frames de animação são funções como quaisquer outras, apenas definidas dessa forma especial, com esses '[ ]' para dizer qual a frame que deve ser usada pela entidade atual ("self", o coelho no caso) e qual a próxima função de animação. É pra facilitar mesmo. A função coelho_stand1, por exemplo, poderia ser substituída pela seguinte variante, sem perda de funcionalidade:
void() coelho_stand1 =
{
self.frame = 0; // define a frame de animacao do MDL
self.think = coelho_stand2; // próxima função de animação
self.nextthink = time + 0.1; // 0,1s de intervalo
(Outros comandos)
};
Falando em outros comandos... note que em cada frame, há um comando adicional para ser executado: uma chamada à ai_stand(). Se você olhar toda a listagem do coelho.qc, achará várias outras chamadas à funções do tipo "ai_xxx()"... estas funções estão definidas em ai.qc, e são rotinas genéricas para manipulação da "inteligência artificial" (AI = "Artificial Intelligence") dos monstros.
ai_stand() 'simplesmente' faz com que o monstro fique parado procurando por alvos em alcance, campo de visão etc. para atacar.
Note que a animação de 'stand' é cíclica: quando chega no coelho_stand5, ele chama o coelho_stand1 de novo (no campo "Nome_da_próxima_Frame_de_animação")... Este não é o caso da próxima animação que analisaremos: a animação de ataque!
//
// animacao de ataque do coelho
//
void() coelho_atk1 =[ 27, coelho_atk2 ] { ai_face(); };
void() coelho_atk2 =[ 28, coelho_atk3 ]
{
ai_face();
// chama a funcao de tiro do coelho (...)
coelho_fire();
// "flash" do tiro
self.effects = self.effects | EF_MUZZLEFLASH;
};
void() coelho_atk3 =[ 29, coelho_atk4 ] { ai_face(); };
void() coelho_atk4 =[ 30, coelho_atk5 ] { ai_face(); };
void() coelho_atk5 =[ 31, coelho_atk6 ] { ai_face(); };
void() coelho_atk6 =[ 32, coelho_run1 ]
{
ai_face();
// isto aqui e' para o nightmare (...)
SUB_CheckRefire (coelho_atk1);
};
E tem outra função de I.A. sendo chamada durante toda a animação: ai_face(); ela faz com que o monstro vire um pouco na direção do seu inimigo. Assim, durante toda a animação de tiro, o monstro está ajustando o seu ângulo na sua direção. Isto muda os ângulos de rotação do modelo, que ele usa para ver se pode atirar em você ou não (se ele estiver muito de costas, não atira)
Note também que na segunda frame há uma chamada para coelho_fire(). Esta função está declarada logo acima (a gente que fez). É igual ao tiro do grunt... Na última frame há uma chamada à outra função (check-refire) que é para a dificuldade "nightmare" aonde os monstros atiram sem parar e não é de grande importância;
Bem, você viu que esta animação não é cíclica? A última frame chama a coelho_run1, a primeira frame de animação da sequência de 'run' (coelho correndo); isto retorna o coelho à perseguição do seu inimigo. Falando nisso...
//
// animacao do coelho correndo
//
void() coelho_run1 =[ 33, coelho_run2 ]
{
ai_run(5);
// toca um som de vez em quando
if (random() < 0.2)
sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE);
};
void() coelho_run2 =[ 34, coelho_run3 ] { ai_run(10); };
void() coelho_run3 =[ 35, coelho_run4 ] { ai_run(20); };
void() coelho_run4 =[ 36, coelho_run5 ] { ai_run(30); };
void() coelho_run5 =[ 37, coelho_run6 ] { ai_run(10); };
void() coelho_run6 =[ 38, coelho_run1 ] { ai_run(5); };
Aquele trecho do "sound" toca o "arf" do grunt quando ele está perseguindo você. O if (random() < 0.2) serve para executar o som apenas com 20% de chance, cada vez que passar por ali (pro bicho não ficar gemendo muito que enjoa, hehehehe...)
O importante é o uso da função ai_run(). Mais uma vez, temos uma função de I.A. prontinha para uso. O parâmetro da função (aquele número entre parênteses) é o número de unidades que o monstro se mexe para a direção que estiver olhando; Você viu como o coelhinho parece estar saltitando? Veja como os valores são diferentes: (pega impulso) 5, 10, (pula) 20, 30 (auge do pulo), 10, 5 (parou, pula denovo).. Ficou legalzinho, né? :) As funções ai_pain() e ai_walk() funcionam de maneira semelhante.
Ok, mas aonde está escrito que o coelho deve chamar coelho_run1 para correr, coelho_stand1 para ficar parado etc?
Bom, vá para a função de spawn do coelho (aquela lááá de baixo, a monstro_coelho()), veja este trecho:
self.th_stand = coelho_stand1; // funcao de stand (parado)
self.th_walk = coelho_walk1; // funcao de walk (andar)
self.th_run = coelho_run1; // funcao de run (correr atras)
self.th_missile = coelho_atk1; // funcao ataque de missil (ataque longo)
self.th_pain = coelho_pain; // funcao de pain ("dor")
self.th_die = coelho_die; // funcao de death (morte)
Os "th_stand", "th_walk" etc. são usados pelas rotinas genéricas de manipulação de monstros (ai.qc, monster.qc, etc.). Assim, quando o Quake descobre, por exemplo, "este monstro morreu" (health < 0), ele chama automaticamente a função apontada por "th_die". Pô... que automático hein? :)
Note que as funções de "death" e "pain" não apontam direto para funções de animação: Por exemplo, a função de death é a coelho_die():
void() coelho_die =
{
// olha só: primeiro verifica se o coelho não
// foi "gibado" (explodido). neste caso, ao invés
// de rodar a animação de morte, apenas espalha um
// monte de carne
if (self.health < -35)
{
// som de gib.. ehehe.. gib rulez..
sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM);
// cabeca
ThrowHead ("progs/gib2.mdl", self.health);
// gibz
ThrowGib ("progs/gib3.mdl", self.health);
ThrowGib ("progs/gib3.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
ThrowGib ("progs/gib1.mdl", self.health);
return;
}
// morte normal: inicia animacao
coelho_die1 ();
};
Primeiro ele checa para ver se o coelho foi gibado. Senão, inicia a animação, chamado coelho_die1()...
Normalmente, utiliza-se um comando remove(entidade); depois que acaba o seu uso, mas os monstros gibados usam 'ThrowHead', que já usam a entidade do monstro para ser a cabeça dele. Os 'ThrowGib' servem para adicionar mais entidades 'pedaco de carne', jogando-as para os lados.
E não usa-se o 'remove' na última frame de animação normal de morte? Não: a animação fica 'presa' na última frame, pro monstro ficar ali, estirado no chão:
...
void() coelho_die12 =[ 24, coelho_die13 ] {};
void() coelho_die13 =[ 25, coelho_die14 ] {};
void() coelho_die14 =[ 26, coelho_die14 ] { };
Se você quisesse remover o coelho logo após acabar a animação de morte (fica estranho, hehe...) você usaria:
void() coelho_die14 =[ 26, SUB_Remove ] { };
A função SUB_Remove() apenas possui um comando: remove(self);, ela está definida em subs.qc caso queira dar uma olhada. É só para facilitar a vida;
Bom, acho que não vamos nos aprofundar muito mais! Praticamente metade das linhas do coelho.qc são comentários, e você pode pegar uma boa idéia do que faz cada instrução lendo-os... Nos tutoriais de QuakeC veremos exemplos mais 'avançados' de monstros... Qualquer dúvida sobre este tutorial pode ser enviada para mim como de costume, o endereço está no fim do texto!
O Quake tem 90% das funções necessárias prontinhas para manipular o seu monstro, definidas em monster.qc, ai.qc e outros... Lendo os arquivos .qc de outros monstros você vê como é quase tudo igual, e como é fácil fazer monstros simples!
No próximo tutorial veremos como fazer o coelho ser um monstro NOVO, para que mapeiros possam usar o seu PROGS para fazer um forte-apache de coelhos e grunts ao mesmo tempo... :)
Mande sua opinião sobre este tutorial para mim! Obrigado!
-Spinal