HTML5游戏开发进阶 4 :物理引擎集成

   首先向关卡中添加物体,对这些物体进行Box2D物理仿真,然后让它们在游戏中动起来。我们将使用这些物体创建测试关卡,加入鼠标交互,使游戏真正具有可玩性。最后,再向测试关卡加入音效和背景音乐,制作出一个完整的游戏。

4.1 定义物体

     装载的物体:英雄、坏蛋、地面和环境中的障碍物。

     属性type包含:"hero", "villain", "ground", "block"这样的值。

     entities 对象

4.2 添加Box2D

      创建box2d对象

4.3 创建物体

      定义entities.create方法:

4.4 向关卡加入物体

      levels.array数组

      levels.load

4.5 设置Box2D调试绘图

     <canvas id="debugcanvas" width="1000" height="480" style="border:1px solid block;"></canvas>

    仅仅是设计和测试关卡

4.6 绘制物体

    entities对象内部定义draw()方法

4.7 Box2D动画

    时间步长

    box2d.step()

4.8 加载英雄

     第一个阶段load-next-hero

     game.countHeroesAndVillains();

4.9 发射英雄

     game.mouseOnCurrentHero()

     使用以下三个阶段实现发射英雄:

  • wait-for-firing:游戏画面平移至弹弓,等待鼠标单击并拖动英雄,然后切换至firing阶段
  • firing:游戏随着鼠标移动英雄,直到鼠标按键松开,此时以特定的速度将英雄抛射出去,并切换到fired阶段。抛射的速度基于英雄与弹弓的距离。
  • fired:游戏画面跟随英雄移动,直至英雄静止下来或者飞出关卡边界之外,然后游戏将英雄从游戏世界中移除,并切换至load-next-hero阶段。

4.10 结束关卡

     显示关卡结束画面

4.11 碰撞损坏

     添加listener事件

4.12 绘制弹弓橡胶带

     game.drawSlingshotBand()

4.13 切换关卡

     重新开始关卡和开始下一个关卡的按钮

4.14 添加声音

     首先,添加一些音效,如弹弓被释放、英雄或坏蛋弹跳、障碍物被摧毁的音效。

     小技巧:在http://www.ccMixter.com中可以为自己的游戏找到一些免费又优质的音乐

     设置断裂和反弹的音效

      碰撞时播放弹跳声:listener.PostSolve  

      别摧毁时播放破坏声:  

      被发射时播放弹弓释放的声音 

      添加背景音乐:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Froot Wars</title>
    <script src="js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="js/Box2dWeb-2.1.a.3.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="js/game.js" type="text/javascript" charset="utf-8"></script>
    <link rel="stylesheet" href="style.css" type="text/css" media="screen" charset="utf-8">
  </head>
  <body>
    <div id="gamecontainer">
      <canvas id="gamecanvas" width="640" height="480" class="gamelayer"></canvas>
      <div id="scorescreen" class="gamelayer">
        <img id="togglemusic" src="images/icons/sound.png" οnclick="game.toggleBackgroundMusic()">
        <img src="images/icons/prev.png" οnclick="game.restartLevel();">
        <span id="score">Score: 0</span>
      </div>
      <div id="gamestartscreen" class="gamelayer">
        <img src="images/icons/play.png" alt="Play Game" οnclick="game.showLevelScreen();"><br>
        <img src="images/icons/settings.png" alt="Settings">
      </div>
      <div id="levelselectscreen" class="gamelayer">
      </div>
      <div id="loadingscreen" class="gamelayer">
        <div id="loadingmessage"></div>
      </div>
      <div id="endingscreen" class="gamelayer">
        <div>
          <p id="endingmessage"> The Level Is Over Message<p>
          <p id="playcurrentlevel" οnclick="game.restartLevel();"><img src="images/icons/prev.png" >Replay Current Level</p>
          <p id="playnextlevel" οnclick="game.startNextLevel();"><img src="images/icons/next.png"> Play Next Level </p>
          <p id="showLevelScreen" οnclick="game.showLevelScreen();"><img src="images/icons/return.png"> Return to Level Screen</p>
        </div>
      </div>
    </div>
    <canvas id="debugcanvas" width="1000" height="480" style="border:1px solid block;"></canvas>
  </body>
</html>
style.css

#gamecontainer {
	width: 640px;
	height: 480px;
	background: url(images/splashscreen.png);
	border: 1px solid block;
}
.gamelayer {
	width: 640px;
	height: 480px;
	position: absolute;
	display: none;
}
/* 开始菜单画面 */
#gamestartscreen {
	padding-top:250px;
	text-align:center;
}
#gamestartscreen img{
	margin:10px;
	cursor:pointer;
}
/* 关卡选择画面 */
#levelselectscreen {
	padding-top: 150px;
	padding-left: 50px;
}
#levelselectscreen input {
	margin: 20px;
	cursor: pointer;
	background: url(images/icons/level.png) no-repeat;
	color: yellow;
	font-size: 20px;
	width: 64px;
	height: 64px;
	border: 0;
}
/* 加载画面 */
#loadingscreen {
	background: rgba(100,100,100,0.3);
}
#loadingmessage {
	margin-top:400px;
	text-align:center;
	height: 48px;
	color:white;
	background:url(images/loader.gif) no-repeat center;
	font:12px Arial;
}
/* 计分板 */
#scorescreen {
	height: 60px;
	font: 32px Comic Sans MS;
	text-shadow: 0 0 2px #000;
	color: white;
}
#scorescreen img{
	opacity: 0.6;
	top: 10px;
	position: relative;
	padding-left: 10px;
	cursor: pointer;
}
#scorescreen #score {
	position: absolute;
	top: 5px;
	right: 20px;
}
/* 结束画面 */
endingscreen {
	text-align: center;
}
#endingscreen div {
	height: 430px;
	padding-top: 50px;
	border: 1px;
	background: rgba(1,1,1,0.5);
	text-align: left;
	padding-left: 100px;
}
#endingscreen p {
	font: 20px Comic Sans MS;
	text-shadow: 0 0 2px #000;
	color: white;
}
#endingscreen p img {
	top: 10px;
	position: relative;
	cursor: pointer;
}
#endingscreen #endingmessage {
	font: 32px Comic Sans MS;
	text-shadow: 0 0 2px #000;
	color: white;
}

game.js

// Declare all the commonly used objects as variables for convenience
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;

// 建立requestAnimationFrame和cancelAnimationFrame以在游戏代码中使用
(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var x=0; x<vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
           window[vendors[x] + 'CancelAnimationFrame'];
    }
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16-(currTime-lastTime));
            var id = window.setTimeout(function() { 
                callback(currTime + timeToCall);},
                timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }
    if (!window.cancelAnimationFrame){
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
    }
}());
//
var game = {
    //开始初始化对象,预加载资源,并显示开始画面
    init: function(){
        // 初始化对象
        levels.init();
        loader.init();
        mouse.init();

        //加载所有的音效及背景音乐
        //由Gurdonark创作的"Kindergarten"
        game.backgroundMusic = loader.loadSound('audio/gurdonark-kindergarten');
        game.slingshotReleaseSound = loader.loadSound('audio/released');
        game.bounceSound = loader.loadSound('audio/bounce');
        game.breakSound = {
            "glass": loader.loadSound('audio/glassbreak'),
            "wood": loader.loadSound('audio/woodbreak')
        };

        //隐藏所有的游戏图层,显示开始画面
        $('.gamelayer').hide();
        $('#gamestartscreen').show();
        //获取游戏画布及其绘图环境的引用
        game.canvas = $('#gamecanvas')[0];
        game.context = game.canvas.getContext('2d');
    },
    showLevelScreen: function(){
        $('.gamelayer').hide();
        $('#levelselectscreen').show('slow');
    },
    //控制背景音乐的方法
    startBackgroundMusic: function() {
        var toggleImage = $("#togglemusic")[0];
        game.backgroundMusic.play();
        toggleImage.src = "images/icons/sound.png";
    },
    stopBackgroundMusic: function() {
        var toggleImage = $("#togglemusic")[0];
        toggleImage.src = "images/icons/nosound.png";
        game.backgroundMusic.pause();
        game.backgroundMusic.currentTime = 0;
    },
    toggleBackgroundMusic: function() {
        var toggleImage = $("#togglemusic")[0];
        if (game.backgroundMusic.paused) {
            game.backgroundMusic.play();
            toggleImage.src = "images/icons/sound.png";
        } else {
            toggleImage.src = "images/icons/nosound.png";
            game.backgroundMusic.pause();
        }
    },

    // 游戏阶段
    mode: "intro", //游戏状态(intro,waiting for firing, firing, fired)
    // 弹弓的x和y坐标
    slingshotX: 140,
    slingshotY: 280,
    start: function() {
        //隐藏其他所有的图层
        $('.gamelayer').hide();
        //显示游戏画布和得分
        $('#gamecanvas').show();
        $('#scorescreen').show();

        game.startBackgroundMusic();

        game.mode = "intro";
        game.offsetLeft = 0; //记录游戏画面在关卡中平移的距离
        game.ended = false;
        game.animationFrame = window.requestAnimationFrame(game.animate, game.canvas);
    },
    // 画面最大平移速度,单位为像素每帧
    maxSpeed: 3,
    // 画面最大和最小平移范围
    minOffset: 0,
    maxOffset: 300,
    // 画面当前平移位置
    offsetLeft: 0,
    // 游戏得分
    score: 0,
    // 画面中心移动到newCenter
    panTo: function(newCenter) {
        if (Math.abs(newCenter - game.offsetLeft - game.canvas.width/4) > 0
            && game.offsetLeft <= game.maxOffset && game.offsetLeft >= game.minOffset) {
            var deltaX = Math.round((newCenter - game.offsetLeft - game.canvas.width/4)/2);
            if (deltaX && Math.abs(deltaX) > game.maxSpeed) {
                deltaX = game.maxSpeed*Math.abs(deltaX)/(deltaX);
            }
            game.offsetLeft += deltaX;
        } else {
            return true;
        }
        if (game.offsetLeft < game.minOffset) {
            game.offsetLeft = game.minOffset;
            return true;
        } else if (game.offsetLeft > game.maxOffset) {
            game.offsetLeft = game.maxOffset;
            return true;
        }
        return false;
    },
    countHeroesAndVillains:function(){
        game.heroes = [];
        game.villains = [];
        for (var body = box2d.world.GetBodyList(); body; body = body.GetNext()) {
            var entity = body.GetUserData();
            if(entity){
                if(entity.type == "hero"){              
                    game.heroes.push(body);         
                } else if (entity.type =="villain"){
                    game.villains.push(body);
                }
            }
        }
    },
    //鼠标是否放置在当前英雄上
    mouseOnCurrentHero:function(){
        if(!game.currentHero){
            return false;
        }
        var position = game.currentHero.GetPosition();
        var distanceSquared = Math.pow(position.x*box2d.scale - mouse.x-game.offsetLeft,2) + Math.pow(position.y*box2d.scale-mouse.y,2);
        var radiusSquared = Math.pow(game.currentHero.GetUserData().radius,2);      
        return (distanceSquared<= radiusSquared);   
    },
    showEndingScreen: function(){
        game.stopBackgroundMusic();
        if (game.mode == "level-success"){
            if (game.currentLevel.number < levels.data.length-1){
                $('#endingmessage').html('Level Complete. Well Done!!!');
                $('#playnextlevel').show();
            } else {
                $('#endingmessage').html('All Levels Complete. Well Done!!!');
                $('#playnextlevel').show();
            }
        } else if (game.mode == "level-failure"){
            $('#endingmessage').html('Failed. Play Again?');
            $('#playnextlevel').show();
        } 
        $('#endingscreen').show();
    },
    handlePanning: function() {
        if (game.mode == "intro"){
            if (game.panTo(700)) {
                game.mode = "load-next-hero";
            }
        }
        if (game.mode == "load-next-hero"){
            game.countHeroesAndVillains();
            // 检查是否有坏蛋还活着,如果没有,结束关卡
            if (game.villains.length == 0){
                game.mode = "level-success";
                return;
            }
            // 检查是否还有可装填英雄,如果没有,结束关卡
            if (game.heroes.length == 0){
                game.mode = "level-failure";
                return;
            }
            // 装填英雄
            if (!game.currentHero){
                game.currentHero = game.heroes[game.heroes.length-1];
                game.currentHero.SetPosition({x:180/box2d.scale,y:200/box2d.scale});
                game.currentHero.SetLinearVelocity({x:0,y:0});
                game.currentHero.SetAngularVelocity(0);
                game.currentHero.SetAwake(true);    
            } else {
                //等待英雄结束弹跳并进入休眠
                game.panTo(game.slingshotX);
                if (!game.currentHero.IsAwake()){
                    game.mode = "wait-for-firing";
                }
            }
        }
        if (game.mode == "wait-for-firing"){
            if (mouse.dragging) {
                if (game.mouseOnCurrentHero()){
                    game.mode = "firing";
                } else {
                    game.panTo(mouse.x + game.offsetLeft);
                }
            } else {
                game.panTo(game.slingshotX);
            }
        }
        if (game.mode == "firing"){
            if (mouse.down) {
               game.panTo(game.slingshotX); 
               game.currentHero.SetPosition({x:(mouse.x + game.offsetLeft)/box2d.scale,
                 y:mouse.y/box2d.scale});
            } else {
                game.mode = "fired";
                game.slingshotReleaseSound.play();
                var impulseScaleFactor = 0.75;
                var impulse = new b2Vec2((game.slingshotX+35-mouse.x-game.offsetLeft)*
                    impulseScaleFactor, (game.slingshotY+25-mouse.y)*impulseScaleFactor);
                game.currentHero.ApplyImpulse(impulse, game.currentHero.GetWorldCenter());
            }
        }
        if (game.mode == "fired"){
            // 跟随当前英雄移动画面
            var heroX = game.currentHero.GetPosition().x*box2d.scale;
            game.panTo(heroX);
            //直到该英雄停止移动或移除边界
            if (!game.currentHero.IsAwake() || heroX<0 
                || heroX>game.currentLevel.foregroundImage.width){
                //然后删除旧的英雄
                box2d.world.DestroyBody(game.currentHero);
                game.currentHero = undefined;
                //加载下一个英雄
                game.mode = "load-next-hero";
            }
        }
        if (game.mode == "level-success" || game.mode == "level-failure"){
            if (game.panTo(0)) {
                game.ended = true;
                game.showEndingScreen();
            }
        }
    },
    animate: function() {
        //移动背景
        game.handlePanning();
        //使角色运动
        var currentTime = new Date().getTime();
        var timeStep;
        if (game.lastUpdateTime){
            timeStep = (currentTime - game.lastUpdateTime)/1000;
            box2d.step(timeStep);
        }
        game.lastUpdateTime = currentTime;

        //使用视差滚动绘制背景,背景图像和前景图像以不同的速度移动,
        //这个差异会造成一种错觉:背景上的云彩离我们更远
        game.context.drawImage(game.currentLevel.backgroundImage, 
            game.offsetLeft/4, 0, 640, 480, 0, 0, 640, 480);
        game.context.drawImage(game.currentLevel.foregroundImage, 
            game.offsetLeft, 0, 640, 480, 0, 0, 640, 480);
        // 绘制弹弓
        game.context.drawImage(game.slingshotImage, game.slingshotX- 
            game.offsetLeft, game.slingshotY);
        // 绘制所有的物体
        game.drawAllBodies();
        //发射英雄时绘制橡胶带
        if (game.mode == "firing"){
            game.drawSlingshotBand();
        }
        // 再次绘制弹弓的外侧支架
        game.context.drawImage(game.slingshotFrontImage, game.slingshotX-
            game.offsetLeft, game.slingshotY);
        if (!game.ended) {
            game.animationFrame = window.requestAnimationFrame(game.animate, game.canvas);
        }
    },
    //绘制弹弓橡胶带
    drawSlingshotBand: function() {
        game.context.strokeStyle = "rgb(68, 31, 11)"; //暗棕色
        game.context.lineWidth = 6; //
        //用英雄被拖拽的角度和半径计算英雄的末端,相对于英雄的中心
        var radius = game.currentHero.GetUserData().radius;
        var heroX = game.currentHero.GetPosition().x * box2d.scale;
        var heroY = game.currentHero.GetPosition().y * box2d.scale;
        var angle = Math.atan2(game.slingshotY+25-heroY, game.slingshotX+50-heroX);

        var heroFarEdgeX = heroX - radius * Math.cos(angle);
        var heroFarEdgeY = heroY - radius * Math.cos(angle);
        game.context.beginPath();
        //从弹弓顶端开始绘制(背面)
        game.context.moveTo(game.slingshotX+50-game.offsetLeft, game.slingshotY+25);
        //画到英雄的中心
        game.context.lineTo(heroX-game.offsetLeft, heroY);
        game.context.stroke();
        //再次绘制英雄
        entities.draw(game.currentHero.GetUserData(), game.currentHero.GetPosition(),
            game.currentHero.GetAngle());
        game.context.beginPath();
        //移动到英雄离弹弓顶部最远的边缘
        game.context.moveTo(heroFarEdgeX-game.offsetLeft, heroFarEdgeY+25);
        //将线画回弹弓(正面)
        game.context.lineTo(game.slingshotX-game.offsetLeft+10,game.slingshotY+30);
        game.context.stroke();
    },
    //重新开始和下一关
    restartLevel: function() {
        window.cancelAnimationFrame(game.animationFrame);
        game.lastUpdateTime = undefined;
        levels.load(game.currentLevel.number);
    },
    startNextLevel: function() {
        window.cancelAnimationFrame(game.animationFrame);
        game.lastUpdateTime = undefined;
        levels.load(game.currentLevel.number+1);
    },
    drawAllBodies: function() {
        box2d.world.DrawDebugData();
        // 遍历所有的物体,并在游戏canvas上绘制出来
        for (var body=box2d.world.GetBodyList(); body; body=body.GetNext()){
            var entity = body.GetUserData();
            if (entity){
                //判断生命值,是否要显示
                var entityX = body.GetPosition().x * box2d.scale;
                if (entityX<0 || entityX>game.currentLevel.foregroundImage.width ||
                    (entity.health && entity.health<0)) {
                    box2d.world.DestroyBody(body);
                    if (entity.type == "villain"){
                        game.score += entity.calories;
                        $('#score').html('Score: ' + game.score);
                    }
                    if (entity.breakSound){
                        entity.breakSound.play();
                    }
                } else {
                    entities.draw(entity, body.GetPosition(), body.GetAngle());
                }
            }
        }
    },
}
$(window).load(function() {
    game.init();
});
// 关卡
var levels = {
    //关卡数据
    data: [
    {   //第一关
        foreground: 'desert-foreground',
        background: 'clouds-background',
        entities:[
            {type:"ground", name:"dirt", x:500,y:440,width:1000,height:20,isStatic:true},
            {type:"ground", name:"wood", x:185,y:390,width:30,height:80,isStatic:true},

            {type:"block", name:"wood", x:520,y:380,angle:90,width:100,height:25},
            {type:"block", name:"glass", x:520,y:280,angle:90,width:100,height:25},                             
            {type:"villain", name:"burger",x:520,y:205,calories:590},

            {type:"block", name:"wood", x:620,y:380,angle:90,width:100,height:25},
            {type:"block", name:"glass", x:620,y:280,angle:90,width:100,height:25},                             
            {type:"villain", name:"fries", x:620,y:205,calories:420},               

            {type:"hero", name:"orange",x:80,y:405},
            {type:"hero", name:"apple",x:140,y:405},
        ]
    },
    {   //第二关
        foreground: 'desert-foreground',
        background: 'clouds-background',
        entities:[
                {type:"ground", name:"dirt", x:500,y:440,width:1000,height:20,isStatic:true},
                {type:"ground", name:"wood", x:185,y:390,width:30,height:80,isStatic:true},
    
                {type:"block", name:"wood", x:820,y:380,angle:90,width:100,height:25},
                {type:"block", name:"wood", x:720,y:380,angle:90,width:100,height:25},
                {type:"block", name:"wood", x:620,y:380,angle:90,width:100,height:25},
                {type:"block", name:"glass", x:670,y:317.5,width:100,height:25},
                {type:"block", name:"glass", x:770,y:317.5,width:100,height:25},                

                {type:"block", name:"glass", x:670,y:255,angle:90,width:100,height:25},
                {type:"block", name:"glass", x:770,y:255,angle:90,width:100,height:25},
                {type:"block", name:"wood", x:720,y:192.5,width:100,height:25}, 

                {type:"villain", name:"burger",x:715,y:155,calories:590},
                {type:"villain", name:"fries",x:670,y:405,calories:420},
                {type:"villain", name:"sodacan",x:765,y:400,calories:150},

                {type:"hero", name:"strawberry",x:30,y:415},
                {type:"hero", name:"orange",x:80,y:405},
                {type:"hero", name:"apple",x:140,y:405},
        ]
    }
    ],
    // 初始化关卡选择画面
    init: function() {
        var html = "";
        for (var i=0; i<levels.data.length; i++) {
            var level = levels.data[i];
            html += '<input type="button" value="' + (i+1) + '">';
        };
        $('#levelselectscreen').html(html);
        //单击按钮时加载关卡
        $('#levelselectscreen input').click(function(){
            levels.load(this.value - 1);
            $('#levelselectscreen').hide();
        });
    },
    // 为某一关加载所有的数据和图像
    load: function(number){
        box2d.init();//
        //声明一个新的当前关卡对象
        game.currentLevel = {number:number, hero:[]};
        game.score = 0;
        $('#score').html('Score: ' + game.score);
        game.currentHero = undefined;
        var level = levels.data[number];
        //加载背景、前景和弹弓图像
        game.currentLevel.backgroundImage = loader.loadImage("images/backgrounds/" + level.background + ".png");
        game.currentLevel.foregroundImage = loader.loadImage("images/backgrounds/" + level.foreground + ".png");
        game.slingshotImage = loader.loadImage("images/slingshot.png");
        game.slingshotFrontImage = loader.loadImage("images/slingshot-front.png");
        // 加载所有的物体
        for (var i=level.entities.length-1; i>=0; i--){
            var entity = level.entities[i];
            entities.create(entity);
        };
        // 一旦所有的图像加载完成,就调用game.start()函数
        if (loader.loaded){
            game.start();
        } else {
            loader.onload = game.start;
        }
    }
}
//
var entities = {
    //定义物体类型(玻璃、木材和地面),以及英雄和坏蛋(橙子、苹果和汉堡)
    definitions:{
        "glass":{
            fullHealth:100,
            density:2.4,
            friction:0.4,
            restitution:0.15,
        },
        "wood":{
            fullHealth:500,
            density:0.7,
            friction:0.4,
            restitution:0.4,
        },
        "dirt":{
            density:3.0,
            friction:1.5,
            restitution:0.2,    
        },
        "burger":{
            shape:"circle",
            fullHealth:40,
            radius:25,
            density:1,
            friction:0.5,
            restitution:0.4,    
        },
        "sodacan":{
            shape:"rectangle",
            fullHealth:80,
            width:40,
            height:60,
            density:1,
            friction:0.5,
            restitution:0.7,    
        },
        "fries":{
            shape:"rectangle",
            fullHealth:50,
            width:40,
            height:50,
            density:1,
            friction:0.5,
            restitution:0.6,    
        },
        "apple":{
            shape:"circle",
            radius:25,
            density:1.5,
            friction:0.5,
            restitution:0.4,    
        },
        "orange":{
            shape:"circle",
            radius:25,
            density:1.5,
            friction:0.5,
            restitution:0.4,    
        },
        "strawberry":{
            shape:"circle",
            radius:15,
            density:2.0,
            friction:0.5,
            restitution:0.4,    
        },
    },
       //根据物体创建Box2D物体,并将其加入世界
    create:function(entity){
        var definition = entities.definitions[entity.name];
        if (!definition){
            console.log("undefined entity name", entity.name);
            return;
        }
        //console.log(entity);
        switch (entity.type){
            case "block":  //简单的矩形
              entity.health = definition.fullHealth;
              entity.fullHealth = definition.fullHealth;
              entity.shape = "rectangle";
              entity.sprite = loader.loadImage("images/entities/"
                +entity.name+".png");
              entity.breakSound = game.breakSound[entity.name];
              box2d.createRectangle(entity, definition);
              break;
            case "ground":  //简单的矩形
              entity.shape = "rectangle";
              box2d.createRectangle(entity, definition);
              break;
            case "hero":
            case "villain":
              entity.health = definition.fullHealth;
              entity.fullHealth = definition.fullHealth;
              entity.sprite = loader.loadImage("images/entities/"
                +entity.name+".png");
              entity.shape = definition.shape;
              entity.bounceSound = game.bounceSound;
              if (definition.shape == "circle"){
                entity.radius = definition.radius;
                box2d.createCircle(entity, definition);
              } else if (definition.shape == "rectangle") {
                entity.width = definition.width;
                entity.height = definition.height;
                box2d.createRectangle(entity, definition);
              }
              break;
            default:
              console.log("Undefined entity type", entity.type);
              break;
        }
    },
    //以物体、物体的位置和角度为参数,在游戏画面中绘制物体
    draw: function(entity, position, angle){
        game.context.translate(position.x*box2d.scale-game.offsetLeft,
            position.y*box2d.scale);
        game.context.rotate(angle);
        switch(entity.type){
            case "block":
              game.context.drawImage(entity.sprite,0,0,entity.sprite.width,
                 entity.sprite.height, -entity.width/2-1, -entity.height/2-1,
                 entity.width+2, entity.height+2);
              break;
            case "villain":
            case "hero":
              if (entity.shape == "circle"){
                game.context.drawImage(entity.sprite,0,0,entity.sprite.width,
                 entity.sprite.height, -entity.radius-1, -entity.radius-1,
                 entity.radius*2+2, entity.radius*2+2);
              } else if (entity.shape == "rectangle") {
                game.context.drawImage(entity.sprite,0,0,entity.sprite.width,
                 entity.sprite.height, -entity.width/2-1, -entity.height/2-1,
                 entity.width+2, entity.height+2);
              }
              break;
            case "ground":
              break;
        }
        game.context.rotate(-angle);
        game.context.translate(-position.x*box2d.scale + game.offsetLeft, -position.y*box2d.scale);
    },
}
//创建box2d对象
var box2d = {
    scale: 30,
    init: function() {
        //创建Box2D世界,大部分物理运算将在其中完成
        var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
        var allowSleep = true;  //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
        box2d.world = new b2World(gravity, allowSleep);
        //设置调试绘图
        var debugContext = document.getElementById('debugcanvas').getContext('2d');
        var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite(debugContext);
        debugDraw.SetDrawScale(box2d.scale);
        debugDraw.SetFillAlpha(0.3);
        debugDraw.SetLineThickness(1.0);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);    
        box2d.world.SetDebugDraw(debugDraw);
        //监听事件
        var listener = new Box2D.Dynamics.b2ContactListener;
        //参数为接触和冲击力(法向和切向冲击力)
        listener.PostSolve = function(contact, impulse){
            var body1 = contact.GetFixtureA().GetBody();
            var body2 = contact.GetFixtureB().GetBody();
            var entity1 = body1.GetUserData();
            var entity2 = body2.GetUserData();
            var impulseAlongNormal = Math.abs(impulse.normalImpulses[0]);
            // 监听器被调用得有些太频繁了,滤去非常小的冲击
            // 尝试不同的值后,5似乎比较好
            if (impulseAlongNormal > 5) {
                //如果对象有生命值,用冲击值消弱生命值
                if (entity1.health) {
                    entity1.health -= impulseAlongNormal;
                }
                if (entity2.health) {
                    entity2.health -= impulseAlongNormal;
                }
                //如果物体具有弹跳音,则播放它
                if (entity1.bounceSound){
                    entity1.bounceSound.play();
                }
                if (entity2.bounceSound){
                    entity2.bounceSound.play();
                }
            }
        };
        box2d.world.SetContactListener(listener);
    },
    step: function(timeStep){
        if (timeStep > 2/60){
            timeStep = 2/60;
        }
        box2d.world.Step(timeStep, 8, 3);
    },
    createRectangle:function(entity, definition){
        var bodyDef = new b2BodyDef;
        if (entity.isStatic) {
            bodyDef.type = b2Body.b2_staticBody;
        } else {
            bodyDef.type = b2Body.b2_dynamicBody;
        }
        bodyDef.position.x = entity.x/box2d.scale;
        bodyDef.position.y = entity.y/box2d.scale;
        if (entity.angle){
            bodyDef.angle = Math.PI*entity.angle/180;
        }
        var fixtureDef = new b2FixtureDef;
        fixtureDef.density = definition.density;
        fixtureDef.friction = definition.friction;
        fixtureDef.restitution = definition.restitution;
        fixtureDef.shape = new b2PolygonShape;  //多边形
        fixtureDef.shape.SetAsBox(entity.width/2/box2d.scale, 
           entity.height/2/box2d.scale); //60宽,100高

        var body = box2d.world.CreateBody(bodyDef);
        body.SetUserData(entity);
        var fixture = body.CreateFixture(fixtureDef);
        return body;
    },
    createCircle:function(entity, definition){
        var bodyDef = new b2BodyDef;
        if (entity.isStatic) {
            bodyDef.type = b2Body.b2_staticBody;
        } else {
            bodyDef.type = b2Body.b2_dynamicBody;
        }
        bodyDef.position.x = entity.x/box2d.scale;
        bodyDef.position.y = entity.y/box2d.scale;
        if (entity.angle){
            bodyDef.angle = Math.PI*entity.angle/180;
        }
        var fixtureDef = new b2FixtureDef;
        fixtureDef.density = definition.density;
        fixtureDef.friction = definition.friction;
        fixtureDef.restitution = definition.restitution;
        fixtureDef.shape = new b2CircleShape(entity.radius/box2d.scale);

        var body = box2d.world.CreateBody(bodyDef);
        body.SetUserData(entity);
        var fixture = body.CreateFixture(fixtureDef);
        return body;
    },
}

//图像/声音资源加载器loader
var loader = {
    loaded: true,
    loadedCount: 0, //已加载的资源数
    totalCount: 0,  //需要被加载的资源总数

    init: function(){
        //检查浏览器支持的声音格式;
        var mp3Support, oggSupport;
        var audio = document.createElement('audio');
        if (audio.canPlayType){
            // 
            mp3Support = "" != audio.canPlayType('audio/mpeg');
            oggSupport = "" != audio.canPlayType('audio/ogg; codecs="vorbis"');
        } else {
            // audio标签不被支持
            mp3Support = false;
            oggSupport = false;
        }
        // 都不支持,就将soundFileExtn设置为undefined
        loader.soundFileExtn = oggSupport?".ogg":mp3Support?".mp3":undefined;
        console.log(loader.soundFileExtn);
    },
    loadImage: function(url){
        this.totalCount++;
        this.loaded = false;
        $('#loadingscreen').show();
        var image = new Image();
        image.src = url;
        image.onload = loader.itemLoaded;
        return image;
    },
    soundFileExtn: ".ogg",
    loadSound: function(url){
        this.totalCount++;
        this.loaded = false;
        $('#loadingscreen').show();
        var audio = new Audio();
        audio.src = url + loader.soundFileExtn;
        audio.addEventListener("canplaythrough", loader.itemLoaded, false);
        return audio;
    },
    itemLoaded: function() {
        loader.loadedCount++;
        $('#loadingmessage').html('Loaded ' + loader.loadedCount + ' of ' + loader.totalCount);
        if (loader.loadedCount === loader.totalCount) {
            // loader完成了资源加载
            loader.loaded = true;
            $('#loadingscreen').hide();
            if (loader.onload){
                loader.onload();
                loader.onload = undefined;
            }
        }
    }
}
//处理鼠标事件
var mouse = {
    x: 0,
    y: 0,
    down: false,
    init: function() {
        $('#gamecanvas').mousemove(mouse.mousemovehandler);
        $('#gamecanvas').mousedown(mouse.mousedownhandler);
        $('#gamecanvas').mouseup(mouse.mouseuphandler);
        $('#gamecanvas').mouseout(mouse.mouseuphandler);
    },
    mousemovehandler: function(ev) {
        var offset = $('#gamecanvas').offset();
        mouse.x = ev.pageX - offset.left;
        mouse.y = ev.pageY - offset.top;
        if (mouse.down) {
            mouse.dragging = true;
        }
    },
    mousedownhandler: function(ev) {
        mouse.down = true;
        mouse.downX = mouse.x;
        mouse.downY = mouse.y;
        ev.originalEvent.preventDefault();
    },
    mouseuphandler: function(ev) {
        mouse.down = false;
        mouse.dragging = false;
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值