Una pregunta que hacen con frecuencia, es la de como crear objetos sólidos, es decir, que al chocar contra ellos, no puedas atravesarlos.
Esta es una técnica realmente sencilla. Para probarla, tomaremos el código de Mover mientras se presiona la tecla, así como las paredes de Interactuando con varios elementos iguales, y los rectángulos 2D de Getters y Setters.
Comenzamos creando nuestras variables para el jugador y las paredes:
Esta es una técnica realmente sencilla. Para probarla, tomaremos el código de Mover mientras se presiona la tecla, así como las paredes de Interactuando con varios elementos iguales, y los rectángulos 2D de Getters y Setters.
Comenzamos creando nuestras variables para el jugador y las paredes:
player = null,
wall = [],
Y los inicializamos en la función "init" con los siguientes valores:
// Create player
player = new Rectangle2D(80, 80, 10, 10, true);
// Create walls
wall.push(new Rectangle2D(100, 50, 10, 10, true));
wall.push(new Rectangle2D(100, 150, 10, 10, true));
wall.push(new Rectangle2D(200, 50, 10, 10, true));
wall.push(new Rectangle2D(200, 150, 10, 10, true));
No olvides dibujarles en la función paint: // Draw player
ctx.fillStyle = '#0f0';
player.fill(ctx);
// Draw walls
ctx.fillStyle = '#999';
for (i = 0, l = wall.length; i < l; i += 1) {
wall[i].fill(ctx);
}
Ahora, para hacer que estas paredes actúen como objetos sólidos, la forma sencilla, es al mover el jugador, si este hace intersección con una pared, se devuelva a su posición anterior: // Move Rect
if (pressing[KEY_UP]) {
player.y -= 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.y += 5;
}
}
}
Sin embargo, si el movimiento no es exacto a la distancia faltante a la pared, quedará un pequeño hueco entre el jugador y la pared. Para solucionar este problema, deberemos posicionar al jugador justo en el punto donde termina un objeto y comienza el otro. La forma más sencilla de solucionar esto, aprovechando las capacidades de nuestra función "Rectangle2D", en el caso actual de avanzar hacia arriba, esto sería posicionando el valor "top" del jugador a la posición "bottom" de la pared: if (player.intersects(wall[i])) {
player.top = wall[i].bottom;
}
No olvides agregar la evaluación de intersección con los otros tres movimientos posibles: if (pressing[KEY_RIGHT]) {
player.x += 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.right = wall[i].left;
}
}
}
if (pressing[KEY_DOWN]) {
player.y += 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.bottom = wall[i].top;
}
}
}
if (pressing[KEY_LEFT]) {
player.x -= 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.left = wall[i].right;
}
}
}
Con esto, podrás crear objetos sólidos en tus juegos, que son la base para juegos de laberintos y (Con algo más de complejidad) juegos de plataformas.Codigo final:
/*jslint bitwise: true, es5: true */
(function (window, undefined) {
'use strict';
var KEY_ENTER = 13,
KEY_LEFT = 37,
KEY_UP = 38,
KEY_RIGHT = 39,
KEY_DOWN = 40,
canvas = null,
ctx = null,
lastPress = null,
pressing = [],
pause = false,
player = null,
wall = [],
i = 0,
l = 0;
function Rectangle2D(x, y, width, height, createFromTopLeft) {
this.width = (width === undefined) ? 0 : width;
this.height = (height === undefined) ? this.width : height;
if (createFromTopLeft) {
this.left = (x === undefined) ? 0 : x;
this.top = (y === undefined) ? 0 : y;
} else {
this.x = (x === undefined) ? 0 : x;
this.y = (y === undefined) ? 0 : y;
}
}
Rectangle2D.prototype = {
left: 0,
top: 0,
width: 0,
height: 0,
get x() {
return this.left + this.width / 2;
},
set x(value) {
this.left = value - this.width / 2;
},
get y() {
return this.top + this.height / 2;
},
set y(value) {
this.top = value - this.height / 2;
},
get right() {
return this.left + this.width;
},
set right(value) {
this.left = value - this.width;
},
get bottom() {
return this.top + this.height;
},
set bottom(value) {
this.top = value - this.height;
},
intersects: function (rect) {
if (rect !== undefined) {
return (this.left < rect.right &&
this.right > rect.left &&
this.top < rect.bottom &&
this.bottom > rect.top);
}
},
fill: function (ctx) {
if (ctx !== undefined) {
ctx.fillRect(this.left, this.top, this.width, this.height);
}
}
};
document.addEventListener('keydown', function (evt) {
lastPress = evt.keyCode;
pressing[evt.keyCode] = true;
if (lastPress >= 37 && lastPress <= 40) {
evt.preventDefault();
}
}, false);
document.addEventListener('keyup', function (evt) {
pressing[evt.keyCode] = false;
}, false);
function paint(ctx) {
// Clean canvas
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw player
ctx.fillStyle = '#0f0';
player.fill(ctx);
// Draw walls
ctx.fillStyle = '#999';
for (i = 0, l = wall.length; i < l; i += 1) {
wall[i].fill(ctx);
}
// Debug last press
ctx.fillStyle = '#fff';
ctx.fillText('Last Press: ' + lastPress, 0, 20);
// Pause
if (pause) {
ctx.textAlign = 'center';
ctx.fillText('PAUSE', 200, 75);
ctx.textAlign = 'left';
}
}
function act(deltaTime) {
if (!pause) {
// Move Rect
if (pressing[KEY_UP]) {
player.y -= 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.top = wall[i].bottom;
}
}
}
if (pressing[KEY_RIGHT]) {
player.x += 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.right = wall[i].left;
}
}
}
if (pressing[KEY_DOWN]) {
player.y += 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.bottom = wall[i].top;
}
}
}
if (pressing[KEY_LEFT]) {
player.x -= 5;
for (i = 0, l = wall.length; i < l; i += 1) {
if (player.intersects(wall[i])) {
player.left = wall[i].right;
}
}
}
// Out Screen
if (player.x > canvas.width) {
player.x = 0;
}
if (player.y > canvas.height) {
player.y = 0;
}
if (player.x < 0) {
player.x = canvas.width;
}
if (player.y < 0) {
player.y = canvas.height;
}
}
// Pause/Unpause
if (lastPress === KEY_ENTER) {
pause = !pause;
lastPress = null;
}
}
function repaint() {
window.requestAnimationFrame(repaint);
paint(ctx);
}
function run() {
setTimeout(run, 50);
act(0.05);
}
function init() {
// Get canvas and context
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 200;
// Create player
player = new Rectangle2D(80, 80, 10, 10, true);
// Create walls
wall.push(new Rectangle2D(100, 50, 10, 10, true));
wall.push(new Rectangle2D(100, 150, 10, 10, true));
wall.push(new Rectangle2D(200, 50, 10, 10, true));
wall.push(new Rectangle2D(200, 150, 10, 10, true));
// Start game
run();
repaint();
}
window.addEventListener('load', init, false);
}(window));
Muy simple pero a la vez muy útil!!
ResponderBorrarMás de una hora sin dar como hacer para no atravesar los bloques y llego en un momento a tu blog y voilá, problema resuelto. Vaya maravilla de blog que tienes compañero, da gusto entrar.
Como siempre breve y conciso. Gracias ^^
Parece ser uno de los secretos más guardados de desarrollobde videojuegos. No se si por lo maravilloso que es esta técnica, o simplemente por que es demasiado sencillo y no vale la pena su mención. ¡Pero es una de las dudas que más se preguntan! (Incluido yo en su momento, aunque tuve que averiguarlo por la mala).
Borrar¡Me alegra haya sido de ayuda para ti! Muchas gracias por tu comentario.
Sos grande!
ResponderBorrarun super bolg muchas gracias
ResponderBorrarMuy bueno tu blog, me esta sirviendo de gran ayuda para empezar a programar mis juegos. Muchas gracias!!
ResponderBorrarUna duda, ¿para que se usa lo siguiente, no lo entiendo, lo de ( ? ) y lo de ( : ) ?
ResponderBorrarthis.x=(x==null)?0:x;
Es una asignación condicional. El signo de interrogación pregunta por el contenido del paréntesis. Si es cierto, devuelve el valor antes del ":", de lo contrario, devuelve el valor despues de este.
Borrarse puede usar como un if?
BorrarAsí es, esta es la forma corta. Sí quieres usar la forma larga, sería así:
Borrarif (x==null) {
this.x = 0;
} else {
this.x = x;
}
El else sería redundante en este caso, por lo que podrías dejarlo fuera.
si lees esto te digo gracias me ayudaste a cumplir mis sueños
ResponderBorrary en esta cuarente xD