Arcade  •  FAQ  •  Register  •  Login

Welcome
Bem Vindo ao oldschoolbrasil

Você está vendo nosso fórum como um visitante, o que te dá acesso limitado a maior parte do nosso fórum. Se registrando gratuitamente na nossa comunidade, você tera acesso livre por todo o fórum!
join our community today!

Iniciação ao Game Maker com foco em SHMUPS

Moderators: coffeejoerx, Mestrechronos

<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Tue Jul 07, 2009 6:38 pm

Iniciação ao Game Maker com foco em SHMUPS

OK, vamos começar um curso básico de Game Maker, linguagem e design. Vamos abordar alguns conceitos do dê-a-bá de programação e passaremos a coisas específicas do GM até chegar ao design dos jogos de nave.

Não use copy/paste. Copie manualmente os códigos e entenda para o que servem. Não baixe logo de cara os exemplos. Se tudo correr certo, nem será preciso que os baixe a não ser que queira conferir. Use os pacotes de gráficos deste curso, pois eles serão específicos para o que vamos criar. Salve seus projetos de preferência antes de testá-los. Caso esteja inseguro, salve num arquivo extra.

Última atualização do pacote 001: 10/07/2009 - 19:45


Baixe o Game Maker 7.0


AVISO: este tutorial faz uso da função image_angle e precisa do Game Maker Pro. Caso você não o tenha registrado, não poderá fazer as partes que usam esta função.

Índice
1- Termos básicos e recursos do Game Maker
2- Iniciando a produção do SHMUP
3- Os tiros do jogador e um plano de fundo básico
4- Criando um controlador
5- Criando o primeiro inimigo
6- Inimigos pt. 2
7- Inimigos pt. 3
8- Inimigos pt. 4 - Canhão ao fundo

Cada aula tem um exemplo em .GMK no final, mas só precisa ser baixado se o leitor quiser conferir o que fez. No início da aula 2, há um pacote com gráficos para cenário e sprites. Caso esteja faltando algum arquivo, baixe de novo, pois ele pode ter sido atualizado.

Quem puder divulgar, divulgue! Não deixe este tutorial ser em vão e, de quebra, ajude o fórum!
Agradecimentos ao feedback do Kollision.

COMUNICADO: já escrevi um bocado de coisas, então até eu ter mais feedbacks, vou dar um tempinho para me dedidar a outros assuntos (tempinho de alguns dias). Enquanto isso, leiam tudo e programem (nada de ficar só roubando o código - leiam, entendam e escrevam).
Last edited by Spiegel on Sat Jul 11, 2009 7:50 pm, edited 21 times in total.
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Tue Jul 07, 2009 6:50 pm

Re: Iniciação ao Game Maker com foco em SHMUPS

Termos básicos de programação e como eles funcionam no GM

Programa: conjunto de declarações (statements) que instruem o computador a determinado funcionamento.
  Code:
{
   declaração;
   declaração;
   ...
}

São escritos nos objetos (dentro dos eventos>Execute code), nas rooms (settings>Creatuon code) e em scripts. Cada programa é delimitado por chaves, mas se ele sozinho for o conteúdo de um bloco de código, então não precisa colocar chaves. Cada declaração deve terminar em ponto-e-vírgula para evitar problemas. Game Maker diferencia letras minúsculas de maiúsculas, portanto cuidado.

Variável: um espaço na memória reservado para receber dados - números reais ou textos. Eles são identificados por nomes. Para criar uma variável, basta digitar um nome começado por letra ou underline e atribuir um valor qualquer.
  Code:
a=100; //Variável a é igual ao número 100.
zz=5.5; //Variável com valor numérico 5.5 (com ponto, não vírgula).
nome="Vic Viper"; //Variável nome com valor em texto (string - entre aspas) Vic Viper.

Algumas variáveis já são pré-definidas como:

x - coordenada horizontal na tela
y - coordenada vertical na tela
id - identificador individual de instância de objetos.

Variáveis individuais funcionam dentro de um objeto e valem apenas internamente, não afetando outros objetos. O valor atribuído fica na variável até ser trocado e desaparece da memória assim que a instância do objeto for destruída.

Variáveis gerais: identificam aspectos do sistema, como mouse_x e mouse_y, são acessíveis de qualquer ponto do jogo, mas nem sempre podem ser modificadas via código (essas citadas só mudam conforme o mouse se move); [CONFIRMAÇÃO?]

Variáveis globais: a partir do momento que são criadas, são acessíveis de qualquer ponto do jogo (qualquer objeto e qualquer código de sala). Para criá-las, basta usar o prefixo global. .
  Code:
global.explosion = 500; //Variável global no valor numérico de 500.


Array: também chamado de vetor ou matriz, são variáveis dimensionais.
  Code:
a[0]=1;
a[1]=50;

Array monodimensional a cujo índice [0] vale 1 e índice [1] vale 50;
  Code:
a[2,3]=37;

Array bidimensional cujo índice [2,3] vale 37. São iguais a uma tabela de batalha naval, cujas coordenadas armazenam e retornam um valor específico. A importância de arrays será vista mais tarde.

Função: comando em código que utiliza um conjunto de argumentos (de zero a vários)

instance_create(x,y,object) - função para criar instância de objeto durante o jogo. x e y são coordenadas, object é o nome.
Variáveis podem ser usadas como argumentos. Exemplo:
  Code:
{
   a=50;
   b=100;
   instance_create(a,b,obj_avatar); // Criar instância do objeto obj_avatar com x=a e y=b.
}

Eis uma função sem argumentos:
  Code:
instance_destroy();

Ainda assim, ela tem parênteses

Scripts: um conjunto de comandos que pode ser "puxado" de fora do objeto. São criados num bloquinho específico e ganham um nome.

Exemplo:

scr_MeuScript

Eles podem levar argumentos e funcionam como funções. Veja este ser executado, sem usar argumentos:

scr_MeuScript();

Supondo agora que ele leve argumentos:

  Code:
scr_MeuScript(60,90,x,y,c_white);


Agora, ele aceita como argumentos:
0: 60
1: 90
2: x (coordenada x)
3: y (coordenada y)
4: c_white (cor branca, pré-definida pelo GM)

Comentário: linhas ignoradas na execução, mas que ajudam o programador a se orientar. Existem em duas formas:
Linha por linha
  Code:
//Toda vez que eu colocar duas barras, o que vier depois é comentário.
//E, na linha seguinte, repito.

Várias linhas
  Code:
/*
Depois da barra-e-asterisco e antes do asterisco-e-barra,
tudo é comentário.
Lá, lááááá, lá, lá, lá, lááááá
Lá, lá, lá, lá, láááááá
É a música dos Smurfs!
*/


Operadores: sinais usados em operações lógicas e matemáticas.
Sinais lógicos:
  Code:
&&    E
||   Ou (um, o outro ou ambos)
^^   Ou um, ou o outro; nunca os dois

Comparadores de valor:
  Code:
==   Igual a
!=   Diferente de
>   Maior que
<   Menor que
>=   Maior ou igual a
<=   Menor ou igual a

Operadores matemáticos:
  Code:
+   Adição
-   Subtração   
*   Multiplicação
/   Divisão
mod   Resto da divisão
=   Igual (atribui valor)


Além disso, para alterar o valor de uma variável com base no seu valor original:
  Code:
a+=10;   significa a=a+10
a-=1   significa a=a-1
a*=2;   significa a=a*2;
a/=2;   significa a=a/2;

Loops e blocos condicionais:
Declaração if: cria um programa que roda se uma proposição for verdadeira

  Code:
if a==0   instance_create(10,20,obj_qualquer);

Se a for igual a 0, cria-se o objeto de nome obj_qualquer na coordenada x=10,y=20. Não precisa usar chaves quando o bloco só tem uma declaração.

  Code:
if a==true
{
   x=10;
   y=5;
}

Se a for verdadeiro, então o objeto se teletransporta para as determinadas coordenadas entre chaves

  Code:
if a>=10 && b!=50
{
   image_blend=c_green;
   instance_create(a,b,obj_blublu);
}

Se a for maior ou igual a 10 e b não for 50, então o objeto se pinta de verde e cria um obj_blublu nas coordenadas x=a e y=b.

  Code:
if (a>10 && b==30) || (a<=10 && b==20)   instance_destroy();

Se um dos dois conteúdos entre parênteses ocorrer, o objeto se destrói. Ou seja:
Tanto no caso de a ser maior que 10 e b igual a 30 quanto no de a ser menor ou igual a10 e b igual a 20, o objeto se destrói.

Declaração else
Sempre que o teste de um if der falso, pode ser definir uma alternativa com else (senão).

  Code:
if a==10
{
   image_blend=c_red;
   b=20;
}
else
{
   image_blend=c_blue;
   b=50;
}

Se a for igual a 10, o objeto se pinta de vermelho e atribui 20 a b. Senão ele se pinta de azul e atribui 50 a b.

Declaração repeat

  Code:
repeat (5) instance_create(random(400),random(400),ball);

Repete 5 vezes a criação de um objeto chamado ball, com coordenadas aleatórias até um número menor que 400 para x e y.

  Code:
repeat (5)
{
   instance_create(random(400),random(400),ball);
   a+=1;
}

Como acima, só que ainda acrescenta 1 ao valor de a cada vez que executar.


Declaração while: Enquanto algo for verdadeiro, o jogo executa o bloco.
  Code:
while a<50
{
   instance_create(random(400),random(400),ball);
   a+=1;
}

Enquanto a<50, cria bola e aumenta a em 1.

Declaração do: executa um bloco até que algo aconteça.
  Code:
do
{
   instance_create(random(400),random(400),ball);
}
until (instance_number(ball)>50)

Faça instâncias de objeto ball até que o número de instâncias ultrapasse 50.

Declaração for
  Code:
for(declaração 1; declaração 2; declaração 3)
{
   programa
}

Crie uma variável na declaração 1 com um valor inicial (ou use uma já existente atribuindo um novo valor inicial). Indique uma condição na declaração 2. Manipule o valor da variável na declaração 3. Quando a variável não mais atender a condição da declaração 2, o loop pára.
  Code:
for(i=0;i<=5;i+=1)
{
   x+=1;
   y-=2;
}

Seja i igual a 0. Enquanto i for menor ou igual a 5, executa o loop entre chaves e, no fim, aumenta i em 1. Quando i for maior que 5, para o loop e continua com o código a seguir.

Declaração break: pára um bloco de loop ou bloco de programa imediatamente.

Declaração exit: sai de um bloco de código ou de um script. Útil para finalizar eventos ou o uma janela de códigos.
  Code:
if x > 400   exit;

Assim, se x>400, sai do bloco. Caso contrário, continua normalmente.

Na próxima, vou falar de coisas mais específicas do GM, como objeto, room, eventos etc. e como usar códigos para manipulá-los.

Recursos do Game Maker

Sprites: elementos gráficos em bitmap usados pelos objetos do jogo.
Sounds: músicas e efeitos sonoros em .WAV, .MID e .MP3.
Background: imagens de fundo e tiles para cenários.
----- Tileset: uma imagem bmp dividida em partes iguais onde cada pedaço serve como uma unidade para montar o cenário.
Paths: caminhos pré-determinados para algum objeto seguir na tela.
Scripts: códigos externos aos objetos que podem ser chamados por qualquer um deles ou de uma sala durante sua criação.
Fonts: fontes do jogo. O computador deve possuir uma cópia de cada arquivo de fonte utilizada pelo jogo. Tamanho e estilo são fixos.
Time Lines: sequências de programas executados por linha do tempo que podem ser executadas a partir de algum objeto.
Objects: todo e qualquer elemento ativo durante um jogo. Podem ser inimigos, o jogador, um controlador invisível de mecânica ou um coqueiro no cenário.
----- Instance: cada cópia de um objeto criada durante um jogo. O objeto é a matriz, enquanto a instância é a execução e pode ser feita várias vezes simultaneamente. Cada instância possui um valor de id único, diferente do nome do objeto.
----- Parent: um objeto determinado do qual os filhos assumem a identidade (não confundir com id). Se uma função for executada para afetar todas as instâncias de um parent, os objetos filhos são afetados, mesmo que o parent não tenha uma instância ativa no instante.
Rooms: fases ou telas, são os ambientes do jogo. Podem ser preenchidas de backgrounds, tiles ou objetos previamente ou durante o jogo.

Para um jogo rodar, é preciso ao menos criar uma room, mesmo que vazia. Na janela da room criada, em settings, existe um campo para o nome, legenda de janela, dimensões, velocidade em steps (nosso frames per second), uma opção para persistente (deixe desligada por enquanto) e um botão que dá na janela de código de criação (códigos rodados assim que o jogo entra na room).
Em backgrounds, há a possibilidade de atribuir uma cor ao fundo da sala (bom na falta de cenário, mas consome recurso à toa se não aparecer). Abaixo, 8 camadas de cenário que podem ser preenchidas com imagens simples, repetidas em tiles ou esticadas, posição de cada imagem na camada e velocidades horizontal e vertical.
Tiles permite "carimbar" unidades de tiles (azulejos) pela tela, indicando uma camada por valor numérico (número gigante de camadas possíveis, apenas indique um valor de profundidade relativa).
Em views, no caso de habilitá-los, você pode definir os espaços da tela que serão exibidos. View in room define posição inicial (canto esquerdo superior) e dimensões do view na tela. Port on screen define o espaço da tela a ser ocupado, forçando o view a ser escalado para caber nele caso seja de tamanho diferente. Object following especifica um objeto a ser seguido pelo view através da room. Hbor e Vbor indicam as margens da tela nas quais o objeto precisa estar para arrastar o view com ele. Hsp e Vsp são a velocidade com que o view segue o objeto (-1 para na mesma velocidade).

EVENTOS
Crie um objeto. Temos algumas opções na esquerda: sprite inicial, visibilidade, solidez, profundidade, persistência, paternidade e máscara. Voltaremos a elas depois. Clique em Add Event. Falarei deles agora.

Evento é algo que acontece durante um step (frame). Cada evento não dura mais de um step, mas pode se repetir no seguinte. Vamos listá-los:
CREATE: Momento da criação da instância do objeto. Só ocorre uma vez. As variáveis mais importantes de cada objeto são criadas aqui.
DESTROY: A última coisa que a instância faz antes de ser apagada. Por exemplo, um inimigo pode criar um bônus assim que some após morrer.
ALARM: Após definir um alarme de tempo, o evento de alarme respectivo é executado quando o tempo chega a zero. São doze no total (0 a 11).
STEP: Os mais importantes de todos, ocorrem em todos os steps propriamente ditos. Se divide entre begin step, step e end step.
COLLISION: Define o que acontece somento no momento em que dois objetos sobrepõem suas caixas de colisão.
KEYBOARD: Um conjunto de eventos para quando alguma determinada tecla estiver sendo segurada.
MOUSE: Idem para mouse.
OTHER: Vários, como sair da sala, da view ou encostar nos limites, início e fim de jogo ou sala, fim de vida ou energia, fim de animação, fim de path, fechamento de jogo e definidos por usuário.
DRAW: Um outro evento importante, ocorre no fim de cada step e servem para desenhar algo na tela. Quando o evento draw é selecionado, a sprite do objeto não será mais desenhada automaticamente, mas um vasto número de opções estão disponíveis, como desenhar 'manualmente' a sprite. O objeto precisa estar visível para que algo seja desenhado.
KEY PRESS: Detecta se determinada tecla foi digitada desde o último step (o ato de digitar, não o de segurar).
KEY RELEASE: Detecta se determinada tecla foi solta desde o último step.

Na verdade, apenas 4 eventos são essenciais: CREATE, STEP e DRAW, porém é quase impossível viver sem COLLISION, DESTROY também possibilita certa praticidade e BEGIN STEP/ENDSTEP podem ser úteis quando algo tiver de ser definido não só após ou antes de certos comandos num objeto, mas também de todos os outros. E DRAW, apesar de essencial, não é para o absoluto principiante - os jogos mais simples não necessitam dele.

Nesse curso, procuraremos abandonar o Drag'n'Drop - programação por ícones e inúmeras janelinhas. Centraremos nosso trabalho no seguinte recurso:
Aba Control > campo CODE > ícone EXECUTE CODE
Outros comandos úteis são COMMENT e, num primeiro momento, MAIN 2 > GAME > RESTART, pois será útil dar reset na hora de testar, embora ele não seja tão prático num produto final (ele apaga as variáveis e só deve ser usado no caso das globais serem armazenadas num arquivo .INI).

Neste site, você pode encontrar o código correspondente para cada ícone d'n'd.
http://www.blackratstudios.com/games/DD_to_GML_7/Drag_and_Drop_Icons_and_their_GML_Equals_Ver_7.html
Guarde-o nos favoritos pois ele será útil.


Rito de passagem:
- Crie um objeto.
- Crie nele um evento DRAW. Dentro do evento, coloque um EXECUTE CODE (aba control). Abra o bloco.
- escreva:
  Code:
draw_text(0,0,'Hello, world!');

Aspas podem ser duplas ou simples, só mantenha o padrão dos dois lados.
- Crie uma room. Na aba object, escolha object0 e coloque ele em qualquer lugar da tela.
- Rode o programa clicando na seta verde de play (na barra de ferramentas).
- ??????
- PROFIT!

Considerações sobre blocos aninhados
Caso seja necessário inserir um bloco de programa dentro do outro, você pode. Isso será necessário ao usar condicionais, loops, etc. Exemplo

  Code:
if a==10
{
    if x>=50
    {
        y+=5;
        lives+=1;
    }
    else
    {
        y-=5;
        lives+=2;
    }
}


Ou sem aninhar, podemos usar else if e, numa situação em que a==10 && x>=50 sejam falsos, caso ao menos a==10 seja verdadeiro,

ele executa a parte do else if
  Code:
if a==10 && x>=50
{
        y+=5;
        lives+=1;
}
else if a==10
{
        y-=5;
        lives+=2;
}


Ou seja, tudo aqui só acontece se a for igual a 10. Se x for maior ou igual a 50, o primeiro sub-bloco é executado. Senão, o segundo bloco é.

Isso também pode ocorrer:
  Code:
if a==50
{
    if b==10
    {
         if c==5
         {
              draw_text(0,0,'Eu sou um imbecil!');
          }
    }
}

Só que é muito melhor fazer assim:
  Code:
if a==50 && b==10 && c==5
{
    draw_text(0,0,'Eu não sou mais um imbecil!');
}


Use parênteses quando for usar mais de um símbolo lógico
  Code:
if a==50 && (b==10 || b==20)
{
    draw_text(0,0,'Conjunção e disjunção no mesmo if.');
}


Tabela de verdade
Vamos ver como funcionam valores de verdade para comparações lógicas. V significa verdadeiro (true ou 1) e F significa falso (

false
ou 0).

  Code:
&& - and / e

a  |  b  | a && b
V  |  V  |       V
V  |  F  |       F
F  |  V  |       F
F  |  F  |       F

Quando perguntando se a && b são verdadeiros, o resultado V só sai quando ambos são verdadeiros. Em todos os outros, o bloco if não

executa (caindo num else se houver)


  Code:
|| - or / ou

a  |  b  | a || b
V  |  V  |       V
V  |  F  |       V
F  |  V  |       V
F  |  F  |       F

Quando perguntado se a || b é verdadeiro, sempre que ao menos um deles é verdadeiro, o resultado é verdadeiro também.


  Code:
^^ - xor / ou excludente (ou um, ou outro, não ambos)

a  |  b  | a ^^ b
V  |  V  |       F
V  |  F  |       V
F  |  V  |       V
F  |  F  |       F

No caso de a^^b, apenas quando um deles é verdadeiro, o resultado é verdadeiro. Para ambos V ou F, dá F.


Mais sobre atribuição de valores

Funções retornam valores. Isso significa que o resultado de uma função pode ser atribuído a uma variável. A criação de instâncias de objetos

retorna o número de id da instância e isso nos será útil. Veja por quê:

  Code:
//Step de um inimigo, dentro de um bloco de programa
{
   a=instance_create(x,y,obj_enemybullet);
   a.direction=270;
   a.speed=2;
}

Explicação:
- Na declaração 1 criamos uma instância do objeto obj_enemybullet nas coordenadas x e y da instância do inimigo. Colocamos a= na frente para que o valor da id da instância criada seja atribuído a a.
- Na declaração 2, nós manipulamos uma variável pré-definida de nome direction, mas é uma variável pertencente a a, pois colocamos o prefixo a. antes do nome da variável. Assim, a direção da instância cujo id corresponde ao valor de a é 270°.
- Na declaração 3, fazemos a mesma coisa para a variável speed de a: damo-lhe valor 2, assim a instância obj_enemybullet se moverá 2 pixels por step na direção que for determinada (270°).

Sobre nomes
Procure nomear cada recurso conforme sua natureza. Isso facilita não só a identificação, mas possibilita dar nomes semelhantes a recursos relacionados. Por exemplo:

Sprite de uma nave inimiga: spr_enemyship ou sprEnemyShip ou SprEnemyShip etc. Qualquer coisa que lembre que isto é uma sprite.
Objeto da nave inimiga: obj_enemyship ou objEnemyShip ou ObjEnemyShip etc. Qualquer nome que lembre que este é o objeto da nave, mas não sua sprite.

Agora você não terá problemas com referências cruzadas e terá uma idéia melhor de que recursos se relacionam, no caso, a nave inimiga terá cara de nave inimiga, e não de uma ilha, porque você não tem como se confundir.
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Wed Jul 08, 2009 10:21 pm

Re: Iniciação ao Game Maker com foco em SHMUPS

Vamos direto ao assunto, pois, do contrário, vai ficar muito maçante. Baixe este pacote de imagens e salve numa pasta: Pacote 001.

VAMOS COMEÇAR NOSSO VERTICAL SHOOTING GAME

Inicie o GM e crie uma Room. Em settings, modifique Width para 360 e Speed para 60. Assim, nós teremos uma tela de orientação vertical e uma velocidade de 60 steps por segundo.
Image
Clique em |> para rodar. Se tudo der certo, uma tela cinza deve aparecer. Saia, voltando ao GM.

Crie um sprite de nome spr_avatar. Carregue o arquivo vicviper.gif e centralize o ponto de origem (botão [Center]). Na bounding box, vamos delimitar a área da caixa de colisão, o espaço da sprite que colidirá com os inimigos e tiros. Se você quiser algo bem antigão, deixe em Full Image e marque Precise collision checking, assim, toda área não transparente da sprite será a caixa de colisão (não se esqueça de marcar Transparent). Se quiser algo mais moderno, diminua manualmente até o limite desejado.
Image Não marque Smooth edges, pois isso custa recursos. Se no fim quiser, faça, mas não agora.
Clique em Edit Sprite e veja que ela possui subimagens de 0 a 8, correspondentes a quadros de animação. Veja o preview se quiser. A subimagem 4 é a nave paralela à tela e o resto inclina para os lados.

Crie um objeto de nome obj_avatar para ser a nave do jogador. No campo sprite, escolha spr_vicviper. Deixe Visible marcado e Solid vazio. Deixe o resto como está e adicione um evento CREATE. Nele, insira um bloco de código (aba control >Execute Code).
Image
Entre no bloco de código e escreva:
  Code:
image_speed=0;
image_index=4;

image_speed define a velocidade da animação do sprite. Valor 1 significa que a cada step, a animação mostra uma subimagem. Valor .5 significa que cara subimagem dura 2 steps (1 imagem/2 steps = .5). Valor 2 significa que alguma subimagem será pulada para a velocidade dobrar.

image_index indica a subimagem que vai aparecer naquele step, no caso, subimagem 4 (a da nave reta). Como a velocidade da animação está em 0, a nave ficará em 4 até o quadro ser mudado manualmente ou a velocidade ser alterada.

Volte à room1. Na aba objects escolha obj_avatar e o insira em qualquer parte da sala. Rode. Se tudo der certo, a nave aparecerá na janela. Saia e volte ao GM.

MOVIMENTAÇÃO

No obj_avatar, insira um evento STEP e, dentro dele, um bloco de código. Vamos escrever um bloco condicional (if) testando a veracidade de uma função:
  Code:
if keyboard_check(vk_left)
{
    x-=4;
}

Explicação:
keyboard_check(tecla) verifica se dada tecla está sendo pressionada. O valor tecla deve ser uma variável que remete a uma tecla a ser testada. Nesse caso, a seta para esquerda. O nome das setas no Game Maker são esses:
vk_left
vk_right
vk_up
vk_down
Se a tecla estiver sendo segurada, o valor retornado é 1 (true). Se perguntamos if keyboard_check(vk_left), quando o valor retornado é 1, o bloco executa. Quando o valor retornado é 0, não executa. É a mesma coisa que perguntar if keyboard_check(vk_left)==1.

Dentro das chaves temos x-=4. Diminuímos em 4 a coordenada x do objeto (instância, na verdade) do jogador, o que significa que a nave anda para a esquerda.

Pode rodar e ver como acontece (a nave até sai da tela). Retorne ao GM para colocar as outras direções.

  Code:
if keyboard_check(vk_left)
{
    x-=4;
}

if keyboard_check(vk_right)
{
    x+=4;
}

if keyboard_check(vk_up)
{
    y-=4;
}

if keyboard_check(vk_down)
{
    y+=2;
}


Você deve ter notado que, na vertical, a coordenada é y. Mas deve ter estranhado por que ela aumenta para baixo e não para cima. Simples: as coordenadas no GM (e geralmente em qualquer outro programa que lida com gráficos na tela) são de cabeça para baixo, pois a primeira linha de um monitor é a de cima. Igualzinho ao editor de texto que vai do alto da página até seu pé. Rode de novo e veja como a nave se move em cruz e xis. Ajuste as velocidades ao seu gosto, mas, de preferência, deixe-a simétrica na horizontal e faça com que a ré seja mais lenta do que o avanço.

Vamos agora limitar a nossa nave ao espaço interno da tela:
  Code:
if x<30  x=30;
if x>(room_width-30)  x=(room_width-30);
if y<70  y=70;
if y>(room_height-50)  y=(room_height-50);

O que temos aqui? Mais duas variáveis pré-definidas! room_width eroom_height indicam largura e altura da room respectivamente. Use-as, pois caso queira alterar o tamanho da sala, este código ainda funcionará. Vamos traduzir o código:

Se minha coordenada horizontal for além de 30 pixels na margem esquerda, eu volto para o pixel 30 na horizontal.
Se minha coordenada horizontal for além de do limite direito da sala menos 30 pixels, eu volto 30 pixels antes do limite direito.
Se minha coordenada vertical for além de 70 pixels na margem superior, eu volto para o pixel 70 na vertical.
Se minha coordenada vertical for além de do limite inferior da sala menos 50 pixels, eu volto 50 pixels antes do fim inferior da sala.

Rode e confira.

Notou que usei chaves com blocos de uma linha só na parte da movimentação? Vamos acrescentar algo para deixar a coisa toda mais bonita. Modifique o código do movimento até que tenhamos isso:

  Code:
//--------------------------CONTROLAR MOVIMENTO
if keyboard_check(vk_left)  //Se apertar para esq...
{
    x-=4;
    if image_index>0    image_index-=1; //Animar para o lado
}

if keyboard_check(vk_right) //Se apertar para dir...
{
    x+=4;
    if image_index<8    image_index+=1;  //Animar para o lado   
}

if keyboard_check(vk_up) //Se apertar para cima...
{
    y-=4;
}

if keyboard_check(vk_down) //Se apertar para baixo...
{
    y+=2;
}

//------------------ANIMAR DEVOLTA À POSIÇÃO INICIAL
if keyboard_check(vk_right)==0 && keyboard_check(vk_left)==0 && image_index!=4
{
    if image_index>4    image_index-=1;
    else image_index+=1;
}


Explicando: sempre que a nave for movida para os lados, ela aumenta ou diminui a subimagem até o limite. Quando tanto <-- quanto --> são soltos, a nave rola de volta para a posição reta. Como você já deve ter percebido, é um pouco problemático apertar posições opostas e tal problema pode ser resolvido, mas não é comum que um jogador queira apertar duas teclas opostas e também é raro que ele se atrapalhe por isso, logo deixe estar. Se isso for um problema, use Joy2Key e jogue com um gamepad.

Resumo da ópera - objeto obj_avatar:
CREATE:
  Code:
image_speed=0;
image_index=4;


STEP
  Code:
//--------------------------CONTROLAR MOVIMENTO
if keyboard_check(vk_left)  //Se apertar para esq...
{
    x-=4;
    if image_index>0    image_index-=1; //Animar para o lado
}

if keyboard_check(vk_right) //Se apertar para dir...
{
    x+=4;
    if image_index<8    image_index+=1;  //Animar para o lado   
}

if keyboard_check(vk_up) //Se apertar para cima...
{
    y-=4;
}

if keyboard_check(vk_down) //Se apertar para baixo...
{
    y+=2;
}
//------------------ANIMAR DEVOLTA À POSIÇÃO INICIAL
if keyboard_check(vk_right)==0 && keyboard_check(vk_left)==0 && image_index!=4
{
    if image_index>4    image_index-=1;
    else image_index+=1;
}

//-------------------------------------------LIMITAR MOVIMENTO NA TELA
if x<30  x=30;
if x>(room_width-30)  x=(room_width-30);
if y<70  y=70;
if y>(room_height-50)  y=(room_height-50);


EXEMPLO DA AULA EM .GMK: GM_TUTO_SHMUP_ex0001.rar

Na próxima aula: aprenda o sexo dos bonobos e descubra os limites do seu prazer.
Image
Ok, brincadeirinha.
Last edited by Spiegel on Thu Jul 09, 2009 8:52 pm, edited 1 time in total.
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Thu Jul 09, 2009 9:01 am

Re: Iniciação ao Game Maker com foco em SHMUPS

TIROS DO JOGADOR

Crie um sprite de nome spr_avatarshot e carregue o arquivo shot.gif. O configure assim:
Image
Não há necessidade da colisão ser precisa. O transparent indica se o campo em vonta da imagem será visível ou não. Tal campo deve conter a mesma cor que o pixel do canto esquerdo inferior - ou seja, todo pixel nessa exata cor será invisível.

Crie agora um objeto obj_avatarshot usando a sprite acima e com Depth 1. Vamos fazer com que o tiro desapareça assim que sair da tela. Existem duas formas de fazer isso: uma é com o evento OUTSIDE ROOM, a outra é com códigos. Faremos em código, pois assim damos um espaço maior para o tiro, indo um pouco além dos limites da sala.
Image
  Code:
if x<-10 || x>room_width+10 || y<-10 || y>room_height+10
    instance_destroy();


Voltemos ao obj_avatar. No seu CREATE, acrescentemos mais estas variáveis:
  Code:
can_fire=1;
can_move=1;
can_die=1;
fire_delay=0;

Elas servirão para definir o que pode e o que não pode acontecer com o jogador. Se can_fire==1, então o jogador pode atacar; se can_fire==0, não pode. Se can_move==1, o jogador pode se mover e se for 0, não. Se can_die==1 o jogador pode morrer, mas se 0, não.
fire_delay será o intervalo entre os tiros.

Vamos agora ao STEP. Pegue toda a parte de movimentação e insira num grande bloco condicional:
  Code:
if can_move==1
{
    if keyboard_check(vk_left)  //Se apertar para esq...
    {
        x-=4;
        if image_index>0    image_index-=1; //Animar para o lado
    }

    if keyboard_check(vk_right) //Se apertar para dir...
    {
        x+=4;
        if image_index<8    image_index+=1;  //Animar para o lado   
    }

    if keyboard_check(vk_up) //Se apertar para cima...
    {
        y-=4;
    }

    if keyboard_check(vk_down) //Se apertar para baixo...
    {
        y+=2;
    }
    //------------------ANIMAR DEVOLTA À POSIÇÃO INICIAL
    if keyboard_check(vk_right)==0 && keyboard_check(vk_left)==0 && image_index!=4
    {
        if image_index>4    image_index-=1;
        else image_index+=1;
    }
}

Você deve ter percebido esse espaço antes das declarações. Isso se chama indentação. Indentamos as declarações dentro de blocos para a leitura ficar mais fácil. Basta apenas apertar Tab no início da linha de cada declaração entre chaves. Indentação é cumulativa, ou seja, as chaves dentro de chaves são indentadas e o conteúdo delas é mais indentado ainda, como pode ser visto acima.

Fizemos isso para que a nave só se mova no caso de can_move ser igual a 1. Isso é útil se o designer quiser que a nave fique inativa durante alguns momentos, como passagem de fases ou noutros em que o programa manipulará a nave deixando ela não-interetiva durante certas transições. Aqui vai um segredo: é possível deixar a nave ativa desde o início do jogo, mesmo nos menus, se ela estiver completamente inativa e invisível. Isso é uma escolha de design, não sendo necessária, mas perfeitamente possível.

Vamos fazer a nave do jogador atirar. Dentro do STEP dele, escrevamos o seguinte código no fim do bloco:
  Code:
//------------------------------------------ATAQUE
if can_fire==1 && fire_delay==0 && keyboard_check(ord('Q'))
{
    a=instance_create(x,y,obj_avatarshot);
    a.direction=90;
    a.speed=10;
    fire_delay=10;
}

if fire_delay>0     fire_delay-=1;
//----------------------------------------

Nós perguntamos aqui se can_fire é verdadeiro, se fire_delay é zero e se a tecla Q está pressionada. O nome da tecla Q no Game Maker é ord('Q'), pois toda letra tem um nome nesse padrão, estando ela maiúscula entre aspas nos parênteses após ord. Caso queira outra letra, troque o Q pela desejada, mas deixe do jeito que está para não se confundir mais tarde.

Caso as condições forem encontradas, a instância de um tiro será criada no centro (ou eixo) do jogador.
a=instance_create(x,y,obj_avatarshot);
Já vimos a função instance_create. Por que tem um a= antes dela? Porque ela retorna o número da id do tiro que acabamos de dar e, sendo assim, a variável a armazena o valor da id. Com o valor de a definido como a id do tiro, vamos mudar algumas características desse tiro em particular:
a.direction=90;
a.speed=10;

Quando colocamos a. (a e ponto) antes de uma variável, significa que essa variável pertence a a, que no caso indica a instância do tiro ainda a pouco criada. Ou seja, criamos um tiro e lhe damos direção e velocidade.
direction é a direção em graus de um objeto. O grau 0 é para a direita e, anti-horariamente, o valor sobe. 90 é para cima, 180 para esquerda, 270 para baixo e 360 é de novo para direita. Cuidado ao aumentar o direction para além de 359, pois o programa não reconhecerá que, por exemplo, 370 é menor que 90 embora seja equivalente a 10.
speed é velocidade em pixels. Definimos em 10, mas se houver problema, baixaremos. O problema pode ser o seguinte: se for muito rápido, o objeto pode pular aquilo em que deveria colidir, pois se ele está em, por exemplo, y=20 e andar 10px para cima, ele se teletransporta para y=10 e assim por diante. Mas isso não parece ser problema neste caso, pois o tiro tem 10 pixels para cima e 10 para baixo, cobrindo o espaço percorrido.

fire_delay=10; - Definiremos um intervalo de 10 steps para que um novo tiro seja dado.

Fora do bloco:
if fire_delay>0 fire_delay-=1;
Isto aqui é outro condicional: se o fire_delay for maior que 0, diminua em 1 a cada step. Ou seja, quando atribuímos 10 ao fire_delay, automaticamente começamos a regredir seu valor até 0. Quando fire_delay for zero, nós poderemos atirar de novo. O resultado é uma chuva de tiros com intervalos enquanto pressionamos Q.

Achou muito chato dar um tiro só? Faremos o seguinte, então: troquemos as três primeiras declarações do bloco de ataque para isso aqui:

  Code:
    for(i=-10;i<=10;i+=20)
    {
        a=instance_create(x+i,y,obj_avatarshot);
        a.direction=90;
        a.speed=10;
    }


Se lembra do loop for? Ele recebe três declarações dentro dos parênteses e depois executa o bloco de loop. Vamos examinar as declarações entre parênteses:

i=-10; //-------- Seja i igual a -10.
i<=10; //-------- Enquanto i for menor ou igual a 10, o bloco será executado (até que a condição seja falsa, ou seja, i maior que 10).
i+=20; //-------- Após executar o bloco, aumenta i em 20 e vê se a condição ainda é verdadeira.

Em outras palavras, o bloco é executado quando i é igual a -10 e +10. Repare que o instance_create foi alterado na coordenada x
x+i - significa que o ponto x da criação do tiro será ou x-10 ou x+10. O resultado disso é que um tiro será criado à esquerda e outro à direita.

DEBUGING
Rode o jogo pelo ícone I> na barra de ferramentas. Vá em Debug Information, Tools > Show Instances. Clique na janela do jogo e solte um tiro. A listagem aumentará em duas instâncias. Assim que os tiros sumirem da tela, eles desaparecem do jogo e somem da listagem.

Resumo da ópera
STEP do obj_avatarshot:
  Code:
if x<-10 || x>room_width+10 || y<-10 || y>room_height+10
    instance_destroy();


CREATE do obj_avatar:
  Code:
image_speed=0;
image_index=4;

can_fire=1;
can_move=1;
can_die=1;
fire_delay=0;


STEP
  Code:
//--------------------------CONTROLAR MOVIMENTO
if can_move==1
{
    if keyboard_check(vk_left)  //Se apertar para esq...
    {
        x-=4;
        if image_index>0    image_index-=1; //Animar para o lado
    }

    if keyboard_check(vk_right) //Se apertar para dir...
    {
        x+=4;
        if image_index<8    image_index+=1;  //Animar para o lado   
    }

    if keyboard_check(vk_up) //Se apertar para cima...
    {
        y-=4;
    }

    if keyboard_check(vk_down) //Se apertar para baixo...
    {
        y+=2;
    }
    //------------------ANIMAR DE VOLTA À POSIÇÃO INICIAL
    if keyboard_check(vk_right)==0 && keyboard_check(vk_left)==0 && image_index!=4
    {
        if image_index>4    image_index-=1;
        else image_index+=1;
    }
}

//-------------------------------------------LIMITAR MOVIMENTO NA TELA
if x<30  x=30;
if x>(room_width-30)  x=(room_width-30);
if y<70  y=70;
if y>(room_height-50)  y=(room_height-50);

//------------------------------------------ATAQUE
if can_fire==1 && fire_delay==0 && keyboard_check(ord('Q'))
{
    for(i=-10;i<=10;i+=20)
    {
        a=instance_create(x+i,y,obj_avatarshot);
        a.direction=90;
        a.speed=10;
    }
    fire_delay=10;
}

if fire_delay>0     fire_delay-=1;
//----------------------------------------



CENÁRIO DE FUNDO
Crie um background de nome bg00 e carregue o arquivo bg00.gif. Não marque nada. Em room1, na aba background, desmarque Draw background color. Onde está <no background>, troque para bg00. Digite 1 em Vert Speed, assim o fundo rolará para baixo e nós pareceremos subir. Assim, criamos um plano de fundo bem básico com scroll. Na próxima aula, criaremos uns asteróides para deixar o cenário mais interessante.

Exemplo desta aula: GM_TUTO_SHMUP_ex0002.html
Image
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Thu Jul 09, 2009 1:12 pm

Re: Iniciação ao Game Maker com foco em SHMUPS

CONTROLADOR
Vamos criar um controlador. Faça o seguinte objeto:
Image
Não precisamos de sprite, embora ele tenha de ser visível. Marque Persistent, pois queremos que ele sobreviva à mudanças de room. Entre no código do Draw:
  Code:
draw_set_color(c_white);
draw_text(0,0,'LIVES: '+ string(lives) + '     SCORE: ' + string(score));

draw_set_color(cor) define a cor que será usada para desenhar. c_white, como vimos, é o nome da cor branca no GM.
draw_text(x,y,texto) desenha um texto na tela. Texto deve ser escrito entre aspas. Se quiser usar texto e variáveis ou mais de uma variável, utilize o sinal de +. Como estamos usando variáveis de valor real (numérico), temos de convertê-las para texto com a função string(variável).

Vá até room1, aba objects, escolha obj_control e coloque-o no canto superior da room. Uma bola com um ponto de interrogação surgirá, pois o objeto não tem sprite. Saia da room e rode o jogo.
Image

Agora temos uma barra de status, com vida e pontos. A variável pré-definida lives armazena o número de vidas (no caso, -1) e score, os pontos. Crie o evento CREATE no obj_control e escreva:
  Code:
lives=3;
Assim, teremos três vidas no início do jogo.

Vamos brincar um pouco com o cenário através do obj_control. Acrescente no CREATE do obj_contro:
  Code:
background_vspeed[0]=.5;
Agora nós teremos um fundo de tela leeeeeeeerdoooo!

Crie uma sprite spr_asteroid com o arquivo asteroid.gif. Centralize o eixo de origem e deixe o resto como está. Crie um objeto obj_asteroid com Depth 1000 e o sprite recém criado. No evento STEP dele, escreva
  Code:
if y>room_height+90     instance_destroy();
Leia o código e tente entendê-lo.

No obj_control, adicione no CREATE:
  Code:
timer=0;

No fim do STEP, escreva:
  Code:
timer+=1;


Deixe SEMPRE o timer+=1 no fim do STEP, a não ser em casos muito especiais. O que for escrito no STEP depois, deve ser escrito acima dele.

CRONÔMETROS ou CONTADORES (timer, mastertimer ou counter)
São variáveis que tem seu valor incrementado continuamente conforme o jogo roda. No fim de um objeto, haverá uma linha como timer+=1. Isso serve para que acontecimentos seqüenciais sejam disparados num momento definido. Devem ser criados geralmente no CREATE e são incrementados no fim do STEP, END STEP ou DRAW.

Usos avançados do cronômetro incluem pausas, pulos, resets. Outras formas de cronômetros podem ser regressivas e acionadas apenas em casos especiais. Mais de um cronômetro pode existir num mesmo objeto. No obj_avatar, fire_delay é um contador regressivo acionado só quando se encontra acima de 0.

Voltando ao obj_control, adicione um evento STEP e um bloco de código.
  Code:
if timer mod 120 == 0
{
    a=instance_create(room_width/2, -90, obj_asteroid)
    a.direction=270;   
    a.speed=2;
}


mod significa resto de divisão. Se timer dividido por 120 der resto 0, então um asteróide será criado no meio do alto da tela. Cada vez que timer/120 der resto 0, um asteróide será criado. Ou seja, um asteróide a cada 2 segundos (120 steps / 60 steps por segundo = 2 segundos).

Chato, não?

Vamos aumentar esse intervalo para 240 e colocar mais um bloco condicional:
  Code:
if (timer-120) mod 240 == 0
{
    for(i=0;i<=1;i+=1)
    {
        a=instance_create(70+i*220, -90-i*90, obj_asteroid)
        a.direction=270;   
        a.speed=2;
    }
}
Ok, compliquei as coisas, mas vamos lá que eu explico:

Se (timer-120)/240 der resto 0, então criamos dois asteróides: o primeiro em x=70,y=-90 e o segundo em x=70+220, y=-90-90. O for, como já foi visto, cria um loop contado. Aqui, ele vai de 0 a 1, armazenando na variável i. Depois, pegamos esse i e fazemos operações com as coordenadas iniciais do asteróide.

x=70+i*220 - Se i=0, então o resultado é x=70; se i=1, o resultado é x=290;
y=-90-i*90 - Se i=0, então o resultado é y=-90; se i=1, o resultado é y=-180;

Resultado: um asteróide esquerdo mais abaixo e outro na direita mais acima.

Mas por que tem de ser timer-120? Porque nós alteramos o primeiro asteróide programado para aparecer a cada 240 steps, correto? Precisamos do mesmo no segundo bloco, mas queremos que o par de asteróides apareçam depois do primeiro, 2 segundos depois para ser exato. Logo, este par aparecerá a cada 240 steps, só que com o cronômetro deslocado em mais 120 steps. Enquanto o primeiro bloco executa em 240, 480 , 720, o segundo executa em 360, 600,840.

Image

RESUMO DA ÓPERA:
obj_control
CREATE

  Code:
lives=3;
background_vspeed[0]=.5;
timer=0;

STEP
  Code:
//----------ASTERÓIDES NO CENÁRIO
if timer mod 240 == 0
{
    a=instance_create(room_width/2, -90, obj_asteroid)
    a.direction=270;   
    a.speed=2;
}

if (timer-120) mod 240 == 0
{
    for(i=0;i<=1;i+=1)
    {
        a=instance_create(70+i*220, -90-i*90, obj_asteroid)
        a.direction=270;   
        a.speed=2;
    }
}

//-----------TIMER
timer+=1;


obj_asteroid
STEP

  Code:
if y>room_height+90     instance_destroy();


Lembra-se que o obj_control é persistente? Marque Persistent também no obj_avatar. Ele ficará vivo o jogo inteiro e, caso necessário, o esconderemos em vez de destruí-lo.

Se você é inteligente, deve ter percebido que o mesmo que fizemos com os asteróides pode ser feito com inimigos. Vamos definir espaços de tempo ou momentos nos quais inimigos surgirão. Mas que inimigos? Aguarde.

EXEMPLO DA AULA: GM_TUTO_SHMUP_ex0003
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Thu Jul 09, 2009 8:01 pm

Re: Iniciação ao Game Maker com foco em SHMUPS

CRIANDO O PRIMEIRO INIMIGO
Crie um objeto de nome de nome par_enemy e largue ele lá como veio ao mundo.

Crie a seguinte sprite com o arquivo BlackViper.gif
Image
spr_enemy00

Crie um objeto de nome obj_enemy00, escolha para ele a sprite acima, defina Depth em 10 e, como Parent, par_enemy. Usando um parent, criaremos uma identificação única para todos os objetos filhos. Parents também podem conter os códigos para vários objetos filhos diferentes, mas nesse caso, usaremos apenas um dummy parent, ou seja, alguém que será pai de todos mais será em si vazio. Assim, toda vez que quisermos interagir com qualquer inimigo sem especificar qual, interagiremos com o par_enemy, que vale para todos.

Faça um código no CREATE do obj_enemy00. Será interessante criar variáveis para interações. Estas variáveis serão colocadas em todos os inimigos e os valores serão dados de acordo.
  Code:
hp=2; // Vida do inimigo, o quanto ele aguenta de tiros
collide=1; // Se pode colidir com os tiros e o jogador
ground=0; // Se está no chão ou voando
mode=0; // Preset de comportamento

Vamos pensar no mode mais tarde.

No STEP, vamos definir um limite no espaço do jogo para o objeto. Se ele voar para fora, ele morre.
  Code:
if y<-100 || x<-100 || y>room_height+100 || x>room_width+100
    instance_destroy();


Vamos definir uma colisão entre ele e os tiros do jogador (ADD EVENT > COLLISION > obj_avatarshot):
  Code:
if collide==1
{
    with (other) {instance_destroy();}
    hp-=1;
    score+=1;
    if hp<=0   
    {
        score+=4;   
        instance_destroy();
    }
}

No caso de collide==1, destruímos o outro objeto envolvido na colisão (o tiro), diminuímos o hp do inimigo em 1 e damos um ponto por tiro acertado. Se o hp dele acabar, daremos mais 4 pontos e destruiremos o inimigo.

with é uma declaração que determina que as próximas ações serão feitas em algum objeto ou instância. Se fosse usado o nome obj_avatarshot em vez de other, todos os tiros da tela seriam destruídos. other é a outra instância envolvida na colisão e só funciona em eventos de colisão. Usando other, apagaremos apenas o tiro envolvido, não o resto.

Vamos testar: coloque um obj_enemy00 na room1 e rode o jogo. Atire nele.

Agora que os resultados ficaram mais interessantes, vamos colocar um reset provisório no jogo para termos mais liberdade nos testes.
obj_control > Add Event > Key Press > Keypad > Keypad 0
Insira no evento press Keypad 0:
Aba main2 > Restart Game
Agora, você pode testar novamente qualquer coisa sem ter de voltar ao GM.

Nada muito chique aqui, o bicho não explode. Mas isso fica para depois. Em termos de mecânica, tudo tá indo bem. Ou quase: nada acontece quando sua nave bate no inimigo. Para um clone de Mars Matrix seria ok, mas vamos tentar algo mais tradicional. Precisamos mexer no obj_avatar

obj_avatar - CREATE:
  Code:
dead=0; //Contagem de tempo de morte.
invinci=0; //Contagem de invencibilidade

obj_avatar COLLISION com par_enemy
  Code:
//Se eu posso morrer, se o inimigo não está no chão e se ele pode colidir
if can_die==1 && other.ground==0 && other.collide=1
{
        dead=30;  //Estarei morto por 30 steps
        can_die=0; //Não posso morrer enquanto estou morto
        can_move=0; //Não posso me mover até voltar a vida de novo
        can_fire=0; //Não posso atirar enquanto isso
        lives-=1; //Perdi uma vida
}

obj_avatar STEP Escreva no alto:
  Code:
//------------------------------------MORTE E INVENCIBILIDADE
//------------ dead é um contador regressivo
if dead>0 //---- Se dead for maior que zero...
{

    if dead>20  image_alpha-=.1; // Vou ficar transparente até  dead chegar a 20 - e vou sumir
   
    if dead==1 // Se dead tiver chegado a 1...
    {
        x=room_width/2;      // vou para o meio da largura
        y=room_height-150;   // vou para o ponto 150 de baixo para cima
        can_move=1;          // já posso me mover
        can_fire=1;          // já posso atirar
        invinci=240;         // estou invencível por 240 steps
        image_alpha=1;       // estou opaco de novo
    }       
    dead-=1;  // diminuindo dead em 1 por step
}

if invinci>0 // Se invinci for maior que 0 (I'm invincible) ... invinci é outro contador regressivo
{
    if invinci mod 3 == 0   visible=0;  //se invinci for múltiplo de 3, pisco
    else    visible=1;                  //do contrário, fico visível
    if invinci<=1    can_die=1;         //se invinci baixar até 1, eu já posso voltar a morrer
    else can_die=0;                     //do contrário ainda tô invencível
    invinci-=1;                         //até lá, baixo invinci em 1 por step até zerar.
   
}
else    visible=1;


Ótimo, já temos um inimigo que nos mata! Não temos Game Over nem Continue ainda, mas isso será bem posterior. Só que nosso inimigo ainda não faz nada. Vamos acordar o preguiçoso. Crie um sprite novo com o arquivo TiroBola.gif
Image
Copie exatamente o que vê na imagem. Transparente, colisão não precisa, eixo centralizado, bounding box manual e com aqueles valores.

Vamos criar um objeto para o tiro inimigo com essa sprite: obj_bullet00, com depth -999. Nos precisamos que ela nunca seja tampada por outras coisas, portanto, o depth tem de ser o menor possível (só maior do que o do obj_control). No STEP, escreva:
  Code:
if x<-60 || x>room_width+60 || y<-60 || y>room_height+60
    instance_destroy();
Assim o tiro desaparece ao chegar a 60px fora da tela.

No obj_avatar, insira um COLLISION com obj_bullet00:
  Code:
if can_die==1 //Se eu posso morrer...
{
        dead=30;    //Estarei morto por 30 steps
        can_die=0;  //Não posso morrer enquanto estou morto
        can_move=0; //Não posso me mover até voltar a vida de novo
        can_fire=0; //Não posso atirar enquanto isso
        lives-=1;   //Perdi uma vida
}
Agora o jogador morre se levar tiro (o tiro só mata se pegar na caixa de colisão da nave)

Abra obj_enemy00 e crie um timer:
CREATE>> timer=0;
FIM DO STEP>> timer+=1;
Ainda no STEP, insira este código acima do step:
  Code:
if timer mod 30 == 0
{
    a=instance_create(x,y,obj_bullet00); //Criar tiro com x,y iguais aos meus
    a.speed=4; //dar velocidade 4 ao tiro
    //dar-lhe direção até o jogador
    a.direction=point_direction(a.x,a.y,obj_avatar.x,obj_avatar.y);
}

A cada 30 steps, ele cria um tiro no seu centro de origem, lhe dá velocidade 4 e uma direção. Mas que raios é aquilo ali?

point_direction(x1,y1,x2,y2) onde x1 e y1 são os pontos de origem e x2 e y2 são os pontos de chegada. A linha que vai de um ponto ao outro é a direção. No caso, os pontos de origem são x,y do tiro e x,y do obj_avatar.

Teste. Percebeu que os tiros estão gigantes? Vamos diminuir seu tamanho no obj_bullet00, CREATE:
  Code:
image_xscale=.5; //tamanho horizontal escalado pela metade
image_yscale=.5; //tamanho vertical escalado pela metade
image_speed=.5;  //velocidade de animação reduzida pela metade

Essas são variáveis pré-definidas, as primeiras para tamanho relativo e a última você já conhece (velocidade de animação). Num outro inimigo, você poderá alterar o tamanho do tiro (atribuindo tamanho assim como fizemos com velocidade e direção). Sendo a sprite maior do que precisamos agora, se em algum momento aumentarmos o tamanho do tiro, ainda teremos um visual bonito.

Mas a nave inimiga continua sem graça, certo? Vamos adicionar uma pequena coisinha no STEP dela...
  Code:
image_angle=direction+90; //a nave gira conforme a direção

(como ela já está virada para baixo - 270 - precisamos virar o ângulo da imagem na direção e somar 90)

...e partamos para o obj_control > STEP:
  Code:
if timer>=60 && timer<=360 && timer mod 60==0
{
    a=instance_create(room_width/2 + choose(-100,0,100), y-90, obj_enemy00);
    a.direction=270;
    a.speed=3;
}


choose() serve para que valores aleatórios sejam escolhidos dentro das opções dadas. room_width/2 + choose(-100,0,100) significa o meio da tela mais -100, 0 ou +100.

Tire o obj_enemy00 da room1. Rode o jogo.

Image
Bem mais interessante, não? Simples ainda, com inimigos saindo de pontos horizontais aleatórios, mas, didaticamente, tudo está funcionando muito bem.

Vamos acrescentar algo no STEP do obj_enemy00, antes da última coisa que pusemos lá:
  Code:
if mode==1 //mode==0 é o normal, mode==1 é esse aqui
{
    targetdir = point_direction(x,y,obj_avatar.x, obj_avatar.y);
   
    if abs(direction - targetdir) > 180
        cw = -1;
    else 
        cw = 1;
       
    if targetdir < direction
        cw *= -1;
       
    direction += cw;
}


Ok, compliquei bastante. Primeiro, veja que duas variáveis são criadas aqui mesmo no bloco: targetdir e cw. O que queremos fazer aqui é que, se o valor da variável mode for 1, o objeto vai seguir o jogador.
targetdir é a direção da linha que vai do objeto ao jogador.
abs() é uma função que retorna o valor numérico absoluto (módulo) do que estiver entre parênteses.
cw é o sentido da rotação.
if abs(direction - targetdir) > 180 - se direção menos targedir for maior que 180, cw é -1, senão cw é +1.
if targetdir < direction - se targetdir for menor que direção, cw muda de sinal.
No fim, a direção é alterada somando-se o valor de cw.
Por que isso? Porque se você simplesmente mandar a nave virar na direção do jogador, ela não faz curva, simplesmente muda imediatamente. Se você mandar ela somar a direção até chegar na correta, ela vai sempre girar no sentido anti-horário. Dessa forma aqui, ela sempre gira no sentido certo.

Esse código foi feito por alguém do fórum oficial do Game Maker. Não me lembro o nome dele, mas se você buscar no Google por parte do código, vai achá-lo.

Vamos mexer naquela parte do obj_control responsável pela criação dos inimigos:
  Code:
if timer>=60 && timer<=600 && timer mod 60==0
{
    a=instance_create(room_width/2 + choose(-100,0,100), y-90, obj_enemy00);
    a.direction=270;
    a.speed=3;
   
    if timer >360   a.mode=1;
}

Primeiro, mudamos timer<=360 para timer<=600. Dentro do bloco, inserimos a última declaração, que pede que, caso timer seja maior que 360, que as naves criadas tenham mode no valor 1. Assim, elas buscarão o jogador. Vamos ver como isso fica? Rode o jogo.

Bem legal, mas os malditos inimigos estão atirando do lado de fora da tela. Vamos corrigir isso no STEP do obj_enemy00: na parte onde mandamos a nave inimiga atirar, é só acrescentar no if que queremos que ela se situe dentro da sala.
  Code:
if timer mod 30 == 0 && y>0 && x>0 && y<room_height && x<room_width
{
    a=instance_create(x,y,obj_bullet00); //Criar tiro com x,y iguais aos meus
    a.speed=4; //dar velocidade 4 ao tiro
    //dar-lhe direção até o jogador
    a.direction=point_direction(a.x,a.y,obj_avatar.x,obj_avatar.y);
}


Rode. Dica: aperte F4 para tela cheia.

EXEMPLO DA AULA: GM_TUTO_SHMUP_ex0004.rar

NOTA: As naves que fazem curva estão voltando à tela depois de sair, não é? Mudaremos isso na próxima aula. Não se preocupem. Aliás, vocês têm idéia de como isso é resolvido? Tentem como trabalho de casa!
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Fri Jul 10, 2009 3:01 am

Re: Iniciação ao Game Maker com foco em SHMUPS

INIMIGOS pt. 2

Vamos resolver aquele problema do obj_enemy00, que faz curva e volta para a tela: onde está
if mode==1
troque para
  Code:
if mode==1 && y>0 && x>0 && y < room_height-30 && x < room_width


Agora ele só segue o jogador se estiver dentro da tela (com uma margem de "rodapé" de 30 pixels).
Vamos fazer um pouco de softcoding, ou seja, criar mais variáveis para facilitar nossa intervenção. No código deste objeto para soltar tiros, temos:
if timer mod 30 == 0 ...
e
a.speed=4;
Seria interessante ter como definir o intervalo e a velocidade de tiros sempre que quisessemos. Então vamos adicionar ao CREATE:
  Code:
firespeed=4; //Velocidade de tiro
firedelay=45; //intervalo entre tiros

Note os comentários. Vamos atribuir essas variáveis lá no STEP:
Troque 30 por firedelay
  Code:
if timer mod firedelay == 0 && y>0 && x>0 && y<room_height && x<room_width


Troque 4 por firespeed
  Code:
a.speed=firespeed;


Desta forma, poderemos atribuir valores diferentes ao obj_enemy00 de dentro do obj_control. Pense nisso como atributos de personagens em RPGs.

Vamos aproveitar e fazer algo parecido com o obj_avatar. Insira no CREATE esta variável:
flightspeed=5; //Velocidade de vôo
No STEP, para cada modificação nas coordenadas nos movimentos, troque os valores numéricos por flightspeed EXCETO no vk_down - coloque flightspeed/2
A vantagem nisso é que, em vez de mudar quatro valores no STEP, mudamos apenas um no CREATE. Além disso, se quisermos criar um item de speed-up, ele simplesmente mudaria essa variável.

Vamos criar uma nova sprite. Carregue TiroPalito.gif, chame-o de spr_bullet01, centralize a origem, tire o Precise e entre com a seguinte bounding box manual:
Left: 11; Right: 40; Top: 2; Bottom: 6.
Dê OK.
Crie uma sprite spr_enemy01 e carregue o arquivo BlueViper.gif. Centralize a origem, deixe o bounding box Full.
Image

Clone o obj_enemy00, chame-o de obj_enemy01 e troque os códigos necessários. Este é o CREATE do obj_enemy01:
  Code:
hp=4; // Vida do inimigo, o quanto ele aguenta de tiros
collide=1; // Se pode colidir com os tiros e o jogador
ground=0; // Se está no chão ou voando
timer=0; //Cronômetro
firespeed=3; //Velocidade de tiro
firedelay=45; //Intervalo entre tiros

image_speed=0; //Velocidade de animação = 0
image_index=4; //Subimagem 4
vspeed=2;      //Velocidade vertical: 2 pixels para baixo
insidescreen=0; //Avisa se já entrou na tela
A diferença são as quatro últimas declarações. A novidade é o vspeed, que controla a velocidade vertical sem interferir na horizontal. insidescreen é uma variável que criamos. Indicará se o objeto já entrou ou não na tela (consideraremos apenas coordenada horizontal). Também tiramos a variável mode, pois não a usaremos.

Vamos mexer no ataque
  Code:
if timer mod firedelay == 0 && y>0 && x>0 && y<room_height && x<room_width
{
    for(i=-1;i<=1;i+=1)
    {
        a=instance_create(x+i*10,y,obj_bullet00); //Criar tiros
        a.speed=firespeed; //dar velocidade 4 ao tiro
        a.direction=270+i*15; //Direção: 270+ (-15, 0 e +15)
        a.sprite_index=spr_bullet01;
        a.image_angle=a.direction;
        a.image_xscale=.66;
        a.image_yscale=.66;       
    }
}

Agora o ataque usa um loop for. Sendo i=-1, enquanto i<=1, atira e aumenta i em 1. No x do instance_create, definimos o x da nave somada ao valor de i multiplicado por 10, ou seja, x-10, x e x+10. Na direção, pedimos que o tiro vá para baixo, mais somado ao valor de i vezes 15. Isso criará 3 tiros com 15 graus de diferença.
sprite_index - define a sprite do objeto. Vamos usar o spr_bullet01, o tiro palito.
a.image_angle=a.direction; - Vamos definir o ângulo da sprite como igual à direção.
Escalamos o tamanho para 2 terços.

Todo bloco que começa com if mode==1 será retirado. Vamos fazer o seguinte:

  Code:
if abs(x-obj_avatar.x)>30
{
    if x>obj_avatar.x && hspeed>-4     //Se estou à direita e meu hpseed é maior que -5...
    {
        hspeed-=.25;                              //Diminuo hspeed
        if image_index<8    image_index+=.5;     //mudo a subimagem até 8
    }
    if x<obj_avatar.x && hspeed<4      //Se estou à esquerda e meu hpseed é menor que 5...
    {
        hspeed+=.25;                              //Aumento hspeed
        if image_index>0    image_index-=.5;     //mudo a subimagem até 0
    }       
}

if x>=0 && x<=room_width    insidescreen=1; //Se entrar na largura da tela, insidescreen vira 1

if insidescreen //Se insidescreen for true...
{
    if x<0              x=0;            //Não pode sair da tela pelos lados
    if x>room_width     x=room_width;
}

O comportamento deste inimigo é o seguinte: ele sempre procura estar na frente do jogador, sempre andando para os lados enquanto desce a tela. Ele vai acelerar sempre que a diferença de x for maior que 30. A importância do insidescreen é que nós criaremos este inimigo fora da largura da tela. Quando ele chegar na tela, insidescreen será true e ele não poderá mais sair pelos lados. Isso interessante por dois motivos: se o criarmos na largura da tela, ele pode estar alinhado ao jogador e não se mover para os lados até o jogador se afastar; se não limitarmos sua movimentação, ele vai voltar à tela pelo lado pegando o jogador de surpresa.
Retire a linha image_angle=direction+90;

No obj_control, evento STEP, antes do timer:
  Code:
if timer>=680 && timer<=760 && timer mod 40==0
{
    a=instance_create(-90+choose(0,20,40),y-90,obj_enemy01);
}


Rode

RESUMO DA ÓPERA:
obj_enemy01 - CREATE

  Code:
hp=4; // Vida do inimigo, o quanto ele aguenta de tiros
collide=1; // Se pode colidir com os tiros e o jogador
ground=0; // Se está no chão ou voando
mode=0; // Preset de comportamento
timer=0; //Cronômetro
firespeed=3; //Velocidade de tiro
firedelay=45; //Intervalo entre tiros

image_speed=0; //Velocidade de animação = 0
image_index=4; //Subimagem 4
vspeed=2;      //Velocidade vertical: 2 pixels para baixo
insidescreen=0; //Avisa se já entrou na tela

STEP
  Code:
if y<-100 || x<-100 || y>room_height+100 || x>room_width+100
    instance_destroy();

//-----------------------ATAQUE   
if timer mod firedelay == 0 && y>0 && x>0 && y<room_height && x<room_width
{
    for(i=-1;i<=1;i+=1)
    {
        a=instance_create(x+i*10,y,obj_bullet00); //Criar tiros
        a.speed=firespeed; //dar velocidade 4 ao tiro
        a.direction=270+i*15; //Direção: 270+ (-15, 0 e +15)
        a.sprite_index=spr_bullet01;
        a.image_angle=a.direction;
        a.image_xscale=.66;
        a.image_yscale=.66;       
    }
}
//-------------------       
if abs(x-obj_avatar.x)>30
{
    if x>obj_avatar.x && hspeed>-4     //Se estou à direita e meu hpseed é maior que -5...
    {
        hspeed-=.25;                              //Diminuo hspeed
        if image_index<8    image_index+=.5;     //mudo a subimagem até 8
    }
    if x<obj_avatar.x && hspeed<4      //Se estou à esquerda e meu hpseed é menor que 5...
    {
        hspeed+=.25;                              //Aumento hspeed
        if image_index>0    image_index-=.5;     //mudo a subimagem até 0
    }       
}

if x>=0 && x<=room_width    insidescreen=1; //Se entrar na largura da tela, insidescreen vira 1

if insidescreen //Se insidescreen for true...
{
    if x<0              x=0;            //Não pode sair da tela pelos lados
    if x>room_width     x=room_width;
}

//-------------------
   
timer+=1; //cronômetro


DEBUGING
Vamos fazer uns recursos de debug. Só duas coisas simples desta vez. Tudo isso será retirado do jogo final.
No obj_control, insira um evento Key Press > Delete
  Code:
if obj_avatar.invinci<240  obj_avatar.invinci=99999;
else   
{
    obj_avatar.invinci=0;
    obj_avatar.can_die=1;
}
Pressionando Delete, nós ficaremos com mais de 27 minutos de invencibilidade. Pressionando de novo, voltaremos a ficar vulneráveis.

No DRAW do obj_control, insira no final:
  Code:
//----------------DEBUG
draw_text(0,429,'Invinci: ' + string(obj_avatar.invinci) + '#' + string(timer) + '#'+ string(fps));

Preste atenção a como são colocados textos e variáveis juntos no mesmo draw_text: textos são entre aspas e variáveis numéricas precisam da função string(). O # faz com que uma linha seja pulada e é texto, logo precisa de aspas. Para juntar variáveis ou textos, use +.

Image
Temos aqui, no canto inferior esquerdo, uma legenda para a duração da invencibilidade do jogador em steps, o cronômetro do obj_control também em steps e a quantidade efetiva de frames por segundo.

DICA: F5 salva o jogo e F6 carrega. Use isto para testar e no jogo final, desabilite em Resources > Change gobal settings > Other.

EXEMPLO DESTA AULA: GM_TUTO_SHMUP_ex0005.rar
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Kollision

Posts: 21

Joined: Thu Jul 02, 2009 2:02 am

Post Fri Jul 10, 2009 3:09 am

Re: Iniciação ao Game Maker com foco em SHMUPS

Parabéns pelo tópico, Spiegel. Espero que alguém use e saiba valorizar o seu esforço e entusiasmo em divulgar esse tutorial.

Eu adoraria aprender algo assim, mas infelizmente é impossível no momento.
Quem sabe algum dia...
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Fri Jul 10, 2009 10:10 pm

Re: Iniciação ao Game Maker com foco em SHMUPS

Valeu, Kollision. (Se tu ajudar a divulgar, agradeço ainda mais :D )

Façamo-nos um favor modificando o obj_enemy00:
No STEP, onde diz
  Code:
if timer mod firedelay == 0 && y>0 && x>0 && y<room_height && x<room_width

troque para
  Code:
if timer mod firedelay == 0 && y>0 && x>0 && y<room_height-60 && x<room_width
Evitamos, assim, ataques pela retaguarda vindos da parte baixa da tela.

INIMIGOS Pt. 3
Crie uma sprite spr_enemy02 com o arquivo GreenViper.gif. Faça assim como fez com os anteriores.
Crie um objeto obj_enemy02 com a sprite acima. As opções são iguais aos inimigos anteriores. Aqui estão os códigos dele:
CREATE
  Code:
//Inimigo 02
//Sem contador
hp=3; // Vida do inimigo, o quanto ele aguenta de tiros
collide=1; // Se pode colidir com os tiros e o jogador
ground=0; // Se está no chão ou voando
firespeed=3; //Velocidade do tiro
fired=0; // 0 se ainda não disparou, 1 se já disparou

//----------------Variáveis pré-definidas
image_speed=0; //Animação velocidade 0
vspeed=7;      //Velocidade vertical 7 (7px para baixo)
gravity=.1;    //Gravidade valor .1
gravity_direction=90; //Direção da gravidade 90°

Não usamos timer nesse. Criamos aqui a variável fired, que indica se ele já atirou (0=ainda não).
gravity - variável pré-definida que indica o quanto o objeto sofre influência de uma gravidade. Ou seja, se ele vai na direção oposta à gravidade, ele é puxado para a outra. O valor indica o quanto ele acelera na direção da gravidade.
gravity direction - variável pré-definida que indica a direção da força da gravidade. Aqui é 90 graus porque queremos que ele seja puxado para cima.

STEP
  Code:
if y<-100 || x<-100 || y>room_height+100 || x>room_width+100
    instance_destroy();

//-----------------------ATAQUE   
if vspeed<1 if image_index<14 image_index+=.5;

if vspeed<0 && fired==0
{
    for(i=-2;i<=2;i+=1)
    {
        a=instance_create(x,y,obj_bullet00); //Criar tiro com x,y iguais aos meus
        a.speed=firespeed; //dar velocidade 4 ao tiro
        a.direction=270+i*7;
    }
    fired=1;
}

if vspeed<-4    vspeed=-4   

Como vimos, o timer foi embora. Vamos ver o que ocorre abaixo do comentário ATAQUE:
Como estamos usando gravidade para cima e a nave está voando para baixo, o vspeed dela será subtraído. Em algum momento ele estará abaixo de 1 e, depois, de 0. Então, quando vspeed for menor que 1 e se o quadro de animação estiver abaixo de 14, este sobe .5 por step.
Se vspeed já estiver abaixo de 0 e fired ainda for 0, soltamos 5 tiros simultâneos. Nós temos um for que vai de -2 até 2, criando tiros que vão para direções relativas à 270, mas somadas ao valor de i*7 (-14, -7, 0, 7, 14).
Por fim, dentro do bloco, atribuímos 1 à fired, assim a nave não atirará mais embora sua vspeed esteja abaixo de 0.

Se a vspeed dela estiver menor que -4, ela volta a ser -4. Assim ela não dispara feito doida até o alto da tela (imagina se você estivesse no caminho dela e não pudesse desviar?).

Vamos colocar essa nave no fim de nossa fase provisória. Vamos fazer algo legal. STEP do obj_control, após os códigos dos outros inimigos:
  Code:
if timer==860
{
    for(i=0;i<=3;i+=1)
    {
        a=instance_create(50+i*87,-40,obj_enemy02);
        a.vspeed=5+i;
    }
}
Criaremos 4 naves, de 0 a 3. a posição x e a velocidade vertical serão influenciadas por i.

Rode.

Vamos dar um efeito aos inimigos no momento em que são alvejados. A todos eles, crie uma variável red=0 no CREATE. Na colisão com o tiro do jogador, insira no fim do bloco if collide==1:
  Code:
    red=5;
    image_blend=c_red;
Isso em todos eles.

Agora crie um Script com nome scr_red_enemy
  Code:
if red>0    red-=1;
else    image_blend=c_white;
Se lembra dos Scripts? São códigos feitos fora dos objetos.

Agora vamos aos objetos inimigos escrever a seguinte declaração no STEP, antes do timer se houver:
  Code:
scr_red_enemy();

Scripts funcionam como funções quando os executamos de dentro de um objeto. Reparou que o código no evento de colisão com os tiros do jogador em todos os inimigos são iguais? Então vamos fazer um script com aquele código!
SCRIPT scr_enemy_dies00:
  Code:
if collide==1
{
    with (other) {instance_destroy();}
    hp-=1;
    score+=1;
    if hp<=0   
    {
        score+=4;   
        instance_destroy();
    }
    red=5;
    image_blend=c_red;
}


Agora, vá nos inimigos e substitua todo o código da colisão por isto:
  Code:
scr_enemy_dies00();


Assim, os códigos idênticos são reduzidos a apenas um script. Quando colocarmos explosões, poderemos fazer isso no script e todos os inimigos seguirão a mesma regra. Colocamos 00 no final do script pois outros inimigos podem explodir de jeitos diferentes, através de outros scripts que criarmos.

Crie um sprite spr_explo00 com o gif PachiBoom.gif. Centralize e tire o Precision - colisão aqui não interessa.
Crie um objeto obj_explo00. Dê-lhe a sprite acima e Depth de 10.
No CREATE, coloque:
  Code:
image_speed=.5;

Queremos que ele suma após completar a animação. Há duas formas de fazer isso:

1) Add Event > Other > Animation End; Aba Main1 > Destroy Instance (Self, OK).

2) Add Event > STEP
  Code:
if image_index>15   instance_destroy();


Volte ao script scr_enemy_dies00 e insira dentro do bloco if hp<=0:
  Code:
        a=instance_create(x,y,obj_explo00); //Criar explosão
        a.depth=depth; //explosão tem o depth do objeto que a originou


Eu admito que é estranho um inimigo que morre com 2 ou 3 tiros piscar vermelho, mas eu só quis mostrar como isso pode ser feito. Caso queira ver como seria num inimigo mais forte, suba o hp deles só para sentir como é. Nosso próximo inimigo terá mais hp, então isso fará mais sentido.

EXEMPLO DA AULA: GM_TUTO_SHMUP_ex0006.rar
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
<<

Spiegel

User avatar

Posts: 21

Joined: Tue Jul 07, 2009 4:13 pm

Post Sat Jul 11, 2009 8:37 am

Re: Iniciação ao Game Maker com foco em SHMUPS

INIMIGOS Pt. 4 - CANHÃO AO FUNDO

Agora, nós criaremos um canhão que fica no cenário. Ele não pode colidir com a nave do jogador. Seu ataque será mais elaborado do que o que fizemos agora. De quebra, alteraremos um script já existente.

Crie a seguinte sprite com o arquivo Canhao.gif:
Image

Com esta sprite, o seguinte objeto
Image

Código no CREATE:
  Code:
hp=6; // 6 pontos de vida
collide=0; // Vamos começar com a colisão impossibilitada
ground=1; // Eles está no chão (plano de fundo) - o jogador não colide com ele
timer=0; //Cronômetro
firespeed=4; //Velocidade de tiro
firedelay=60; //Intervalo entre os ataques
red=0;
image_angle=270; //Ângulo inicial da sprite
image_blend=c_gray; //Pintando o canhão de cinza

Viramos o canhão para baixo com image_angle. Vamos desligar a colisão e pintá-lo de cinza com image_blend (assim que ele puder colidir, vamos retorná-lo à cor normal).

No código do STEP:
  Code:
if y<-200 || x<-200 || y>room_height+200 || x>room_width+200
    instance_destroy();
Destruir o objeto após ele se afastar 200 px em x ou y dos limites da sala
  Code:
//------------------- O QUE FAZER AO CHEGAR À y=0 ou passá-lo?       
if y>=0              //Se y é maior ou igual a 0
{
    scr_red_enemy();
    collide=1;
}
Depois que ele descer até 0, vamos executar o script scr_red_enemy, aquele que faz o inimigo piscar vermelho ao ser alvejado. Automaticamente ele ficará com image_blend=c_white, deixando de estar tingido de cinza. Vamos também possibilitar a colisão com tiros do jogador.

  Code:
//--------------------- VIRAR CANHÃO PARA O JOGADOR
image_angle=point_direction(x,y,obj_avatar.x,obj_avatar.y);
Vamos fazê-lo apontar para o jogador.

  Code:
//-----------------------ATAQUE 
xx=lengthdir_x(104,image_angle); // Distância de x para uma linha de 104px no ângulo da imagem
yy=lengthdir_y(104,image_angle); // Distância de y para uma linha de 104px no ângulo da imagem
if point_distance(x+xx,y+yy,obj_avatar.x,obj_avatar.y)>75
{
    for(i=0;i<=20;i+=1)
    {
        if (timer-i) mod firedelay == 0 && y>0 && x>0 && y<room_height-60 && x<room_width
        {
            a=instance_create(x+xx,y+yy,obj_bullet00); //Criar tiro com x,y iguais aos meus
            a.speed=firespeed+i*.1; //dar velocidade ao tiro
            //dar-lhe direção até o jogador
            a.direction=image_angle;
            a.sprite_index=spr_bullet01;
            a.image_angle=a.direction;
        }
    }
}
Este é o trecho do ataque. Não presumo que você queira moleza, estou certo? Passo a passo:

Vamos criar variáveis xx e yy e nelas guardar um certo valor para cada através das funções lengthdir_x elengthdir_y. Estas duas funções indicam o comprimento nos eixos x e y para uma linha (um vetor) de tamanho tal. Ou seja, digamos que eu queira saber qual a distância para x em 104 pixels num ângulo de 45 graus. eu uso lengthdir_x(104, 45); para descobrir. Imagine que 104 é o valor de uma hipotenusa de 45 graus e nós queremos descobrir o cateto da base. Com lengthdir_y(104,45);, nós saberemos a medida do cateto vertical. Com
xx=lengthdir_x(104,image_angle);
yy=lengthdir_y(104,image_angle);

nós saberemos as medidas dos catetos para a hipotenusa de 104 pixels no ângulo em que o objeto estiver. Essas são as coordenadas para a ponta do cano do canhão em relação a sua origem.

if point_distance(x+lengthdir_x(104,image_angle),y+lengthdir_y(104,image_angle),obj_avatar.x,obj_avatar.y)>75
Só atacaremos se a distância entre a ponta do canhão e o centro do jogador for maior do que 75. Somamos os valores de xx,yy a x,y na origem e colocamos x,y do obj_avatar no destino. Comparamos com 75. point_distance(x1,y1,x2,y2) é uma das funções que retornam a distância entre dois pontos.

for(i=0;i<=20;i+=1) - Queremos vinte tiros - mas não simultâneamente. Veja abaixo:

  Code:
if (timer-i) mod firedelay == 0 && y>0 && x>0 && y<room_height-60 && x<room_width

Se o resto de timer menos o valor de i dividido por firedelay for zero e se o objeto estiver dentro dos limites da tela...
Isso quer dizer que o timer será testado vinte vezes por step e se o valor de timer-i for múltiplo do firedelay, ele atira. O resultado é que do momento inicial até 20 steps depois, ele vai atirar um tiro por step.

a=instance_create(x+xx,y+yy,obj_bullet00); - Criamos um tiro com x,y somados a xx,yy para que ele saia da ponta.
a.speed=firespeed+i*.1; - Queremos que a velocidade do tiro aumente um após o outro para dar um efeito legal. Que ela aumente em relação ao valor de i (velocidade padrão + um décimo de i).
a.direction=image_angle; - Dar ao tiro a mesma direção em que o canhão aponta.
a.sprite_index=spr_bullet01; - Queremos aquela sprite verdinha pro tiro.
a.image_angle=a.direction; - E o ângulo do tiro é o de sua direção.

Nós sabemos que no script da colisão com o tiro, há uma declaração para criar uma explosão. Só que a explosão é pequena. Nós queremos uma do tamanho adequado ao canhão. Então, entre no script scr_enemy_dies00 e escreva logo abaixo da criação da explosão:
  Code:
        a.image_xscale=argument0;
        a.image_yscale=argument0; 
O que é esse argument0? Num script, sempre que você quiser possibilitar o uso de algum argumento, use isto como valor. Você pode usar de argument0 até argument15. Então, quando for executar a script, coloque o valor do argumento entre parênteses, assim esse valor será usado. Como nós dissemos que o tamanho da explosão será argument0, vamos aos inimigos anteriores e alterar o comando na colisão com o tiro do jogador para que fique assim:
  Code:
scr_enemy_dies00(1);
Com o 1 dentro dos parênteses, assim o tamanho da explosão continua normal. Mas no obj_enemy03, vamos colocar assim:
  Code:
scr_enemy_dies00(3);
Temos agora uma explosão 3x maior só para ele.

Volte ao scr_enemy_dies. Na linha
red=5;
mude para
red=2;
Assim o inimigo pisca mais rápido ao ser acertado.

Como esse inimigo novo é um canhão de fundo de tela, ele não pode sair voando por aí. Mas nós temos os asteróides e é neles que o canhão vai ficar. Só precisamos saber quando os asteróides aparecem e onde. Vamos olhar no obj_control:

Asteróide central surge a cada 240 steps (240, 480, 720...), no meio da largura e em y=-90.
Asteróides laterais surgem a cada 240 steps mais 120 (360, 600, 840...), um em x=70,y=-90 e outro em x=290,y=-180.

Logo vamos colocar os os canhões assim:

  Code:
//Criar canhões nos momentos da criação de asteróides e sobre os arteróides
if timer==120   || timer==360 || timer==1080    //Canhão nesses 3 momentos
{
    a=instance_create(70,-90,obj_enemy03);  //No asteróide da esquerda
    a.vspeed=2;
}

if timer==360 || timer==600  || timer==1080    //Canhão nesses 3 momentos
{
    a=instance_create(290,-180,obj_enemy03); //No asteróide da direita
    a.vspeed=2;
}

if timer==960 //Canhão no momento 360
{
    a=instance_create(room_width/2,-90,obj_enemy03); // No asteróide do meio
    a.vspeed=2;
}
//


Image

RESUMO DA ÓPERA
obj_enemy03
CREATE:
  Code:
hp=6; // 6 pontos de vida
collide=0; // Vamos começar com a colisão impossibilitada
ground=1; // Eles está no chão (plano de fundo) - o jogador não colide com ele
timer=0; //Cronômetro
firespeed=4; //Velocidade de tiro
firedelay=60; //Intervalo entre ataques
red=0;
image_angle=270; //Ângulo inicial da sprite
image_blend=c_gray; //Pintando o canhão de cinza

STEP:
  Code:
if y<-200 || x<-200 || y>room_height+200 || x>room_width+200
    instance_destroy();
   
//------------------- O QUE FAZER AO CHEGAR À y=0 ou passá-lo?       
if y>=0              //Se y é maior ou igual a 0
{
    scr_red_enemy();
    collide=1;
}

//--------------------- VIRAR CANHÃO PARA O JOGADOR
image_angle=point_direction(x,y,obj_avatar.x,obj_avatar.y);

//-----------------------ATAQUE 
xx=lengthdir_x(104,image_angle); // Distância de x para uma linha de 104px no ângulo da imagem
yy=lengthdir_y(104,image_angle); // Distância de y para uma linha de 104px no ângulo da imagem
if point_distance(x+lengthdir_x(104,image_angle),y+lengthdir_y(104,image_angle),obj_avatar.x,obj_avatar.y)>75
{
    for(i=0;i<=20;i+=1)
    {
        if (timer-i) mod firedelay == 0 && y>0 && x>0 && y<room_height-60 && x<room_width
        {
            a=instance_create(x+xx,y+yy,obj_bullet00); //Criar tiro com x,y iguais aos meus
            a.speed=firespeed+i*.1; //dar velocidade ao tiro
            //dar-lhe direção até o jogador
            a.direction=image_angle;
            a.sprite_index=spr_bullet01;
            a.image_angle=a.direction;
        }
    }
}

//-------------------
   

timer+=1; //cronômetro

COLLISION com obj_avatarshot
  Code:
scr_enemy_dies00(3);


COLLISION dos outros inimigos com obj_avatarshot
  Code:
scr_enemy_dies00(1);


scr_enemy_dies00
  Code:
if collide==1
{
    with (other) {instance_destroy();}
    hp-=1;
    score+=1;
    if hp<=0   
    {
        score+=4;   
        instance_destroy();
        a=instance_create(x,y,obj_explo00); //Criar explosão
        a.depth=depth; //explosão tem o depth do objeto que a originou
        // tamano da explosão é dado através de um argumento na execução
        // do script
        a.image_xscale=argument0;
        a.image_yscale=argument0;   
       
    }
    red=2;
    image_blend=c_red;
}


obj_control - STEP
  Code:
//----------ASTERÓIDES NO CENÁRIO
if timer mod 240 == 0
{
    a=instance_create(room_width/2, -90, obj_asteroid)
    a.direction=270;   
    a.speed=2;
}

if (timer-120) mod 240 == 0
{
    for(i=0;i<=1;i+=1)
    {
        a=instance_create(70+i*220, -90-i*90, obj_asteroid)
        a.direction=270;   
        a.speed=2;
    }
}
//-----------------------------------------
//Criar canhões nos momentos da criação de asteróides e sobre os arteróides
if timer==120   || timer==360 || timer==1080    //Canhão nesses 3 momentos
{
    a=instance_create(70,-90,obj_enemy03);  //No asteróide da esquerda
    a.vspeed=2;
}

if timer==360 || timer==600  || timer==1080    //Canhão nesses 3 momentos
{
    a=instance_create(290,-180,obj_enemy03); //No asteróide da direita
    a.vspeed=2;
}

if timer==960 //Canhão no momento 360
{
    a=instance_create(room_width/2,-90,obj_enemy03); // No asteróide do meio
    a.vspeed=2;
}
//

if timer>=60 && timer<=600 && timer mod 60==0
{
    a=instance_create(room_width/2 + choose(-100,0,100), y-90, obj_enemy00);
    a.direction=270;
    a.speed=3;
   
    if timer >360   a.mode=1;
}

if timer>=680 && timer<=760 && timer mod 40==0
{
    a=instance_create(-90+choose(0,20,40),y-90,obj_enemy01);
}

if timer==860
{
    for(i=0;i<=3;i+=1)
    {
        a=instance_create(50+i*87,-40,obj_enemy02);
        a.vspeed=5+i;
    }
}

//-----------TIMER
timer+=1;


Se eu esqueci alguma coisa nesta aula, me avisem.

EXEMPLO DESTA AULA: GM_TUTO_SHMUP_ex0007.rar
Onde encontrar mais sobre Cris Spiegel:
MySpace Music: Spiegelworks
Blog: Cris Spiegel's Visual Atrocity
Next

Return to Off-Topic

Who is online

Users browsing this forum: No registered users and 0 guests

cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by Vjacheslav Trushkin for Free Forums/DivisionCore.
Hosted by FreeForums.org | Create a free forum