Uno de los seguidores del blog ha preguntado como crear un enemigo que dispare. Aprovechando esta duda, he decidido abrir esta nueva sección que cubra algunos de los enemigos más populares en los juegos de naves. Para mantenerlo simple, no los agregaré directo sobre el código anterior, si no que los pondré en un código individual, y tendrás tú que encargarte de integrarle con el resto de tu juego si deseas usar alguno de estos.
Hasta el momento hemos trabajado con el tipo de enemigo más básico, el kamikaze, cuya única función es estrellarse contra el jugador. Ahora, para empezar esta serie de enemigos populares, hablaremos del segundo tipo de enemigo popular: El disparador. Esa molesta nave que no es solo peligrosa por su presencia, si no que además lanza disparos en contra de nuestro personaje.
En realidad, veremos aquí no solo un tipo de enemigo, si no dos. ¿Por qué dos? Si lo analizas un momento, comprenderás que los disparos enemigos, son en realidad, un tercer tipo de enemigo. Comencemos agregando estos dibujos a nuestra hoja de sprites:
Si sigues usando la hoja de estilos de la lección pasada, los disparos enemigos han de comenzar en el pixel 75, y la nueva nave en el pixel 80.
Comencemos por decidir la forma en que actuará nuestro enemigo. Para hacerlo distinto a nuestro kamikaze, haremos que este aparezca únicamente en la parte superior de nuestra pantalla en intervalos al azar, se mueva de forma horizontal, y lance algunos disparos antes de desaparecer por el otro lado de la pantalla.
Para empezar, declararemos un nuevo contador, que nos indicará el tiempo antes de crear un nuevo enemigo:
Pasemos a programar la disparadora (enemigo al que asignamos el tipo 1). Tu ya sabes como hacer que se mueva, solo asigna esta vez los valores para que se desplace hacia la derecha:
¿Qué otros enemigos comunes les gustaría conocer en esta sección? Espero sus comentarios.
Hasta el momento hemos trabajado con el tipo de enemigo más básico, el kamikaze, cuya única función es estrellarse contra el jugador. Ahora, para empezar esta serie de enemigos populares, hablaremos del segundo tipo de enemigo popular: El disparador. Esa molesta nave que no es solo peligrosa por su presencia, si no que además lanza disparos en contra de nuestro personaje.
En realidad, veremos aquí no solo un tipo de enemigo, si no dos. ¿Por qué dos? Si lo analizas un momento, comprenderás que los disparos enemigos, son en realidad, un tercer tipo de enemigo. Comencemos agregando estos dibujos a nuestra hoja de sprites:
Si sigues usando la hoja de estilos de la lección pasada, los disparos enemigos han de comenzar en el pixel 75, y la nueva nave en el pixel 80.
Comencemos por decidir la forma en que actuará nuestro enemigo. Para hacerlo distinto a nuestro kamikaze, haremos que este aparezca únicamente en la parte superior de nuestra pantalla en intervalos al azar, se mueva de forma horizontal, y lance algunos disparos antes de desaparecer por el otro lado de la pantalla.
Para empezar, declararemos un nuevo contador, que nos indicará el tiempo antes de crear un nuevo enemigo:
var eTimer=0;
Disminuiremos el contador en uno en el ciclo de tiempo, y cuando llegue a cero, crearemos una nueva disparadora en la parte superior izquierda, y pondremos un nuevo valor al azar a nuestro contador: // Generate Enemy
eTimer--;
if(eTimer<0){
enemies.push(new Rectangle(0,40,10,10,1));
eTimer=20+random(40);
}
El nuevo valor indica que se generará una nueva nave dentro de 20 a 60 ciclos después, esto equivale de 1 a 3 segundos. En este momento, para muestra, está bien que se generen las naves en tan corto lapso de tiempo, pero cuando las incluyas en tu juego, es posible que quieras que esto ocurra en un lapso de tiempo más disperso.Pasemos a programar la disparadora (enemigo al que asignamos el tipo 1). Tu ya sabes como hacer que se mueva, solo asigna esta vez los valores para que se desplace hacia la derecha:
if(enemies[i].type==1){
enemies[i].x+=5;
// Shooter Outside Screen
if(enemies[i].x>canvas.width){
enemies.splice(i--,1);
l--;
continue;
}
Como la disparadora se encuentra en la parte superior siempre, y el jugador en la parte inferior, no tiene caso que comparemos si ambas naves entran en colisión. Pasemos a crear los disparos de la nave, el concepto es el mismo que la creación de la disparadora, y el intervalo de tiempo ha sido pensado para que dispare de 2 a 4 veces más o menos por aparición, sin que haga dos disparos muy juntos: // Shooter Shots
enemies[i].timer--;
if(enemies[i].timer<0){
enemies.push(new Rectangle(enemies[i].x+3,enemies[i].y+5,5,5,2));
enemies[i].timer=10+random(30);
}
Por último, agrega la intersección entre las disparadoras y nuestros disparos. Querremos eliminar a la nave enemiga si da la casualidad que uno de nuestros disparos logra pegar en contra de esta: // Shot Intersects Shooter
for(var j=0,ll=shots.length;j<ll;j++){
if(shots[j].intersects(enemies[i])){
score++;
shots.splice(j--,1);
ll--;
enemies.splice(i--,1);
l--;
}
}
Continuemos por programar los disparos enemigos (enemigo al que asignamos el tipo 2). Su función es bastante simple: se mueve hacia abajo, y si pega con el jugador, lo daña; nada más que eso. Es lógico que los disparos enemigos se mueva más rápido que las naves enemigas: // EnemyShot
else if(enemies[i].type==2){
enemies[i].y+=10;
// EnemyShot Outside Screen
if(enemies[i].y>canvas.height){
enemies.splice(i--,1);
l--;
continue;
}
// Player Intersects EnemyShot
if(player.intersects(enemies[i])&&player.timer<1){
player.health--;
player.timer=20;
}
}
Por último, vamos a dibujarle. Incluyo el dibujado de la nave kamikaze para dejar claro como se dibujarían todos los elementos juntos: for(var i=0,l=enemies.length;i<l;i++){
ctx.strokeStyle='#00f';
if(enemies[i].type==0){
if(enemies[i].timer%2==0)
enemies[i].drawImageArea(ctx,spritesheet,30,0,10,10);
else{
ctx.strokeStyle='#fff';
enemies[i].drawImageArea(ctx,spritesheet,40,0,10,10);
}
}
else if(enemies[i].type==1)
enemies[i].drawImageArea(ctx,spritesheet,80+(aTimer%2)*10,0,10,10);
else if(enemies[i].type==2)
enemies[i].drawImageArea(ctx,spritesheet,75,(aTimer%2)*5,5,5);
}
Probamos el código, y veremos la nave disparadora apareciendo continuamente y lanzando sus disparos en contra nuestra. Si lo has agregado directo en tu juego, podrás sentir la interacción de todos sus elementos. Con esto, tenemos ya nuestra enemiga nave disparadora.¿Qué otros enemigos comunes les gustaría conocer en esta sección? Espero sus comentarios.
Código final:
(function(){
'use strict';
window.addEventListener('load',init,false);
var canvas=null,ctx=null;
var pause;
var aTimer=0;
var eTimer=0;
var player=new Rectangle(90,280,10,10,0,3);
var shots=[];
var enemies=[];
var spritesheet=new Image();
spritesheet.src='assets/spritesheet.png';
function random(max){
return ~~(Math.random()*max);
}
function init(){
canvas=document.getElementById('canvas');
ctx=canvas.getContext('2d');
canvas.width=200;
canvas.height=300;
run();
repaint();
}
function run(){
setTimeout(run,50);
act(0.05);
}
function repaint(){
requestAnimationFrame(repaint);
paint(ctx);
}
function act(deltaTime){
if(!pause){
// Generate Enemy
eTimer--;
if(eTimer<0){
enemies.push(new Rectangle(0,40,10,10,1));
eTimer=20+random(40);
}
// Move Enemies
for(var i=0,l=enemies.length;i<l;i++){
if(enemies[i].timer>0)
enemies[i].timer--;
// Shooter
if(enemies[i].type==1){
enemies[i].x+=5;
// Shooter Outside Screen
if(enemies[i].x>canvas.width){
enemies.splice(i--,1);
l--;
continue;
}
// Shooter Shots
enemies[i].timer--;
if(enemies[i].timer<0){
enemies.push(new Rectangle(enemies[i].x+3,enemies[i].y+5,5,5,2));
enemies[i].timer=10+random(30);
}
// Player Intersects Shooter
/*if(player.intersects(enemies[i])&&player.timer<1){
player.health--;
player.timer=20;
}*/
// Shot Intersects Shooter
for(var j=0,ll=shots.length;j<ll;j++){
if(shots[j].intersects(enemies[i])){
score++;
shots.splice(j--,1);
ll--;
enemies.splice(i--,1);
l--;
}
}
}
// EnemyShot
else if(enemies[i].type==2){
enemies[i].y+=10;
// EnemyShot Outside Screen
if(enemies[i].y>canvas.height){
enemies.splice(i--,1);
l--;
continue;
}
// Player Intersects EnemyShot
if(player.intersects(enemies[i])&&player.timer<1){
player.health--;
player.timer=20;
}
}
}
// Timer
aTimer+=deltaTime;
if(aTimer>3600)
aTimer-=3600;
}
}
function paint(ctx){
ctx.fillStyle='#000';
ctx.fillRect(0,0,canvas.width,canvas.height);
for(var i=0,l=enemies.length;i<l;i++){
ctx.strokeStyle='#00f';
if(enemies[i].type==0){
if(enemies[i].timer%2==0)
enemies[i].drawImageArea(ctx,spritesheet,30,0,10,10);
else{
ctx.strokeStyle='#fff';
enemies[i].drawImageArea(ctx,spritesheet,40,0,10,10);
}
}
else if(enemies[i].type==1)
enemies[i].drawImageArea(ctx,spritesheet,80+(~~(aTimer*10)%2)*10,0,10,10);
else if(enemies[i].type==2)
enemies[i].drawImageArea(ctx,spritesheet,75,(~~(aTimer*10)%2)*5,5,5);
}
ctx.fillStyle='#fff';
ctx.fillText('Score: 0',0,20);
ctx.fillText('Health: ?',150,20);
if(pause){
ctx.textAlign='center';
ctx.fillText('PAUSE',100,150);
ctx.textAlign='left';
}
}
function Rectangle(x,y,width,height,type,health){
this.x=(x==null)?0:x;
this.y=(y==null)?0:y;
this.width=(width==null)?0:width;
this.height=(height==null)?this.width:height;
this.type=(type==null)?1:type;
this.health=(health==null)?1:health;
this.timer=0;
}
Rectangle.prototype.intersects=function(rect){
if(rect!=null){
return(this.x<rect.x+rect.width&&
this.x+this.width>rect.x&&
this.y<rect.y+rect.height&&
this.y+this.height>rect.y);
}
}
Rectangle.prototype.fill=function(ctx){
ctx.fillRect(this.x,this.y,this.width,this.height);
}
Rectangle.prototype.drawImageArea=function(ctx,img,sx,sy,sw,sh){
if(img.width)
ctx.drawImage(img,sx,sy,sw,sh,this.x,this.y,this.width,this.height);
else
ctx.strokeRect(this.x,this.y,this.width,this.height);
}
window.requestAnimationFrame=(function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback){window.setTimeout(callback,17);};
})();
})();
lol eres el amo tio
ResponderBorrarMuchísimas gracias :))
ResponderBorrarGenia genial genial!
ResponderBorrarBuenísimo!!
ResponderBorrarDiferenciar el tipo de objetos en nuestro mundo mediante numeros puede llegar a ser un lío, en su lugar se podría usar un string que es algo un poco mas expresivo, por ejemplo..
ResponderBorrarfunction Entity ( type, options ) {
type = type ? type : "generic_type"
this.x = options.x;
// more properties goes here
}
Ejemplo de uso
var player = new Entity( "player", { x: 10, y: 10, w:15, h: 15 } );
var kamikase = new Entity( "kamikase", { x: 10, y: 10, w:15, h: 15 } );
if ( kamikase.type === "kamikase" && player.isCollidedBy( kamikase ) ) {
// magics goes here
}
Usar nombres identificables para los humanos es sin duda una forma de mantener mejor control sobre el código. Sin embargo, es bien sabido que comparar contra strings es más costoso que comparar contra enteros, ya que el evaluador compara caracter por caracter detrás del código.
BorrarEn realidad esto rara vez afecta el rendimiento en juegos pequeños, pero si se desea optimizar más, una solución popular es utilizar constantes con valores numéricos para esta tarea:
var KAMIKAZE = 0;
var SHOOTER = 1;
...
Que Gran idea y contundente respuesta, tus argumentos no tienen discucion.. ahora querido Karl quisiera preguntarte como haces para que tus videogames sean fluidos en moviles, resulta que estoy usando una tablet razonablemente buena y aun asi no se ve muy natural el ejercicio de esta pagina...(movimiento)
Borrar¿Es el mismo ejemplo directo de este sitio? ¿O un ejemplo que has adaptado aparte? Si es directo de este sitio, he de comentar que hay varios scripts extra que están corriendo en el sitio, que podrían quizá alentar el desempeño del juego.
BorrarAun así, me resulta algo extraño, ya que reviso el ejemplo desde un celular relativamente bueno, y el rendimiento se siente bastante natural...
Es el ejemplo directo de este sitio, tampoco es que se vea como el gtaV corriendo en un radio pero bueno hay un pequeño lag, tal vez tengas razon con lo de los otros scripts, estare testeando, muchas gracias por tus respuestas...
BorrarPara estar seguros, puedes hacer una versión aparte del código y comprobar si eso mejora su rendimiento.
BorrarPuedes probar también el ejemplo de RequestAnimationFrame para revisar los cuadros por segundo que soporta tu tableta en una simulación simple. Eso podría darnos un mejor perfil de sus capacidades.
Hola Karl, gracias por todos estos tutoriales, te quería hacer una consulta, he implementado este código al juego de naves, y la nave disparadora y los disparos no me aparecen, pero si están ahí porque me hacen daño, donde puede estar el fallo?
ResponderBorrar¿De que tamaño son los gráficos de tus balas? Me ha ocurrido ver que a veces ponen un gráfico tan grande que este se dibuja fuera del área del juego y no tienen forma de detectar que este es el problema.
BorrarSi este no es tu caso, necesitaría ver tu juego para analizar que otro problema puede ser. Quizá algún nombre incorrecto o una instrucción faltante por accidente.
Muchas gracias por responder tan rápido, tengo el código como el último de tema2 naves, he implementado el código tal y como lo pones aqui.
Borrar//Dibuja enemigos
for(var i=0,l=enemigos.length;i<l;i++){
ctx.strokeStyle='#00f';
if(enemigos[i].tipo==0){
if(enemigos[i].tiempo%2==0){
//Imagen de nave enemiga.
enemigos[i].drawImageArea(ctx,naves,30,0,10,10);
}
else{
ctx.strokeStyle='#fff'; //Imagen de nave en blanco al recibir daño.
enemigos[i].drawImageArea(ctx,naves,40,0,10,10);
}
}
else if(enemigos[i].tipo==2) //Dos imágenes a 10 cuadros por segundo,con 10 pixeles.
enemigos[i].drawImageArea(ctx,navee,80+(~~(anitemp*10)%2)*10,0,10,10);
else if(enemigos[i].tipo==3)
enemigos[i].drawImageArea(ctx,navee,75,(~~(anitemp*10)%2)*5,5,5);
}
// Generamos enemigo tipo disparador.
Borraretemp--;
if(etemp<0){
enemigos.push(new Rectangle(0,40,10,10,1));
etemp=20+aleatorio(40);//Equivale a de 1 a 3 segundos.
}
//Movemos nave disparadora.
for(var i=0,l=enemigos.length;i0)
enemigos[i].tiempo--;
//Disparo
if(enemigos[i].tipo==2){ //Tipo 2 es la nave.
enemigos[i].x+=5;
//Disparo fuera de la pantalla.
if(enemigos[i].x>canvas.width){
enemigos.splice(i--,1);
l--;
continue;
}
//Disparando
enemigos[i].tiempo--;
if(enemigos[i].tiempo<0){
enemigos.push(new Rectangle(enemigos[i].x+3,enemigos[i].y+5,5,5,2));
enemigos[i].tiempo=10+aleatorio(30);
}
//Intersección entre nuestro disparos y nave disparadora.
for(var j=0,ll=disparos.length;jcanvas.height){
enemigos.splice(i--,1);
l--;
continue;
}
//Intersección jugador con disparos enemigos.
if(jugador.intersects(enemigos[i])&&jugador.tiempo<1){
jugador.vida--;
jugador.tiempo=20;
}
}
}
¿Cual es el tipo de tu nave disparadora y cual el tipo de sus disparos? Por el código de dibujo y los comentarios, parecen ser 2 y 3 respectivamente, pero al momento de crear un nuevo disparo, los creas con el tipo 2.
BorrarRevisa si este es el problema, y si no, ¿Podría ver la imagen que estás usando?
Gracias por responder¡¡ Utilizo la misma imagen que proporcionas por aquí.
Borrar//Disparos enemigos.
Borrarelse if(enemigos[i].tipo==3){//Tipo 3 disparo.
enemigos[i].y+=10;
//Disparo enemigo fuera de la pantalla
if(enemigos[i].y>canvas.height){
enemigos.splice(i--,1);
l--;
continue;
}
//Intersección jugador con disparos enemigos.
if(jugador.intersects(enemigos[i])&&jugador.tiempo<1){
jugador.vida--;
jugador.tiempo=20;
}
}
}
Esto también lo tengo puesto en el código
El tipo 2 es el disparador, estaba mal el comentario, y el tipo 3 el disparo.
BorrarEn esta parte:
Borrar//Disparando
enemigos[i].tiempo--;
if(enemigos[i].tiempo<0){
enemigos.push(new Rectangle(enemigos[i].x+3,enemigos[i].y+5,5,5,**2**));
enemigos[i].tiempo=10+aleatorio(30);
}
¿El número entre asteriscos no debería ser 3?
// Generamos enemigo tipo disparador.
Borraretemp--;
if(etemp<0){
enemigos.push(new Rectangle(0,40,10,10,1));
etemp=20+aleatorio(40);//Equivale a de 1 a 3 segundos.
}
//Movemos nave disparadora.
for(var i=0,l=enemigos.length;i0)
enemigos[i].tiempo--;
//Disparador
if(enemigos[i].tipo==2){ //Tipo 2 es la nave.
enemigos[i].x+=5;
//Disparador fuera de la pantalla.
if(enemigos[i].x>canvas.width){
enemigos.splice(i--,1);
l--;
continue;
}
//Disparador nuevo
enemigos[i].tiempo--;
if(enemigos[i].tiempo<0){
enemigos.push(new Rectangle(enemigos[i].x+3,enemigos[i].y+5,5,5,2));
enemigos[i].tiempo=10+aleatorio(30);
}
//Intersección entre nuestro disparos y nave disparadora.
for(var j=0,ll=disparos.length;jcanvas.height){
enemigos.splice(i--,1);
l--;
continue;
}
//Intersección jugador con disparos enemigos.
if(jugador.intersects(enemigos[i])&&jugador.tiempo<1){
jugador.vida--;
jugador.tiempo=20;
}
}
}
He corregido comentarios que estaban mal, he cambiado eso y sigue igual, me hace daño pero no se ve la imagen...
Me refiero que he cambiado el dos por el tres y sigue igual
BorrarTodo parece estar bien. No creo que pueda hacer más sin depurar directamente tu juego con todos sus componentes.
BorrarComo te lo podría pasar?
BorrarSi puedes hospedar las imágenes en algún sitio, podrías usar JSFiddle, aunque seguro sería más fácil algún enlace a un ZIP con tu proyecto, como Dropbox.
Borrarhttps://www.dropbox.com/sh/8ci0bph2qzfoxco/AADO764tDnaW0w_FCqfcGVzKa?dl=0
BorrarPerdón por las molestias y gracias por mirar mi código, aquí te dejo el enlace¡¡
Ya vi en donde se dio la confusión.
BorrarEn la lección, indiqué que las imágenes debían ser anexadas al final de la imagen original, sin embargo, tú creaste una nueva variable para incluir estas imágenes nuevas por aparte. Al dibujarlo, estás buscando las imágenes en la posición 75 y 80 para disparos y el nuevo enemigo, pero la imagen solo mide 25px de ancho. Si deseas mantener las imágenes aparte, únicamente debes cambiar la posición a 0 y 5 respectivamente, y deberán verse sin problemas.
Encontré un error más. Los tipos de tus enemigos son 0 (kamikazes), 1 (Disparadores) y 2 (Munición enemiga), pero estás intentando dibujar 0, 2 y 3. Cambia el 2 por 1 y el 3 por 2, eso corregirá las naves fantasmas actuales.
BorrarEste comentario ha sido eliminado por el autor.
BorrarMuchas gracias,ya sale¡¡ Lo cambié para hacer pruebas, a ver donde estaba el error..y la lié,no sé xq la nave disparadora me sale en paralelo..
Borrar