HTML5游戏开发进阶 2 :创建基本的游戏世界

https://github.com/apress/pro-html5-games

    需要用到的所有关键组件---启动画面、载入画面、预加载器、主菜单、视差滚动、声音、基于Box2D引擎的物理仿真、记分牌。有了这样一个基本框架,你就能够在自己的游戏中反复使用它们。

2.1 基本HTML布局

   包括以下图层:

  • 启动画面:页面加载时显示。
  • 游戏开始画面:包含主菜单,允许玩家开始游戏或进行游戏设置。
  • 加载/进程画面:包含加载进度条,当游戏正在加载资源(如图像和声音文件)时显示。
  • 主画面:实际的游戏画面。
  • 计分板:主画面顶部的小块区域,显示若干个按钮和计分情况。
  • 结束画面:每一关结束时的画面。

2.2 创建启动画面和主菜单

    基本框架(index.html)及添加的图层,定义了id为gamecontainer的div元素作为游戏容器。游戏容器包含了每一个游戏图层(gamelayer class):gamestartscreen(开始菜单)、levelselectscreen(关卡选择界面)、loadingscreen(加载画面)、计分板(scorescreen)、结束画面(endingscreen)和主画面(gamecanvas)。

<!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/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">
        <img src="images/icons/prev.png">
        <span id="score">Score: 0</span>
      </div>
      <div id="gamestartscreen" class="gamelayer">
        <img src="images/icons/play.png" alt="Play Game"><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"><img src="images/icons/prev.png" >Replay Current Level</p>
          <p id="playnextlevel"><img src="images/icons/next.png"> Play Next Level </p>
          <p id="showLevelScreen"><img src="images/icons/return.png"> Return to Level Screen</p>
        </div>
      </div>
    </div>
  </body>
</html>

    style.css,样式表:

  • 定义游戏容器以及其中所有的游戏图层的尺寸为640px X 480px
  • 保证所有的游戏图层以绝对位置布局(它们互相重叠),这样就可以根据需要显示/隐藏和重叠这些图层。默认情况下,这些图层都是隐藏。
  • 将游戏容器的背景设置为启动画面图像,当页面加载时,玩家第一眼看到的就是该图像。
  • 为游戏开始画面添加一些样式,开始画面中有“开始游戏”、“更改设置”等菜单项。

#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;
}
   js/game.js

var game = {
    //开始初始化对象,预加载资源,并显示开始画面
    init: function(){
        //隐藏所有的游戏图层,显示开始画面
        $('.gameplayer').hide();
        $('#gamestartscreen').show();
        //获取游戏画布及其绘图环境的引用
        game.canvas = $('#gamecanvas')[0];
        game.context = game.canvas.getContext('2d');
    },
}

$(window).load(function() {
    game.init();
});

2.3 关卡选择

   当玩家单击PLAY按钮后,游戏应当显示一个关卡选择画面和一系列可玩的关卡。

   创建一个对象来处理关卡。这个对象既包含了关卡中的数据,又提供了一些简单的函数来对关卡进行初始化。

2.4 加载图像

   完成关卡之前,需要实现图形加载器和加载画面。

   设计一个简单的加载画面,它包含一张进度条GIF动画图片和显示已加载图像数目的文字。

2.5 加载关卡

   首先加载游戏的背景、前景和弹弓图像

/* 关卡选择画面 */
#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;
}

2.6 动画

   使用requestAnimationFrame,并在一秒内多次调用绘图和动画代码

   视差滚动是一种利用背景图像移动得比前景图像慢,而产生立体错觉的技术。这项技术说明了一个事实,即远处的物体看上去比近处的移动得快。

2.7 处理鼠标输入

    利用JavaScript中的几个事件来捕捉鼠标输入---mousedown, mouseup和mousemove。为了简单,我们利用jQuery创建一个独立的mouse对象,以处理所有的鼠标事件

     mouse对象的init()方法为鼠标移动、鼠标按下、鼠标松开和鼠标离开画布区域等事件绑定了响应方法

2.8 设置游戏阶段

     将游戏的当前阶段存储到game.mode变量中:

  • intro:关卡刚刚载入,游戏将在整个关卡范围中平移游戏画面,向玩家展现关卡中的所有东西
  • load-next-hero:检查是否有下一个英雄可以装填到弹弓上去,如果有,装填该英雄。如果我们的英雄耗尽了而坏蛋却没有被全部消灭,关卡就结束了。
  • wait-for-firing:将视野移回到弹弓,等待用户发射“英雄”。此时,游戏正在等待用户单击英雄。在这个阶段,用户可以也很有可能用鼠标拖拽画面,查看整个关卡。
  • firing:这个阶段中,用户已经单击了英雄,但还没有释放鼠标按键。此时,游戏正在等待用户拖拽英雄,调整角度和位置并释放英雄。
  • fired:用户释放了鼠标按键并发射英雄之后进入这个阶段。此时,游戏将所有的事情交给物理引擎来处理,用户仅仅在观看。游戏画面会随着发射出的英雄平移,这样用户就可以追踪英雄的轨迹。
js/game.js

// 建立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();

        //隐藏所有的游戏图层,显示开始画面
        $('.gamelayer').hide();
        $('#gamestartscreen').show();
        //获取游戏画布及其绘图环境的引用
        game.canvas = $('#gamecanvas')[0];
        game.context = game.canvas.getContext('2d');
    },
    showLevelScreen: function(){
        $('.gamelayer').hide();
        $('#levelselectscreen').show('slow');
    },
    // 游戏阶段
    mode: "intro", //游戏状态(intro,waiting for firing, firing, fired)
    // 弹弓的x和y坐标
    slingshotX: 140,
    slingshotY: 280,
    start: function() {
        //隐藏其他所有的图层
        $('.gamelayer').hide();
        //显示游戏画布和得分
        $('#gamecanvas').show();
        $('#scorescreen').show();
        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;
    },
    handlePanning: function() {
        if (game.mode == "intro"){
            if (game.panTo(700)) {
                game.mode = "load-next-hero";
            }
        }
        if (game.mode == "wait-for-firing"){
            if (mouse.dragging) {
                game.panTo(mouse.x + game.offsetLeft);
            } else {
                game.panTo(game.slingshotX);
            }
        }
        if (game.mode == "load-next-hero"){
            // 待完成
            // 检查是否有坏蛋还活着,如果没有,结束关卡
            // 检查是否还有可装填英雄,如果没有,结束关卡
            // 装填英雄
            game.mode = "wait-for-firing";
        }
        if (game.mode == "firing"){
            game.panTo(game.slingshotX);
        }
        if (game.mode == "fired"){
            // 待完成
            // 视野移动到英雄的当前位置
        }
    },
    animate: function() {
        //移动背景
        game.handlePanning();
        //使角色运动

        //使用视差滚动绘制背景,背景图像和前景图像以不同的速度移动,
        //这个差异会造成一种错觉:背景上的云彩离我们更远
        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.context.drawImage(game.slingshotFrontImage, game.slingshotX-
            game.offsetLeft, game.slingshotY);
        if (!game.ended) {
            game.animationFrame = window.requestAnimationFrame(game.animate, game.canvas);
        }
    }
}
$(window).load(function() {
    game.init();
});
// 关卡
var levels = {
    //关卡数据
    data: [
    {   //第一关
        foreground: 'desert-foreground',
        background: 'clouds-background',
        entities:[]
    },
    {   //第二关
        foreground: 'desert-foreground',
        background: 'clouds-background',
        entities:[]
    }
    ],
    // 初始化关卡选择画面
    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){
        //声明一个新的当前关卡对象
        game.currentLevel = {number:number, hero:[]};
        game.score = 0;
        $('#score').html('Score: ' + game.score);
        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");
        // 一旦所有的图像加载完成,就调用game.start()函数
        if (loader.loaded){
            game.start();
        } else {
            loader.onload = game.start;
        }
    }
}
//图像/声音资源加载器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;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值