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:使用者釋放了滑鼠按鍵併發射英雄之後進入這個階段。此時,遊戲將所有的事情交給物理引擎來處理,使用者僅僅在觀看。遊戲畫面會隨著發射出的英雄平移,這樣使用者就可以追蹤英雄的軌跡。
// 建立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;
}
}
相關文章
- 悠遊世界/遊戲/系統技術開發/悠遊世界養成遊戲開發解析遊戲開發
- 如何建立遊戲世界觀遊戲
- “遊戲中的遊戲世界”——遊戲副本的起源與發展史遊戲
- 悠遊世界合成遊戲系統技術開發解析/合成遊戲/小遊戲遊戲
- 幽冥世界鏈遊/闖關/系統開發/合成卡牌遊戲/幽冥世界遊戲玩法遊戲
- HTML5遊戲開發(一):3分鐘建立一個hello worldHTML遊戲開發
- 第 1 天|基於 AI 進行遊戲開發:5 天建立一個農場遊戲!AI遊戲開發
- 坦克世界開發商Wargaming進軍手機遊戲市場GAM遊戲
- HTML5遊戲開發過程中的二三事HTML遊戲開發
- Nim遊戲2(臺階型)遊戲
- 使用 .NET 進行遊戲開發遊戲開發
- 微信小遊戲開發(2)遊戲開發
- 環遊世界/合成/遊戲/系統技術開發案例遊戲
- 遊戲開發入門(一)遊戲開發概述遊戲開發
- 專業遊戲開發者眼中的《夢想世界》遊戲開發
- 【譯】闖入遊戲開發 #2:遊戲開發的常見陷阱(以及如何避免它們)遊戲開發
- NFT遊戲系統開發/遊戲開發技術遊戲開發
- 遊戲開發中遊戲效能的最佳化遊戲開發
- 使用Xamarin開發移動應用示例——數獨遊戲(二)建立遊戲介面遊戲
- 遊戲裡的中土世界遊戲
- Python遊戲開發工程師的起步,幾款遊戲開發案例Python遊戲開發工程師
- 開發者談合格遊戲文件的基本構成要素遊戲
- Unity遊戲示例來了,用Unity開源遊戲資源做遊戲,遊戲開發不再難!Unity遊戲開發
- NFT鏈遊(農民世界)遊戲系統模型開發(玩法解析)遊戲模型
- 《殘世界的鳶尾花》創作者2D_貓分享遊戲開發歷程遊戲開發
- 遊戲開發流程遊戲開發
- 網易推出新遊戲業務“發燒遊戲” 主打引進精品遊戲遊戲
- HTML5遊戲開發(二):使用TypeScript編寫程式碼HTML遊戲開發TypeScript
- 遊戲策劃進階技能:如何輸出優秀的‘遊戲測評報告’遊戲
- 【刷遊戲開發面經記錄】2遊戲開發
- 【程式設計師的遊戲開發之路】 遊戲架構程式設計師遊戲開發架構
- 遊戲開發者都擅長“打自己的遊戲”嗎?遊戲開發
- 如何建立智慧合約遊戲系統?智慧合約遊戲開發核心原始碼示例遊戲開發原始碼
- 無盡的探索:遊戲中的開放世界遊戲
- 鬥羅世界NFT遊戲系統技術開發丨鬥羅世界鏈遊開發模式詳情遊戲模式
- 喵的Unity遊戲開發之路 - 推球:遊戲中的物理Unity遊戲開發
- 搭建我的世界遊戲伺服器 讓遊戲更high遊戲伺服器
- 阿里開源HTML5小遊戲開發框架Hilo實戰教程阿里HTML遊戲開發框架
- 小遊戲進階課程:解析休閒小遊戲發行與運營,助力規模化遊戲