HTML5 está creciendo más rápido de lo que cualquiera hubiese imaginado. Poderosas y profesionales soluciones están siendo desarrolladas dÃa a dÃa... ¡Incluso en el mundo de los videojuegos! Hoy, te enseñaremos a hacer tu primer juego utilizando Box2D y la propiedad canvas de HTML5.
Haz clic aquà para ver una demostración >>
Pero... ¿Qué es Box2D? Es un motor de código abierto que simula fÃsicas en 2D para desarrollar juegos y aplicaciones. Escrito principalmente en C++, ha sido convertido a numerosos lenguajes por su comunidad.
Con los mismos métodos y objetos, tienes la capacidad de crear las fÃsicas de tus juegos en varios lenguajes como Objective C (iPhone/iPad), Actionscript 3.0 (Web), HTML 5 (Web), etc.
1 - Situando los archivos
Para comenzar a desarrollar tu demo, descarga el motor Box2D para HTML5 aquÃ.Â
Haz clic en este enlace para descargar Box2D para HTML5 >>
Copia los directorios js y lib del proyecto box2d-js a la carpeta de tu juego, y luego crea un nuevo archivo HTML en el mismo directorio raÃz, con la siguiente estructura:
-
<script src="lib/prototype-1.6.0.2.js"></script>
-
<!-- box2djs -->
-
<script src='js/box2d/common/b2Settings.js'></script>
-
<script src='js/box2d/common/math/b2Vec2.js'></script>
-
<script src='js/box2d/common/math/b2Mat22.js'></script>
-
<script src='js/box2d/common/math/b2Math.js'></script>
-
<script src='js/box2d/collision/b2AABB.js'></script>
-
<script src='js/box2d/collision/b2Bound.js'></script>
-
<script src='js/box2d/collision/b2BoundValues.js'></script>
-
<script src='js/box2d/collision/b2Pair.js'></script>
-
<script src='js/box2d/collision/b2PairCallback.js'></script>
-
<script src='js/box2d/collision/b2BufferedPair.js'></script>
-
<script src='js/box2d/collision/b2PairManager.js'></script>
-
<script src='js/box2d/collision/b2BroadPhase.js'></script>
-
<script src='js/box2d/collision/b2Collision.js'></script>
-
<script src='js/box2d/collision/Features.js'></script>
-
<script src='js/box2d/collision/b2ContactID.js'></script>
-
<script src='js/box2d/collision/b2ContactPoint.js'></script>
-
<script src='js/box2d/collision/b2Distance.js'></script>
-
<script src='js/box2d/collision/b2Manifold.js'></script>
-
<script src='js/box2d/collision/b2OBB.js'></script>
-
<script src='js/box2d/collision/b2Proxy.js'></script>
-
<script src='js/box2d/collision/ClipVertex.js'></script>
-
<script src='js/box2d/collision/shapes/b2Shape.js'></script>
-
<script src='js/box2d/collision/shapes/b2ShapeDef.js'></script>
-
<script src='js/box2d/collision/shapes/b2BoxDef.js'></script>
-
<script src='js/box2d/collision/shapes/b2CircleDef.js'></script>
-
<script src='js/box2d/collision/shapes/b2CircleShape.js'></script>
-
<script src='js/box2d/collision/shapes/b2MassData.js'></script>
-
<script src='js/box2d/collision/shapes/b2PolyDef.js'></script>
-
<script src='js/box2d/collision/shapes/b2PolyShape.js'></script>
-
<script src='js/box2d/dynamics/b2Body.js'></script>
-
<script src='js/box2d/dynamics/b2BodyDef.js'></script>
-
<script src='js/box2d/dynamics/b2CollisionFilter.js'></script>
-
<script src='js/box2d/dynamics/b2Island.js'></script>
-
<script src='js/box2d/dynamics/b2TimeStep.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2ContactNode.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2Contact.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2ContactConstraint.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2ContactConstraintPoint.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2ContactRegister.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2ContactSolver.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2CircleContact.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2Conservative.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2NullContact.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2PolyAndCircleContact.js'></script>
-
<script src='js/box2d/dynamics/contacts/b2PolyContact.js'></script>
-
<script src='js/box2d/dynamics/b2ContactManager.js'></script>
-
<script src='js/box2d/dynamics/b2World.js'></script>
-
<script src='js/box2d/dynamics/b2WorldListener.js'></script>
-
<script src='js/box2d/dynamics/joints/b2JointNode.js'></script>
-
<script src='js/box2d/dynamics/joints/b2Joint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2JointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2DistanceJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2DistanceJointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2Jacobian.js'></script>
-
<script src='js/box2d/dynamics/joints/b2GearJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2GearJointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2MouseJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2MouseJointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2PrismaticJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2PrismaticJointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2PulleyJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2PulleyJointDef.js'></script>
-
<script src='js/box2d/dynamics/joints/b2RevoluteJoint.js'></script>
-
<script src='js/box2d/dynamics/joints/b2RevoluteJointDef.js'></script>
Ahora, crea dos scripts más dentro del directorio /js/, llamados "box2dutils.js" y "game.js".
- box2dutils.js – contendrá un código que suele venir en algunas demos con
box2dlib, y es importante para las funciones de dibujo. - game.js – El juego, en sà mismo. Aquà es donde crearemos las plataformas, el jugador, aplicaremos las interacciones de teclado, etc.
Copia y pega el siguiente código dentro de box2dutils.js:
-
function drawWorld(world, context) {
-
for (var j = world.m_jointList; j; j = j.m_next) {
-
drawJoint(j, context);
-
}
-
for (var b = world.m_bodyList; b; b = b.m_next) {
-
for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {
-
drawShape(s, context);
-
}
-
}
-
}
-
function drawJoint(joint, context) {
-
var b1 = joint.m_body1;
-
var b2 = joint.m_body2;
-
var x1 = b1.m_position;
-
var x2 = b2.m_position;
-
var p1 = joint.GetAnchor1();
-
var p2 = joint.GetAnchor2();
-
context.strokeStyle = '#00eeee';
-
context.beginPath();
-
switch (joint.m_type) {
-
case b2Joint.e_distanceJoint:
-
context.moveTo(p1.x, p1.y);
-
context.lineTo(p2.x, p2.y);
-
break;
-
-
case b2Joint.e_pulleyJoint:
-
// TODO
-
break;
-
-
default:
-
if (b1 == world.m_groundBody) {
-
context.moveTo(p1.x, p1.y);
-
context.lineTo(x2.x, x2.y);
-
}
-
else if (b2 == world.m_groundBody) {
-
context.moveTo(p1.x, p1.y);
-
context.lineTo(x1.x, x1.y);
-
}
-
else {
-
context.moveTo(x1.x, x1.y);
-
context.lineTo(p1.x, p1.y);
-
context.lineTo(x2.x, x2.y);
-
context.lineTo(p2.x, p2.y);
-
}
-
break;
-
}
-
context.stroke();
-
}
-
function drawShape(shape, context) {
-
context.strokeStyle = '#000000';
-
context.beginPath();
-
switch (shape.m_type) {
-
case b2Shape.e_circleShape:
-
{
-
var circle = shape;
-
var pos = circle.m_position;
-
var r = circle.m_radius;
-
var segments = 16.0;
-
var theta = 0.0;
-
var dtheta = 2.0 * Math.PI / segments;
-
// draw circle
-
context.moveTo(pos.x + r, pos.y);
-
for (var i = 0; i <segments; i++) {
-
var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
-
var v = b2Math.AddVV(pos, d);
-
context.lineTo(v.x, v.y);
-
theta += dtheta;
-
}
-
context.lineTo(pos.x + r, pos.y);
-
-
// draw radius
-
context.moveTo(pos.x, pos.y);
-
var ax = circle.m_R.col1;
-
var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
-
context.lineTo(pos2.x, pos2.y);
-
}
-
break;
-
case b2Shape.e_polyShape:
-
{
-
var poly = shape;
-
var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
-
context.moveTo(tV.x, tV.y);
-
for (var i = 0; i <poly.m_vertexCount; i++) {
-
var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
-
context.lineTo(v.x, v.y);
-
}
-
context.lineTo(tV.x, tV.y);
-
}
-
break;
-
}
-
context.stroke();
-
}
-
-
function createWorld() {
-
var worldAABB = new b2AABB();
-
worldAABB.minVertex.Set(-1000, -1000);
-
worldAABB.maxVertex.Set(1000, 1000);
-
var gravity = new b2Vec2(0, 300);
-
var doSleep = true;
-
var world = new b2World(worldAABB, gravity, doSleep);
-
return world;
-
}
-
-
function createGround(world) {
-
var groundSd = new b2BoxDef();
-
groundSd.extents.Set(1000, 50);
-
groundSd.restitution = 0.2;
-
var groundBd = new b2BodyDef();
-
groundBd.AddShape(groundSd);
-
groundBd.position.Set(-500, 340);
-
return world.CreateBody(groundBd)
-
}
-
-
function createBall(world, x, y) {
-
var ballSd = new b2CircleDef();
-
ballSd.density = 1.0;
-
ballSd.radius = 20;
-
ballSd.restitution = 1.0;
-
ballSd.friction = 0;
-
var ballBd = new b2BodyDef();
-
ballBd.AddShape(ballSd);
-
ballBd.position.Set(x,y);
-
return world.CreateBody(ballBd);
-
}
-
-
function createBox(world, x, y, width, height, fixed, userData) {
-
if (typeof(fixed) == 'undefined') fixed = true;
-
var boxSd = new b2BoxDef();
-
if (!fixed) boxSd.density = 1.0;
-
-
boxSd.userData = userData;
-
-
boxSd.extents.Set(width, height);
-
var boxBd = new b2BodyDef();
-
boxBd.AddShape(boxSd);
-
boxBd.position.Set(x,y);
-
return world.CreateBody(boxBd)
-
}
2 - Desarrollando el juego
Abre el archivo index.html que creamos previamente, y añade un elemento canvas (600x400) dentro del elemento body. Esto es donde trabajaremos con la API de dibujo de HTML5:
-
<canvas id="game" width="600" height="400"></canvas>
Además, añadiremos como referencia game.js y box2dutils.js.
Y eso es todo el HTML. Comencemos con el JavaScript: Abrimos game.js, y allà insertamos este código:
-
// some variables that we gonna use in this demo
-
var initId = 0;
-
var player = function(){
-
this.object = null;
-
this.canJump = false;
-
};
-
var world;
-
var ctx;
-
var canvasWidth;
-
var canvasHeight;
-
var keys = [];
-
-
// HTML5 onLoad event
-
Event.observe(window, 'load', function() {
-
world = createWorld(); // box2DWorld
-
ctx = $('game').getContext('2d'); // 2
-
var canvasElm = $('game');
-
canvasWidth = parseInt(canvasElm.width);
-
canvasHeight = parseInt(canvasElm.height);
-
initGame(); // 3
-
step(); // 4
-
-
// 5
-
window.addEventListener('keydown',handleKeyDown,true);
-
window.addEventListener('keyup',handleKeyUp,true);
-
});
3 - Box2DWorld
Box2DWorld es una de las clases a las que podemos acceder a través del nucleo de box2d. Su función es simple: combina todo en una sola clase. En box2DWorld, tienes la definición de cuerpos y el gestor de colisiones de tu juego o aplicación.
Manten los archivos game.js y box2dutils.js abiertos, y busca por la función createWorld() dentro de box2dutils.js:
-
function createWorld() {
-
// here we create our world settings for collisions
-
var worldAABB = new b2AABB();
-
worldAABB.minVertex.Set(-1000, -1000);
-
worldAABB.maxVertex.Set(1000, 1000);
-
// set gravity vector
-
var gravity = new b2Vec2(0, 300);
-
var doSleep = true;
-
// init our world and return its value
-
var world = new b2World(worldAABB, gravity, doSleep);
-
return world;
-
}
4 -Volvamos a Game.js
Copia y pega este código en el archivo game.js:
-
function initGame(){
-
// create 2 big platforms
-
createBox(world, 3, 230, 60, 180, true, 'ground');
-
createBox(world, 560, 360, 50, 50, true, 'ground');
-
-
// create small platforms
-
for (var i = 0; i <5; i++){
-
createBox(world, 150+(80*i), 360, 5, 40+(i*15), true, 'ground');
-
}
-
-
// create player ball
-
var ballSd = new b2CircleDef();
-
ballSd.density = 0.1;
-
ballSd.radius = 12;
-
ballSd.restitution = 0.5;
-
ballSd.friction = 1;
-
ballSd.userData = 'player';
-
var ballBd = new b2BodyDef();
-
ballBd.linearDamping = .03;
-
ballBd.allowSleep = false;
-
ballBd.AddShape(ballSd);
-
ballBd.position.Set(20,0);
-
player.object = world.CreateBody(ballBd);
-
-
}
-
-
Inside <code>box2dutils.js</code>, we've created a function, called <code>createBox</code>. This creates a static rectangle body.
-
function createBox(world, x, y, width, height, fixed, userData) {
-
if (typeof(fixed) == 'undefined') fixed = true;
-
//1
-
var boxSd = new b2BoxDef();
-
if (!fixed) boxSd.density = 1.0;
-
//2
-
boxSd.userData = userData;
-
//3
-
boxSd.extents.Set(width, height);
-
//4
-
var boxBd = new b2BodyDef();
-
boxBd.AddShape(boxSd);
-
//5
-
boxBd.position.Set(x,y);
-
//6
-
return world.CreateBody(boxBd)
-
}
5 -Box2DBody
Un Box2DBody tiene caracterÃsticas únicas:
- Puede ser estático (no lo afectan las colisiones), kinético (no lo afectan las colisiones, pero puede ser movido por el mouse u otros, o dinámico (interactúa con todo)
- Debe tener una definición de forma, y debe indicar cómo aparece el objeto.
- Puede tener más de un aparato, lo que indica cómo el objeto interactuará con colisiones
- Su posición estará establecida en el centro del objeto, no en su costado superior izquierdo como otros motores suelen hacer.
6 - Creando el cuerpo Player Ball
El código del jugador (la bola) estará en el archivo game.js. Lo seguirá la misma secuencia de crear cajas, pero esta vez, será una bola.
-
var ballSd = new b2CircleDef();
-
ballSd.density = 0.1;
-
ballSd.radius = 12;
-
ballSd.restitution = 0.5;
-
ballSd.friction = 1;
-
ballSd.userData = 'player';
-
var ballBd = new b2BodyDef();
-
ballBd.linearDamping = .03;
-
ballBd.allowSleep = false;
-
ballBd.AddShape(ballSd);
-
ballBd.position.Set(20,0);
-
player.object = world.CreateBody(ballBd);
7 - Box2DCircle
Como notamos antes, la bola tiene algunas diferencias en su creación con las cajas:
- radius - Este es el largo de una linea desde el centro del cÃrculo a cualquier punto de su borde.
- restitution - Cómo la bola perderá, o ganará fuerza cuando colisione con otro cuerpo.
- friction - Cómo la bola rodará.
8 - Hora del render
Ahora, llegó el momento de renderear nuestro box2DWorld.
Abre game.js, y añade el siguiente código:
-
function step() {
-
-
var stepping = false;
-
var timeStep = 1.0/60;
-
var iteration = 1;
-
// 1
-
world.Step(timeStep, iteration);
-
// 2
-
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
-
drawWorld(world, ctx);
-
// 3
-
setTimeout('step()', 10);
-
}
Si hemos hecho todo bien, nuestro juego se verá asÃ:
9 - drawWorld en box2dutils.js
Ahora, añadiremos este código a nuestro archivo box2dutils.js:
-
function drawWorld(world, context) {
-
for (var j = world.m_jointList; j; j = j.m_next) {
-
drawJoint(j, context);
-
}
-
for (var b = world.m_bodyList; b; b = b.m_next) {
-
for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {
-
drawShape(s, context);
-
}
-
}
-
}
-
function drawShape(shape, context) {
-
context.strokeStyle = '#000000';
-
context.beginPath();
-
switch (shape.m_type) {
-
case b2Shape.e_circleShape:
-
{
-
var circle = shape;
-
var pos = circle.m_position;
-
var r = circle.m_radius;
-
var segments = 16.0;
-
var theta = 0.0;
-
var dtheta = 2.0 * Math.PI / segments;
-
// draw circle
-
context.moveTo(pos.x + r, pos.y);
-
for (var i = 0; i <segments; i++) {
-
var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
-
var v = b2Math.AddVV(pos, d);
-
context.lineTo(v.x, v.y);
-
theta += dtheta;
-
}
-
context.lineTo(pos.x + r, pos.y);
-
-
// draw radius
-
context.moveTo(pos.x, pos.y);
-
var ax = circle.m_R.col1;
-
var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
-
context.lineTo(pos2.x, pos2.y);
-
}
-
break;
-
case b2Shape.e_polyShape:
-
{
-
var poly = shape;
-
var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
-
context.moveTo(tV.x, tV.y);
-
for (var i = 0; i <poly.m_vertexCount; i++) {
-
var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
-
context.lineTo(v.x, v.y);
-
}
-
context.lineTo(tV.x, tV.y);
-
}
-
break;
-
}
-
context.stroke();
-
}
10 - Interactividad
Un juego sin interactividad es una pelÃcula, y una pelÃcula con interactividad es un juego.
Vamos a construir la funcionalidad de las flechas del teclado para saltar y mover la bola. Añade el siguiente código a tu archivo game.js :
-
function handleKeyDown(evt){
-
keys[evt.keyCode] = true;
-
}
-
-
function handleKeyUp(evt){
-
keys[evt.keyCode] = false;
-
}
-
-
// disable vertical scrolling from arrows
-
document.onkeydown=function(){return event.keyCode!=38 && event.keyCode!=40}
Con handleKeyDown y handleKeyUp, configuramos un array con caminos cada vez que el usuario presiona una tecla. Con document.onkeydown, desactivamos la función de scrolling vertical nativa del navegador para las flechas arriba y abajo.
Añade a esto un poco de código al comienzo de tu función step():
-
handleInteractions();
Y por fuera, declara la función:
-
function handleInteractions(){
-
// up arrow
-
// 1
-
var collision = world.m_contactList;
-
player.canJump = false;
-
if (collision != null){
-
if (collision.GetShape1().GetUserData() == 'player' || collision.GetShape2().GetUserData() == 'player'){
-
if ((collision.GetShape1().GetUserData() == 'ground' || collision.GetShape2().GetUserData() == 'ground')){
-
var playerObj = (collision.GetShape1().GetUserData() == 'player' ? collision.GetShape1().GetPosition() : collision.GetShape2().GetPosition());
-
var groundObj = (collision.GetShape1().GetUserData() == 'ground' ? collision.GetShape1().GetPosition() : collision.GetShape2().GetPosition());
-
if (playerObj.y <groundObj.y){
-
player.canJump = true;
-
}
-
}
-
}
-
}
-
// 2
-
var vel = player.object.GetLinearVelocity();
-
// 3
-
if (keys[38] && player.canJump){
-
vel.y = -150;
-
}
-
-
// 4
-
// left/right arrows
-
if (keys[37]){
-
vel.x = -60;
-
}
-
else if (keys[39]){
-
vel.x = 60;
-
}
-
-
// 5
-
player.object.SetLinearVelocity(vel);
-
}
11 - Objetivo del juego y pantalla de triunfo
Añade el siguiente código al comienzo de tu función LinearVelocity:
-
if (player.object.GetCenterPosition().y> canvasHeight){
-
player.object.SetCenterPosition(new b2Vec2(20,0),0)
-
}
-
else if (player.object.GetCenterPosition().x> canvasWidth-50){
-
showWin();
-
return;
-
}
- La primera condición determina si el jugador cae, y debe ser transportado de vuelta al punto de comenzo (debajo de la plataforma oeste).
- La segunda condición se asegura si el jugador está sobre la segunda plataforma, ganando asà el juego. Aquà debe ir la función
showWin():
-
function showWin(){
-
ctx.fillStyle = '#000';
-
ctx.font = '30px verdana';
-
ctx.textBaseline = 'top';
-
ctx.fillText('Ye! you made it!', 30, 0);
-
ctx.fillText('thank you, andersonferminiano.com', 30, 30);
-
ctx.fillText('@andferminiano', 30, 60);
-
}
¡Y eso es todo! Has completado tu primer (muy sencillo) juego en HTML5 y Box2D. ¡Felicitaciones!
Haz clic aquà para ver una demostración >>
Fuente original del artÃculo: Net Tuts+
Traducción realizada por elWebmaster.com









Lunes, 5 de Marzo de 2012 a las 18.52
recien lo voy a leer haber si se entiende les digo
Lunes, 19 de Marzo de 2012 a las 20.17
Hola, me parece un exelente ejemplo para empezar a desarrollar juegos con html5 y Box2D.
Sin embargo, creo que cometiste un error en el paso 11, el código que colocas allà debe ir en la función "step()" y no en "LinearVelocity()" ya que esa función no existe y al no colocar ese fragmento de código correctamente como resultado el juego nunca termina.
Saludos!
Lunes, 19 de Marzo de 2012 a las 22.45
muy bueno!!