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:
- <!--[if IE]><script src="lib/excanvas.js"></script><![endif]-->
- <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)
- }
- [/javascript]
- <h2>2 - Desarrollando el juego</h2>
- Abre el archivo <code>index.html</code> que creamos previamente, y añade un elemento <code>canvas</code> (600x400) dentro del elemento <code>body</code>. Esto es donde trabajaremos con la API de dibujo de HTML5:
- [html]
- <canvas id="game" width="600" height="400"></canvas>
- [/html]
- Además, añadiremos como referencia <code>game.js</code> y <code>box2dutils.js</code>.
- [html]
- <script src='js/box2dutils.js'></script>
- <script src='js/game.js'></script>
- [/html]
- Y eso es todo el HTML. Comencemos con el JavaScript: Abrimos <code>game.js</code>, y allà insertamos este código:
- [javascript]
- // 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();
- }
- [/javascript]
- <h2>10 - Interactividad</h2>
- 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 <code>game.js </code>:
- [javascript]
- 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);
- }
- [/javascript]
- <h2>11 - Objetivo del juego y pantalla de triunfo</h2>
- Añade el siguiente código al comienzo de tu función <code>LinearVelocity</code>:
- [javascript]
- 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!!