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

HTML5 前端物理引擎:游戏开发必备 本文旨在全面介绍HTML5前端物理引擎的技术原理和实现方法,帮助开发者理解如何在网页中实现真实的物理效果。我们将涵盖从基础概念到实际应用的完整知识体系。核心概念与联系:介绍物理引擎的基本原理核心算法原理:解析物理引擎的关键算法项目实战:通过实际案例展示物理引擎的应用工具和资源推荐:分享实用的开发工具未来发展趋势:展望物理引擎的发展方向物理引擎:模拟物理规律(如重力、碰撞、摩擦等)的软件组件刚体:形状和大小不会因外力作用而改变的理想化物体碰撞检测:判断两个或多个物体是否发生接触的过程。 阅读详情

   首先向关卡中添加物体,对这些物体进行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" onclick="game.toggleBackgroundMusic()">
        <img src="images/icons/prev.png" onclick="game.restartLevel();">
        <span id="score">Score: 0</span>
      </div>
      <div id="gamestartscreen" class="gamelayer">
        <img src="images/icons/play.png" alt="Play Game" onclick="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" onclick="game.restartLevel();"><img src="images/icons/prev.png" >Replay Current Level</p>
          <p id="playnextlevel" onclick="game.startNextLevel();"><img src="images/icons/next.png"> Play Next Level </p>
          <p id="showLevelScreen" onclick="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;
    }
}


七巧板平台html5游戏开发之初识物理引擎 七巧板平台游戏开发之初识物理引擎   七巧板平台是用于Web App、中轻度HTML5游戏及富媒体类应用开发,集开发、运营于一体的平台。它基于HTML5技术开发,具有进入门槛极低、开发极简单、跨平台的优势,适合广大希望从事上述应用开发的初学者及中、高级开发人员使用。 阅读详情

相关推荐

HTML5游戏开发进阶指南 中文pdf扫描版​

HTML5游戏开发进阶指南介绍了HTML5游戏开发的一般过程和技巧。全书共分12章,第1章介绍了本书相关的HTML5的诸多新特性,包括在canvas上绘图、播放声音等,另外还引入了子画面页的概念;第2~4章利用Box2D物理引擎开发了一款非常类似于《愤怒的小鸟》的游戏,全面介绍了物理引擎的概念,以及在游戏中使用物理引擎的方法,这一部分还引入了视差滚动技巧,以实现某种伪3D效果;第5~10章开发了一...

randyjin6的博客 1310

HTML5游戏开发进阶 3 :物理引擎基础

Box2D最初是有Erin Catto用C++编写的。很多流行的物理仿真类游戏都使用了该引擎,包含《愤怒的小鸟》,该引擎已经被转化为几种不同语言的版本,包括Java,ActionScript,C#和JavaScript。JS版本又被称为Box2dWeb。     http://code.google.com/p/box2dweb 3.1 Box2D基础      Box2D使用一些基本对象来

Just for fun的专栏 1110

HTML5游戏开发进阶指南与源码实践

HTML5 Canvas是一个基于Web的API,允许我们在网页中绘制图形。它通过JavaScript与HTML的<canvas>元素进行交互,提供了丰富的图形绘制功能。Canvas非常适合用于制作动态图形,例如游戏、图表、动画等。

weixin_35752122的博客 363

【Canvas】HTML5游戏开发的基本流程+P2.js物理引擎实战开发

《HTML5游戏开发的基本流程》 * 1. HTML5的简述 * 2. HTML5游戏开发所需的环境与工具 * 2.1. 开发环境 * 2.1.1. 浏览器 * 2.1.2. 开发语言 * 2.1.3. 开发平台 * 2.2. 开发工具 * 2.2.1. 代码编辑器 * 2.2.2. 版

林洪炳的日常技术积累 1万+

HTML5游戏开发实战:跳跃游戏源码剖析

HTML5作为现代网页技术的核心,它的出现彻底改变了网页游戏的开发模式。HTML5不仅提供了更加丰富的多媒体和图形渲染能力,还引入了<canvas>元素,为2D游戏的开发打开了新天地。利用HTML5和相关技术,开发者能够创作出既可以在浏览器中运行,也能兼容移动设备的跨平台游戏。

weixin_42629522的博客 1228

Box2dWeb-2.1.a.3.min.js

一个js版本的box2d引擎,包含重力系统和碰撞检测,可以去github上下载,放在此处只是为了方便,侵删~

html5物理引擎对比,主流物理引擎对比

End of theyear isproper time to gather some statictics andsummarize what PhysX SDK has archieved in past 4 years. In thisarticle will try to compare it with other physics engines presentedon the mark...

weixin_33737110的博客 664

h5_canvas2D简单物理引擎和简单开发.zip

h5_canvas2D简单物理引擎和简单开发,h5 canvas 简单的物理引擎 实现了简单游戏动画 写了几个小demo

Html5 Canvas+JavaScript物理引擎实例

Html5 Canvas+JavaScript物理引擎实例  canvas绘制蜘蛛和js物理引擎模拟行为的实现                  Document  http://sandbox.runjs.cn/uploads/rs/146/73fi1rab/objects.js">  http://sandbox.runjs.cn/uploa

2128

H5动画物理引擎

H5动画 物理引擎

基于js与canvas的物理引擎

基于canvas和javascript的物理引擎

HTML5游戏开发进阶指南

《HTML5游戏开发进阶指南》 基本信息 作者: (印)香卡(Shankar,A.R.) 译者: 谢光磊 出版社:电子工业出版社 ISBN:9787121212260 上架时间:2013-8-26 出版日期:2013 年9月 开本:16开 页码:412 版次:1-1 所属分类:计算机 更多关于 》》》《HTML5游戏开发进阶指南》 内容简介 计算机书籍   《ht...

weixin_34384681的博客 118

html5 canvas javascript游戏开发物理引擎box2d-js_0.1.0

这是一个非常好的javascript物理引擎,用于html5+canvas游戏开发。这个版本是由开发flash游戏的Box2DFlashAS3 1.4.3.1物理引擎转换而来的。用这个引擎可以用html5的canvas开发出各种优秀的物理引擎类游戏,完全可以与flash开发的游戏相媲美。绝对是好东西。

ImpactJS HTML5 游戏开发(一)

原文:zh.annas-archive.org/md5/441DA316F62E4350E9115A286AB618B0 译者:飞龙 协议:CC BY-NC-SA 4.0 前言 今天你在阅读本书,是因为你想制作视频游戏。你希望构建自己的视频游戏,可以在人们的浏览器以及他们的智能手机和平板电脑上运行。所有这些今天都是可能的,尽管这并非一直如此。你现在能够相对轻松地构建自己的游戏,原因有两个:HTML5 和 ImpactJS。 HTML5 是我们的全球网络超文本标记语言的最新版本,也是网页的通用语言。HT

龙哥盟 2080

抖音无人直播技术全解析:从原理、实操到合规化思考

直播技术作为内容分发的核心形式,其底层依赖于流媒体协议(如RTMP)与实时音视频编码推流。通过OBS等软件进行场景合成与虚拟摄像头技术,可以实现丰富的画面包装与自动化播放。这项技术的核心价值在于突破人力与时间的限制,实现内容的持续输出与流量获取,在电商带货、品牌展示、陪伴式内容等场景中具有应用潜力。然而,单纯依赖录播循环存在高风险,平台风控系统会通过内容重复性检测与互动行为模型进行识别。因此,更可持续的方向是探索“半无人直播”或自动化辅助工具,例如利用AI数字人驱动或直播切片技术,在合规前提下提升运营效率。

weixin_29895109的博客 508

MATLAB R2022b 安装教程

1. 下载安装先放资源链接,百度网盘太贵了不想续了,换了夸克网盘。1. 下载安装。

qq_51654158的博客 2万+

HTML5 Canvas游戏开发中的物理引擎实现技巧

本文深入探讨了HTML5 Canvas游戏开发中物理引擎的实现技巧,涵盖基础架构设计、精确碰撞检测、高级物理效果模拟及性能优化策略。通过JavaScript代码示例,详细解析了刚体系统、四叉树空间分区、分离轴定理等核心技术,帮助开发者打造高性能的2D游戏物理引擎。

grafana8visual的博客 701

双极性步进电机驱动技术:从H桥电路到精准控制

本文深入解析了双极性步进电机驱动技术的核心,从H桥电路的基础原理讲起,阐述了其作为实现电机正反转和精准控制的关键。文章详细介绍了H桥的工作模式、防共态导通设计,并对比了L298N、DRV8825及TMC2209等主流集成驱动芯片的选型。进一步探讨了通过微步进与电流矢量控制实现平滑、静音运行的高级技术,并提供了基于STM32的实战代码与避坑指南,帮助开发者从理论到实践全面掌握精准运动控制。

tea88的博客 594

30分钟从零上手Phaser:小白也能做的HTML5游戏开发终极指南

Phaser是一个有趣、免费且快速的2D游戏框架,专门用于为桌面和移动web浏览器创建HTML5游戏,支持Canvas和WebGL渲染。作为最受欢迎的HTML5游戏开发框架之一,Phaser让初学者也能轻松入门游戏开发领域。 ## 🎮 为什么选择Phaser框架? Phaser框架为HTML5游戏开发提供了完整的解决方案,无论是简单的休闲游戏还是复杂的角色扮演游戏,都能找到适合的工具和组件。

gitblog_00104的博客 989

智能座舱音频系统集成与调试:从芯片选型到音质优化实战

在智能座舱域控制器架构中,音频系统已从简单的功放加扬声器演进为SoC、数字音频总线、外部DSP与多通道功放协同的复杂子系统。TDM总线的时隙分配、PDM麦克风阵列的信号完整性,以及AEC回声消除算法如何与硬件协同,直接决定多音区体验和语音交互的可靠性。工程实践中,音频调试的关键在于先理顺供电、接地、时钟与数据路由,再针对功放削波、采样率失配、TDM slot错位等典型问题进行系统性排查。从芯片选型到系统级指标验收,只有将音频链路视为整车电子电气架构的一个协同整体,才能真正实现从“响”到“好听”的跨越。本文结

weixin_30162121的博客 364

手把手教你配置MAX96712解串器:从I2C地址分配到PWM帧同步

本文详细解析了MAX96712解串器在自动驾驶视觉链路中的核心配置流程。从I2C地址分配、GMSL2数据流映射到实现多摄像头精准同步的PWM帧同步触发,手把手指导工程师构建高可靠性的图像传输系统,并提供了实用的Linux调试技巧与问题排查思路。

weixin_29297127的博客 923

JavaScript 游戏开发:手把手实现碰撞物理引擎

目录基础结构绘制小球移动小球重构代码碰撞检测边界碰撞向量的基本操作碰撞处理动量守恒定律动能守恒定律非弹性碰撞重力总结 年前我看到合成大西瓜小游戏火了,想到之前从来没有研究过游戏方面的开发,这次就想趁着这个机会看看 JavaScript 游戏开发,从原生角度上如何实现游戏里的物理特性,例如运动、碰撞。虽然之前研究过物理相关的动画库,但是我打算试试不用框架编写一个简单的 JavaScript 物理引擎,实现小球的碰撞效果。 为什么不用现成的游戏库呢?因为我觉得在了解底层的实现原理之后,才能更有效的理解框架上的概

张旭乾 - 全栈工程师/海归 3万+

在Window上安装和配置VTK9.x,并在QT项目中调试VTK是否可用

VTK+MSVC+Qt Creator + CMake配置

qq_42279379的博客 1000
上一篇: HTML5游戏开发进阶 3 :物理引擎基础
下一篇: HTML5游戏开发进阶 5 :创建即时战略游戏世界
CopperDong
博客等级 码龄14年 1205粉丝 466原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值