您的位置:首页 > Web前端 > HTML5

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

2018-02-09 16:30 423 查看
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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: