Build your First Game with HTML5

本文提供了一篇详细的教程,演示如何利用HTML5和Box2D引擎创建一个简单的游戏。从设置项目开始,到游戏逻辑、物理模拟、绘制及交互功能的实现,全程指导读者完成游戏开发。

某位有爱同学提供的这篇文章,很好的启蒙教程,我都在看呢

原文:http://net.tutsplus.com/tutorials/html-css-techniques/build-your-first-game-with-html5/


Build your First Game with HTML5

Tutorial Details
  • Technologies Used: HTML5, JS, CSS
  • Difficulty: Intermediate
  • Estimated Completion Time: 45-60 minutes

Final Product What You'll Be Creating

HTML5 is growing up faster than anyone could have imagined. Powerful and professional solutions are already being developed…even in the gaming world! Today, you’ll make your first game using Box2D and HTML5′s canvas tag.


What is Box2D?

Box2D is an open source and popular engine that simulates 2D physics for making games and applications. Primarily written in C++, it has been converted to numerous languages by community contributors.

With the same methods and objects, you have the ability to make your games’ physics in many languages, such as Objective C (iPhone/iPad), Actionscript 3.0 (Web), HTML 5 (Web), etc.


Step 1 - Setting up your Project

To begin developing your demo, download the Box2D engine for HTML5 here. Next, create a new HTML file with the following structure (copy js and lib directories from box2d-js project to your game folder).

Now, you must insert the necessary files to run box2D into your HTML file:

  1. <!--[if IE]><script src="lib/excanvas.js"></script><![endif]-->  
  2.  <script src="lib/prototype-1.6.0.2.js"></script>  
  3. <!-- box2djs -->  
  4.  <script src='js/box2d/common/b2Settings.js'></script>  
  5.  <script src='js/box2d/common/math/b2Vec2.js'></script>  
  6.  <script src='js/box2d/common/math/b2Mat22.js'></script>  
  7.  <script src='js/box2d/common/math/b2Math.js'></script>  
  8.  <script src='js/box2d/collision/b2AABB.js'></script>  
  9.  <script src='js/box2d/collision/b2Bound.js'></script>  
  10.  <script src='js/box2d/collision/b2BoundValues.js'></script>  
  11.  <script src='js/box2d/collision/b2Pair.js'></script>  
  12.  <script src='js/box2d/collision/b2PairCallback.js'></script>  
  13.  <script src='js/box2d/collision/b2BufferedPair.js'></script>  
  14.  <script src='js/box2d/collision/b2PairManager.js'></script>  
  15.  <script src='js/box2d/collision/b2BroadPhase.js'></script>  
  16.  <script src='js/box2d/collision/b2Collision.js'></script>  
  17.  <script src='js/box2d/collision/Features.js'></script>  
  18.  <script src='js/box2d/collision/b2ContactID.js'></script>  
  19.  <script src='js/box2d/collision/b2ContactPoint.js'></script>  
  20.  <script src='js/box2d/collision/b2Distance.js'></script>  
  21.  <script src='js/box2d/collision/b2Manifold.js'></script>  
  22.  <script src='js/box2d/collision/b2OBB.js'></script>  
  23.  <script src='js/box2d/collision/b2Proxy.js'></script>  
  24.  <script src='js/box2d/collision/ClipVertex.js'></script>  
  25.  <script src='js/box2d/collision/shapes/b2Shape.js'></script>  
  26.  <script src='js/box2d/collision/shapes/b2ShapeDef.js'></script>  
  27.  <script src='js/box2d/collision/shapes/b2BoxDef.js'></script>  
  28.  <script src='js/box2d/collision/shapes/b2CircleDef.js'></script>  
  29.  <script src='js/box2d/collision/shapes/b2CircleShape.js'></script>  
  30.  <script src='js/box2d/collision/shapes/b2MassData.js'></script>  
  31.  <script src='js/box2d/collision/shapes/b2PolyDef.js'></script>  
  32.  <script src='js/box2d/collision/shapes/b2PolyShape.js'></script>  
  33.  <script src='js/box2d/dynamics/b2Body.js'></script>  
  34.  <script src='js/box2d/dynamics/b2BodyDef.js'></script>  
  35.  <script src='js/box2d/dynamics/b2CollisionFilter.js'></script>  
  36.  <script src='js/box2d/dynamics/b2Island.js'></script>  
  37.  <script src='js/box2d/dynamics/b2TimeStep.js'></script>  
  38.  <script src='js/box2d/dynamics/contacts/b2ContactNode.js'></script>  
  39.  <script src='js/box2d/dynamics/contacts/b2Contact.js'></script>  
  40.  <script src='js/box2d/dynamics/contacts/b2ContactConstraint.js'></script>  
  41.  <script src='js/box2d/dynamics/contacts/b2ContactConstraintPoint.js'></script>  
  42.  <script src='js/box2d/dynamics/contacts/b2ContactRegister.js'></script>  
  43.  <script src='js/box2d/dynamics/contacts/b2ContactSolver.js'></script>  
  44.  <script src='js/box2d/dynamics/contacts/b2CircleContact.js'></script>  
  45.  <script src='js/box2d/dynamics/contacts/b2Conservative.js'></script>  
  46.  <script src='js/box2d/dynamics/contacts/b2NullContact.js'></script>  
  47.  <script src='js/box2d/dynamics/contacts/b2PolyAndCircleContact.js'></script>  
  48.  <script src='js/box2d/dynamics/contacts/b2PolyContact.js'></script>  
  49.  <script src='js/box2d/dynamics/b2ContactManager.js'></script>  
  50.  <script src='js/box2d/dynamics/b2World.js'></script>  
  51.  <script src='js/box2d/dynamics/b2WorldListener.js'></script>  
  52.  <script src='js/box2d/dynamics/joints/b2JointNode.js'></script>  
  53.  <script src='js/box2d/dynamics/joints/b2Joint.js'></script>  
  54.  <script src='js/box2d/dynamics/joints/b2JointDef.js'></script>  
  55.  <script src='js/box2d/dynamics/joints/b2DistanceJoint.js'></script>  
  56.  <script src='js/box2d/dynamics/joints/b2DistanceJointDef.js'></script>  
  57.  <script src='js/box2d/dynamics/joints/b2Jacobian.js'></script>  
  58.  <script src='js/box2d/dynamics/joints/b2GearJoint.js'></script>  
  59.  <script src='js/box2d/dynamics/joints/b2GearJointDef.js'></script>  
  60.  <script src='js/box2d/dynamics/joints/b2MouseJoint.js'></script>  
  61.  <script src='js/box2d/dynamics/joints/b2MouseJointDef.js'></script>  
  62.  <script src='js/box2d/dynamics/joints/b2PrismaticJoint.js'></script>  
  63.  <script src='js/box2d/dynamics/joints/b2PrismaticJointDef.js'></script>  
  64.  <script src='js/box2d/dynamics/joints/b2PulleyJoint.js'></script>  
  65.  <script src='js/box2d/dynamics/joints/b2PulleyJointDef.js'></script>  
  66.  <script src='js/box2d/dynamics/joints/b2RevoluteJoint.js'></script>  
  67.  <script src='js/box2d/dynamics/joints/b2RevoluteJointDef.js'></script>  

Yep, that’s a huge number of HTTP requests!

Please note that, for deployment, it’s highly recommended that you concatenate all of these resources into one script file.

Next, create two more scripts inside the /js/ folder, called "box2dutils.js" and "game.js".

  • box2dutils.js – it’s a copy and paste from some demos that come with box2dlib, and is important for drawing functions (I will also explain some important parts here).
  • game.js – the game, itself; this is where we create the platforms, the player, apply the keyboard interactions, etc.

Copy and paste the following code into box2dutils.js. Don’t worry! I’ll explain it bit by bit!

  1. function drawWorld(world, context) {  
  2.     for (var j = world.m_jointList; j; j = j.m_next) {  
  3.         drawJoint(j, context);  
  4.     }  
  5.     for (var b = world.m_bodyList; b; b = b.m_next) {  
  6.         for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {  
  7.             drawShape(s, context);  
  8.         }  
  9.     }  
  10. }  
  11. function drawJoint(joint, context) {  
  12.     var b1 = joint.m_body1;  
  13.     var b2 = joint.m_body2;  
  14.     var x1 = b1.m_position;  
  15.     var x2 = b2.m_position;  
  16.     var p1 = joint.GetAnchor1();  
  17.     var p2 = joint.GetAnchor2();  
  18.     context.strokeStyle = '#00eeee';  
  19.     context.beginPath();  
  20.     switch (joint.m_type) {  
  21.     case b2Joint.e_distanceJoint:  
  22.         context.moveTo(p1.x, p1.y);  
  23.         context.lineTo(p2.x, p2.y);  
  24.         break;  
  25.   
  26.     case b2Joint.e_pulleyJoint:  
  27.         // TODO  
  28.         break;  
  29.   
  30.     default:  
  31.         if (b1 == world.m_groundBody) {  
  32.             context.moveTo(p1.x, p1.y);  
  33.             context.lineTo(x2.x, x2.y);  
  34.         }  
  35.         else if (b2 == world.m_groundBody) {  
  36.             context.moveTo(p1.x, p1.y);  
  37.             context.lineTo(x1.x, x1.y);  
  38.         }  
  39.         else {  
  40.             context.moveTo(x1.x, x1.y);  
  41.             context.lineTo(p1.x, p1.y);  
  42.             context.lineTo(x2.x, x2.y);  
  43.             context.lineTo(p2.x, p2.y);  
  44.         }  
  45.         break;  
  46.     }  
  47.     context.stroke();  
  48. }  
  49. function drawShape(shape, context) {  
  50.     context.strokeStyle = '#000000';  
  51.     context.beginPath();  
  52.     switch (shape.m_type) {  
  53.     case b2Shape.e_circleShape:  
  54.         {  
  55.             var circle = shape;  
  56.             var pos = circle.m_position;  
  57.             var r = circle.m_radius;  
  58.             var segments = 16.0;  
  59.             var theta = 0.0;  
  60.             var dtheta = 2.0 * Math.PI / segments;  
  61.             // draw circle  
  62.             context.moveTo(pos.x + r, pos.y);  
  63.             for (var i = 0; i < segments; i++) {  
  64.                 var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));  
  65.                 var v = b2Math.AddVV(pos, d);  
  66.                 context.lineTo(v.x, v.y);  
  67.                 theta += dtheta;  
  68.             }  
  69.             context.lineTo(pos.x + r, pos.y);  
  70.   
  71.             // draw radius  
  72.             context.moveTo(pos.x, pos.y);  
  73.             var ax = circle.m_R.col1;  
  74.             var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);  
  75.             context.lineTo(pos2.x, pos2.y);  
  76.         }  
  77.         break;  
  78.     case b2Shape.e_polyShape:  
  79.         {  
  80.             var poly = shape;  
  81.             var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));  
  82.             context.moveTo(tV.x, tV.y);  
  83.             for (var i = 0; i < poly.m_vertexCount; i++) {  
  84.                 var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));  
  85.                 context.lineTo(v.x, v.y);  
  86.             }  
  87.             context.lineTo(tV.x, tV.y);  
  88.         }  
  89.         break;  
  90.     }  
  91.     context.stroke();  
  92. }  
  93.   
  94. function createWorld() {  
  95.     var worldAABB = new b2AABB();  
  96.     worldAABB.minVertex.Set(-1000, -1000);  
  97.     worldAABB.maxVertex.Set(1000, 1000);  
  98.     var gravity = new b2Vec2(0, 300);  
  99.     var doSleep = true;  
  100.     var world = new b2World(worldAABB, gravity, doSleep);  
  101.     return world;  
  102. }  
  103.   
  104. function createGround(world) {  
  105.     var groundSd = new b2BoxDef();  
  106.     groundSd.extents.Set(1000, 50);  
  107.     groundSd.restitution = 0.2;  
  108.     var groundBd = new b2BodyDef();  
  109.     groundBd.AddShape(groundSd);  
  110.     groundBd.position.Set(-500, 340);  
  111.     return world.CreateBody(groundBd)  
  112. }  
  113.   
  114. function createBall(world, x, y) {  
  115.     var ballSd = new b2CircleDef();  
  116.     ballSd.density = 1.0;  
  117.     ballSd.radius = 20;  
  118.     ballSd.restitution = 1.0;  
  119.     ballSd.friction = 0;  
  120.     var ballBd = new b2BodyDef();  
  121.     ballBd.AddShape(ballSd);  
  122.     ballBd.position.Set(x,y);  
  123.     return world.CreateBody(ballBd);  
  124. }  
  125.   
  126. function createBox(world, x, y, width, height, fixed, userData) {  
  127.     if (typeof(fixed) == 'undefined') fixed = true;  
  128.     var boxSd = new b2BoxDef();  
  129.     if (!fixed) boxSd.density = 1.0;  
  130.   
  131.     boxSd.userData = userData;  
  132.   
  133.     boxSd.extents.Set(width, height);  
  134.     var boxBd = new b2BodyDef();  
  135.     boxBd.AddShape(boxSd);  
  136.     boxBd.position.Set(x,y);  
  137.     return world.CreateBody(boxBd)  
  138. }  

Step 2 - Developing the Game

Open the index.html file that you previously created, and add a canvas element (600x400) within thebody element. This is where we'll work with the HTML5 drawing API:

  1. <canvas id="game" width="600" height="400"></canvas>  

Also, while you're here, reference game.js and box2dutils.js.

  1. <script src='js/box2dutils.js'></script>  
  2. <script src='js/game.js'></script>  

That'll do it for the HTML! Let's work on the fun JavaScript now!

Open game.js, and insert the code below:

  1. // some variables that we gonna use in this demo  
  2. var initId = 0;  
  3. var player = function(){  
  4.     this.object = null;  
  5.     this.canJump = false;  
  6. };  
  7. var world;  
  8. var ctx;  
  9. var canvasWidth;  
  10. var canvasHeight;  
  11. var keys = [];  
  12.   
  13. // HTML5 onLoad event  
  14. Event.observe(window, 'load'function() {  
  15.     world = createWorld(); // box2DWorld  
  16.     ctx = $('game').getContext('2d'); // 2  
  17.     var canvasElm = $('game');  
  18.     canvasWidth = parseInt(canvasElm.width);  
  19.     canvasHeight = parseInt(canvasElm.height);  
  20.     initGame(); // 3  
  21.     step(); // 4  
  22.   
  23. // 5  
  24.     window.addEventListener('keydown',handleKeyDown,true);  
  25.     window.addEventListener('keyup',handleKeyUp,true);  
  26. });  

Box2DWorld - that's why we're here

Okay, let's figure out what this chunk of code does!

Box2DWorld is one of the classes that is made available, via the core of box2d. Its function is simple: combine everything into one class. In box2DWorld, you have the bodies definition and collisions manager of your game or application.

Keep the game.js and box2dutils.js files open, and search for the createWorld() function withinbox2dutils.js.

  1. function createWorld() {  
  2.     // here we create our world settings for collisions  
  3.     var worldAABB = new b2AABB();  
  4.     worldAABB.minVertex.Set(-1000, -1000);  
  5.     worldAABB.maxVertex.Set(1000, 1000);  
  6.     // set gravity vector  
  7.     var gravity = new b2Vec2(0, 300);  
  8.     var doSleep = true;  
  9.     // init our world and return its value  
  10.     var world = new b2World(worldAABB, gravity, doSleep);  
  11.     return world;  
  12. }  

It's quite simple to create the box2DWorld.


Back to game.js

Refer to the commented numbers in the two blocks of code above. On number two, we retrieve the canvaselement's context by using the selector API (looks like jQuery or MooTools selectors, don't they?). On number three, we have a new interesting function: initGame(). This is where we create the scenery.

Copy and paste the code below into game.js, and then we'll review it together.

  1. function initGame(){  
  2.     // create 2 big platforms  
  3.     createBox(world, 3, 230, 60, 180, true'ground');  
  4.     createBox(world, 560, 360, 50, 50, true'ground');  
  5.   
  6.     // create small platforms  
  7.     for (var i = 0; i < 5; i++){  
  8.         createBox(world, 150+(80*i), 360, 5, 40+(i*15), true'ground');  
  9.     }  
  10.   
  11.     // create player ball  
  12.     var ballSd = new b2CircleDef();  
  13.     ballSd.density = 0.1;  
  14.     ballSd.radius = 12;  
  15.     ballSd.restitution = 0.5;  
  16.     ballSd.friction = 1;  
  17.     ballSd.userData = 'player';  
  18.     var ballBd = new b2BodyDef();  
  19.     ballBd.linearDamping = .03;  
  20.     ballBd.allowSleep = false;  
  21.     ballBd.AddShape(ballSd);  
  22.     ballBd.position.Set(20,0);  
  23.     player.object = world.CreateBody(ballBd);  
  24.   
  25. }  
  26.   
  27.  Inside <code>box2dutils.js</code>, we've created a function, called <code>createBox</code>. This creates a static rectangle body.  
  28.  
  29. function createBox(world, x, y, width, height, fixed, userData) { 
  30.     if (typeof(fixed) == 'undefined') fixed = true;  
  31.     //1  
  32. var boxSd = new b2BoxDef();  
  33.     if (!fixed) boxSd.density = 1.0;  
  34.     //2  
  35.     boxSd.userData = userData;  
  36.     //3  
  37.     boxSd.extents.Set(width, height);  
  38.   
  39.     //4  
  40.     var boxBd = new b2BodyDef();  
  41.     boxBd.AddShape(boxSd);  
  42.     //5  
  43.     boxBd.position.Set(x,y);  
  44.     //6  
  45.     return world.CreateBody(boxBd)  
  46. }  

Box2DBody

Box2DBody has some unique characteristics:

  • It can be static (not affected by collisions impacts), kinematic (it isn't affected by collisions, but it can be moved by your mouse, for example), or dynamic (interacts with everything)
  • Must have a shape definition, and should indicate how the object appears
  • May have more than one fixture, which indicates how the object will interact with collisions
  • Its position is set by the center of your object, not the left top edge as many other engines do.
Reviewing the code:
  1. Here, we create one shape definition that will be a square or rectangle, and setup its density (how often it gonna be moved, or rotate by forces).
  2. We setup the userData, usually you setup graphics objects here, but in this example, I just setup strings that will be the identifier of the type of the object for collisions. This parameter doesn't affect physics algorithms.
  3. Setup half of the size of my box (it's a line from the position point, or the center point of the object to a corner)
  4. We create the body definition, and add to it the box shape definition.
  5. Setup the position.
  6. Create the body in the world and return its value.

Creating the Player Ball Body

I've coded the player (ball) directly in the game.js file. It follows the same sequence of creating boxes, but, this time, it's a ball.

  1. var ballSd = new b2CircleDef();  
  2.     ballSd.density = 0.1;  
  3.     ballSd.radius = 12;  
  4.     ballSd.restitution = 0.5;  
  5.     ballSd.friction = 1;  
  6.     ballSd.userData = 'player';  
  7.     var ballBd = new b2BodyDef();  
  8.     ballBd.linearDamping = .03;  
  9.     ballBd.allowSleep = false;  
  10.     ballBd.AddShape(ballSd);  
  11.     ballBd.position.Set(20,0);  
  12.     player.object = world.CreateBody(ballBd);  

So how do we create a body, step by step?

  1. Create the shape, fixture and sensor definition
  2. Create the body definition
  3. Add into the body your shape, fixtures or sensors (not explained in this article)
  4. Create the body in the world

Box2DCircle

As I noted earlier, this follows the same creation process of a box, but now you must set some new parameters.

  • radius - This is the length of a line from the center of the circle to any point on its edge.
  • restitution - How the ball will lose, or gain force when collides with other body.
  • friction - How the ball will roll.

Box2DBody - More Properties

  • damping is used to reduce the velocity of the body - there's angular damping and linear damping.
  • sleep in box2D, bodies can sleep to solve performance issues. For example, let's suppose you are developing a platform game, and the level is defined by a 6000x400 screen. Why do you need to perform physics for objects that are off screen? You don't; that's the point! So the correct choice is to put them to sleep, and improve your game's performance.

We've already created our world; you can test the code that you have so far. You'll see the player falling above the west platform.

Now, if you tried to run the demo, you should be wondering, why is the page as barren as white paper?

Always remember: Box2D doesn't render; it only calculates physics.


Step 3 - Rendering Time

Next, let's render the box2DWorld.

Open your game.js script, and add the following code:

  1. function step() {  
  2.   
  3.     var stepping = false;  
  4.     var timeStep = 1.0/60;  
  5.     var iteration = 1;  
  6.     // 1  
  7.     world.Step(timeStep, iteration);  
  8.     // 2  
  9.     ctx.clearRect(0, 0, canvasWidth, canvasHeight);  
  10.     drawWorld(world, ctx);  
  11.     // 3  
  12.     setTimeout('step()', 10);  
  13. }  
What we accomplish here:
  1. Instructed box2dWorld to perform physics simulations
  2. Cleared canvas screen and draw again
  3. Execute the step() function again in ten milliseconds

With this bit of code, we are now working with physics and drawing. You can test yourself, and watch for a falling ball, as demonstrated below:


drawWorld in box2dutils.js

  1. function drawWorld(world, context) {  
  2.     for (var j = world.m_jointList; j; j = j.m_next) {  
  3.         drawJoint(j, context);  
  4.     }  
  5.     for (var b = world.m_bodyList; b; b = b.m_next) {  
  6.         for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {  
  7.             drawShape(s, context);  
  8.         }  
  9.     }  
  10. }  

What we've written above is a debug function that draws our world into the canvas, using the graphics API provided by HTML5's Canvas API.

The first loop draws all joints. We didn't use joints in this article. They are a bit complex for a first demo, but, nonetheless, they're essential for your games. They allow you to create very interesting bodies.

The second loop draws all bodies, which is why we're here!

  1. function drawShape(shape, context) {  
  2.     context.strokeStyle = '#000000';  
  3.     context.beginPath();  
  4.     switch (shape.m_type) {  
  5.     case b2Shape.e_circleShape:  
  6.         {  
  7.             var circle = shape;  
  8.             var pos = circle.m_position;  
  9.             var r = circle.m_radius;  
  10.             var segments = 16.0;  
  11.             var theta = 0.0;  
  12.             var dtheta = 2.0 * Math.PI / segments;  
  13.             // draw circle  
  14.             context.moveTo(pos.x + r, pos.y);  
  15.             for (var i = 0; i < segments; i++) {  
  16.                 var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));  
  17.                 var v = b2Math.AddVV(pos, d);  
  18.                 context.lineTo(v.x, v.y);  
  19.                 theta += dtheta;  
  20.             }  
  21.             context.lineTo(pos.x + r, pos.y);  
  22.   
  23.             // draw radius  
  24.             context.moveTo(pos.x, pos.y);  
  25.             var ax = circle.m_R.col1;  
  26.             var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);  
  27.             context.lineTo(pos2.x, pos2.y);  
  28.         }  
  29.         break;  
  30.     case b2Shape.e_polyShape:  
  31.         {  
  32.             var poly = shape;  
  33.             var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));  
  34.             context.moveTo(tV.x, tV.y);  
  35.             for (var i = 0; i < poly.m_vertexCount; i++) {  
  36.                 var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));  
  37.                 context.lineTo(v.x, v.y);  
  38.             }  
  39.             context.lineTo(tV.x, tV.y);  
  40.         }  
  41.         break;  
  42.     }  
  43.     context.stroke();  
  44. }  

We're looping through every vertices of the object and drawing it with lines (context.moveTo andcontext.lineTo). Now, it's useful to have an example... but not so useful in practice. When you use graphics, you only need to pay attention to the bodies' positioning. You don't need to loop vertices, as this demo does.


Step 4 - Interactivity

A game without interactivity is a movie, and a movie with interactivity is a game.

Let's develop the keyboard arrow functionality to jump and move the ball.

Add the following code to your game.js file:

  1. function handleKeyDown(evt){  
  2.     keys[evt.keyCode] = true;  
  3. }  
  4.   
  5. function handleKeyUp(evt){  
  6.     keys[evt.keyCode] = false;  
  7. }  
  8.   
  9. // disable vertical scrolling from arrows :)  
  10. document.οnkeydοwn=function(){return event.keyCode!=38 && event.keyCode!=40}  

With handleKeyDown and handleKeyUp, we setup an array which tracks every key the user types. Withdocument.onkeydown, we disable the browser's native vertical scrolling function for up and down arrows. Have you ever played an HTML5 game, and when you jump, the player, enemies and objects go off screen? That won't be an issue now.

Add this next bit of code to the beginning of your step() function:

  1. handleInteractions();  

And outside, declare the function:

  1. function handleInteractions(){  
  2.     // up arrow  
  3.     // 1  
  4.     var collision = world.m_contactList;  
  5.     player.canJump = false;  
  6.     if (collision != null){  
  7.         if (collision.GetShape1().GetUserData() == 'player' || collision.GetShape2().GetUserData() == 'player'){  
  8.             if ((collision.GetShape1().GetUserData() == 'ground' || collision.GetShape2().GetUserData() == 'ground')){  
  9.                 var playerObj = (collision.GetShape1().GetUserData() == 'player' ? collision.GetShape1().GetPosition() :  collision.GetShape2().GetPosition());  
  10.                 var groundObj = (collision.GetShape1().GetUserData() == 'ground' ? collision.GetShape1().GetPosition() :  collision.GetShape2().GetPosition());  
  11.                 if (playerObj.y < groundObj.y){  
  12.                     player.canJump = true;  
  13.                 }  
  14.             }  
  15.         }  
  16.     }  
  17.     // 2  
  18.     var vel = player.object.GetLinearVelocity();  
  19.     // 3  
  20.     if (keys[38] && player.canJump){  
  21.         vel.y = -150;  
  22.     }  
  23.   
  24.     // 4  
  25.     // left/right arrows  
  26.     if (keys[37]){  
  27.         vel.x = -60;  
  28.     }  
  29.     else if (keys[39]){  
  30.         vel.x = 60;  
  31.     }  
  32.   
  33.     // 5  
  34.     player.object.SetLinearVelocity(vel);  
  35. }  

The most complicated piece of the code above is the first one, where we check for a collision, and write some conditions to determine if the shape1 or the shape2 is the player. If it is, we verify if shape1 orshape2 is a ground object. Again, if so, the player is colliding with the ground. Next, we check if the player is above the ground. If that's the case, then the player can jump.

On the second commented line (2), we retrieve the LinearVelocity of the player.

The third and forth commented regions verify if arrows are being pressed, and adjust the velocity vector, accordingly.

In the fifth region, we setup the player with the new velocity vector.

The interactions are now done! But there's no objective, We just jump, jump, jump… and jump!


Step 5 - "You Win" Message

Add the code below to the beginning of your LinearVelocity function:

  1. if (player.object.GetCenterPosition().y > canvasHeight){  
  2.     player.object.SetCenterPosition(new b2Vec2(20,0),0)  
  3. }  
  4. else if (player.object.GetCenterPosition().x > canvasWidth-50){  
  5.     showWin();  
  6.     return;  
  7. }  
  • The first condition determines if the player falls, and should be transported back to the start point (above the west platform).
  • The second condition checks if the player is above the second platform, and won the game. Here's the showWin() function.
  1. function showWin(){  
  2.     ctx.fillStyle    = '#000';  
  3.     ctx.font         = '30px verdana';  
  4.     ctx.textBaseline = 'top';  
  5.     ctx.fillText('Ye! you made it!', 30, 0);  
  6.     ctx.fillText('thank you, andersonferminiano.com', 30, 30);  
  7.     ctx.fillText('@andferminiano', 30, 60);  
  8. }  

And that's it! You've just completed your first simple game with HTML5 and Box2D. Congratulations!


<think>我们正在面对一个特定的链接错误:无法定位程序输入点 `_ZNSo5seekpES4_fposliE` 在动态链接库 `sfml-system-d-2.dll` 上。这个错误通常意味着程序在运行时尝试调用的函数在DLL中不存在。具体来说,这个函数名是一个C++经过名称修饰(name mangling)后的符号,对应的是标准输出流(std::ostream)的成员函数 `seekp` 的一个重载版本。 根据错误信息,我们可以推断出以下几点: 1. 这个符号 `_ZNSo5seekpES4_fposliE` 是C++标准库中一个函数的名称修饰后的形式。它属于标准库,而不是SFML本身。SFML的DLL不应该导出这样的符号(因为标准库函数应该由应用程序自己的C++运行时库提供)。 2. 出现这个错误的原因可能是:我们使用的SFML动态库(特别是调试版本,因为DLL文件名中有`-d`)与我们的编译器使用的C++标准库版本不兼容。 具体分析: - `_ZNSo5seekpES4_fposliE` 这个符号对应于 `std::ostream::seekp(std::streampos, std::ios_base::seekdir)` 函数(经过名称修饰后的形式)。这个函数是C++标准库的一部分,应该由MSVCP140D.dll(Debug版)或MSVCP140.dll(Release版)提供。 - 但是,错误发生在SFML的DLL上,这表明SFML的DLL可能依赖于某个特定版本的C++标准库,而我们当前运行环境中的标准库版本与它构建时使用的版本不一致。 解决方案: 1. **检查编译器版本和SFML库的兼容性** - 确保你使用的SFML库是用与你当前项目相同的编译器(相同版本的Visual Studio)编译的。例如,如果你使用的是Visual Studio 2022(编译器版本MSVC v143),那么你应该下载对应VC17的SFML库(SFML 2.6.x for Visual C++ 17 (Visual Studio 2022))。 - 注意:不同版本的Visual Studio(例如2019和2022)使用的C++运行时库可能不兼容。 2. **检查C++运行时库的安装** - 确保你的系统上安装了与构建SFML相同的Visual C++ Redistributable。如果是Debug版本,则不需要安装Redistributable(因为Redistributable只提供Release版本),但需要确保开发环境中的运行时库一致(即安装对应版本的Visual Studio)。 3. **统一项目的运行时库设置** - 在Visual Studio中,打开项目属性,转到“C/C++” -> “代码生成” -> “运行时库”。确保你的项目设置的运行时库与SFML库编译时使用的设置一致。 - Debug配置应使用“多线程调试DLL (/MDd)” - Release配置应使用“多线程DLL (/MD)” - 注意:SFML官方提供的预编译库都是使用`/MD`和`/MDd`编译的,所以你的项目也必须使用相同的设置。 4. **清理和重建项目** - 删除整个`build`目录,然后重新生成项目(使用CMake重新配置和构建)。 5. **检查DLL文件是否匹配** - 确保运行目录(例如`build`目录)下的`sfml-system-d-2.dll`确实是与你使用的SFML版本匹配的。有时候,可能由于路径问题,另一个不同版本的DLL被放置在了这里。 - 建议从官方下载的SFML包中复制对应的DLL文件覆盖现有文件。 6. **检查是否有多个C++运行时库版本冲突** - 如果你在项目中链接了其他第三方库,确保它们也是用相同版本的编译器编译的,并且使用相同的运行时库设置。 7. **使用Dependency Walker进一步分析** - 使用Dependency Walker打开`sfml-system-d-2.dll`,查看它依赖哪些DLL,特别是C++运行时库(如MSVCP140D.dll, VCRUNTIME140D.dll等)。确保这些DLL的版本与你的开发环境匹配。 8. **考虑静态链接SFML** - 如果问题仍然存在,可以考虑使用静态链接SFML。这样,SFML的代码会被编译进你的可执行文件,从而避免动态链接库的问题。但请注意,静态链接需要你链接SFML的静态库(以`-s`后缀的库文件)并定义宏`SFML_STATIC`。 具体步骤(静态链接): - 在项目属性中,预处理器定义中添加`SFML_STATIC`。 - 在链接器输入中,将SFML的动态库(如`sfml-system-d-2.lib`)替换为静态库(如`sfml-system-s-d.lib`)。 - 注意:静态链接时,还需要链接SFML所依赖的库(如opengl32.lib, winmm.lib, gdi32.lib等)。具体依赖可以参考SFML官方文档。 9. **重新安装或重新构建SFML** - 如果上述方法都不奏效,考虑从源代码重新构建SFML,确保使用和你项目完全相同的编译器和设置。 由于错误信息指向了一个标准库函数,所以问题很可能出在C++运行时库的版本不匹配。请重点检查编译器的版本和运行时库的设置。 引用: [1] SFML官方编译指南:https://www.sfml-dev.org/tutorials/2.6/compile-with-cmake.php [2] Microsoft关于运行时库的文档:https://docs.microsoft.com/cpp/build/reference/md-mt-ld-use-run-time-library 相关问题: 1. 如何确定一个DLL文件是由哪个版本的Visual Studio编译的? 2. 在CMake项目中如何指定使用动态链接还是静态链接SFML? 3. 使用vcpkg安装SFML时,如何确保与当前项目的编译器版本匹配? </think>### 解决动态链接库输入点定位错误:`_ZNSo5seekpES4_fposliE` 该错误表明程序在调用函数 `seekp`(标准输出流的定位函数)时,发现动态链接库 `sfml-system-d-2.dll` 中的函数签名与程序预期不匹配。核心原因是 **编译器版本或C++标准库不兼容**,以下是系统化解决方案: --- ### 1. 编译器版本验证(首要步骤) SFML库必须使用与项目**完全相同的编译器版本**构建: ```mermaid graph LR A[检查VS版本] --> B{下载匹配的SFML} B -->|Visual Studio 2022| C[SFML-VC17] B -->|Visual Studio 2019| D[SFML-VC16] B -->|MinGW| E[SFML-GCC] ``` - **操作步骤**: 1. 打开VS:帮助 → 关于 → 确认版本(如MSVC v143) 2. 下载对应版本:[SFML官方下载页](https://www.sfml-dev.org/download.php) - VS 2022 → 选择 **VC17** - VS 2019 → 选择 **VC16** 3. 替换项目中的 `sfml-system-d-2.dll` > 不同编译器版本的C++名称修饰规则不同(如 `seekp` 的修饰名 `_ZNSo5seekp...`),导致符号解析失败[^1] --- ### 2. 清理并重建依赖 ```powershell # 删除构建目录 rmdir /s /q build mkdir build cd build # 重新配置CMake(VS2022示例) cmake -G "Visual Studio 17 2022" -A x64 -DSFML_DIR="path/to/sfml/lib/cmake/SFML" .. # 强制重建 cmake --build . --config Debug --clean-first ``` --- ### 3. 统一C++运行时库 | 配置 | 项目设置 (VS) | 对应SFML DLL | |------------|---------------------------|-------------------| | **Debug** | `/MDd` (多线程调试DLL) | `sfml-system-d-2.dll` | | **Release**| `/MD` (多线程DLL) | `sfml-system-2.dll` | **验证步骤**: 1. 项目属性 → C/C++ → 代码生成 → 运行时库 → 选择匹配值 2. 检查SFML的 `CMakeLists.txt` 是否包含: ```cmake if(SFML_COMPILER_MSVC) target_compile_options(sfml-system PRIVATE "/MD$<$<CONFIG:Debug>:d>") endif() ``` --- ### 4. DLL依赖链分析 使用 [Dependencies工具](https://github.com/lucasg/Dependencies) 检查: ```mermaid graph TD A[打开sfml-system-d-2.dll] --> B{检查红色项} B -->|缺失MSVCP140D.dll| C[安装VC++ Redist] B -->|符号冲突| D[更换SFML版本] B -->|IAT异常| E[重建SFML] ``` - **修复方案**: - 安装匹配的 [Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) - 运行命令检查导出符号: ```powershell dumpbin /exports D:\develop\code\snake-game-master\build\sfml-system-d-2.dll | findstr "seekp" ``` --- ### 5. 终极方案:静态链接 修改项目配置: ```cmake # CMakeLists.txt set(SFML_STATIC_LIBRARIES ON) # 启用静态链接 target_link_libraries(your_project PRIVATE sfml-system-s-d # 注意-s和-d后缀 sfml-window-s-d ... ) # 添加静态编译宏 target_compile_definitions(your_project PRIVATE SFML_STATIC) ``` 静态链接会将所有依赖编译进EXE文件,彻底消除DLL问题[^2]。 --- ### 预防措施 1. **版本锁定**: ```cmake # 在CMake中固定SFML版本 find_package(SFML 2.6.1 EXACT REQUIRED COMPONENTS system) ``` 2. **使用vcpkg**: ```powershell vcpkg install sfml:x64-windows-static-md # 静态链接 vcpkg integrate install ``` > 经过上述处理,90%的输入点定位错误可解决。若问题依旧,请检查是否混用了不同来源的SFML组件(如部分来自vcpkg,部分手动下载)[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值