HTML5移動遊戲開發高階程式設計 1:先飛後走,先難後易
1.1 引言
在HTML5上從頭開始構建一個一次性遊戲---一個名為Alien Invasion的縱向卷軸2D太空射擊類遊戲。
1.2 用500行程式碼構建一個完整遊戲
Alien Invasion秉承了遊戲“1942”的精髓(但是在太空中),或可把它看成Galaga的一個簡化版本。玩家控制出現在螢幕底部的飛船,操作飛船垂直飛過無邊無際的太空領域,同時保衛地球,抵抗成群入侵的外星人。
在移動裝置上玩遊戲時,使用者是通過顯示在螢幕左下角的左右箭頭進行控制的,發射按鈕在右側。在桌面上玩遊戲時,使用者可以使用鍵盤的箭頭鍵來控制飛行和使用空格鍵進行射擊。
為彌補移動裝置螢幕大小各有不同這一不足,遊戲會調整遊戲區域,始終按照裝置大小來執行遊戲。在桌面上,遊戲會被放在瀏覽器頁面中間的一個矩形區域中執行。
結構化遊戲:幾乎每個這種型別的遊戲都包含了幾塊相同的內容:一些資產的載入、一個標題畫面、一些精靈、使用者輸入、碰撞檢測以及一個把這幾塊內容整合在一起的遊戲迴圈。
http://cykod.github.com/AlienInvasion/
1.3 新增HTML和CSS樣板程式碼
<canvas>元素
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Alien Invasion</title>
<link rel="stylesheet" href="base.css" type="text/css" />
</head>
<body>
<div id="container">
<canvas id='game' width='320' height="480"></canvas>
</div>
<script src="game.js"></script>
</body>
</html>
base.css
#container {
padding-top: 50px;
margin: 0 auto;
width: 480px;
}
canvas {
background-color: black;
}
1.4 畫布入門
drawImage有著幾種不同的呼叫形式,這取決於你是想繪製完整影像還是僅繪製部分影像。
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
這種形式使得你能使用引數sx、sy、sWidth和sHeight在影像中指定源矩形,以及使用引數dx、dy、dWidth和dHeight在畫布上指定目標矩形。要從精靈表的某個精靈中抽取單獨的幀,這就是你應用使用的格式
game.js
var canvas = document.getElementById('game');
var ctx = canvas.getContext && canvas.getContext('2d');
if (!ctx) {
// No 2d
alert('Please upgrade your browser');
} else {
startGame();
}
function startGame() {
ctx.fillStyle = "#FFFF00";
ctx.fillRect(50, 100, 380, 400);
ctx.fillStyle = "rgba(0,0,128,0.5);";
ctx.fillRect(0, 50, 380, 400);
//載入影像
var img = new Image();
img.onload = function() {
// 非同步呼叫
ctx.drawImage(img,18,0,18,25,100,100,18,25);
console.log("onload");
}
img.src = 'images/sprites.png';
}
1.5 建立遊戲的結構
利用鴨子型別:基於物件的外部介面(而非它們的型別)來使用物件的概念稱為鴨子型別(duck typing)
Alien Invasion在遊戲介面和精靈這兩個地方用到了這一概念,該遊戲把任何響應step()和draw()方法呼叫的事物都當成遊戲介面物件或有效的精靈對待。把鴨子型別用於遊戲介面,這使得Alien Invasion能夠把標題畫面和遊戲中的介面當成同樣的物件型別看待,簡化了關卡和標題畫面之間的切換。同樣鴨子型別用於精靈意味著遊戲能夠靈活決定往遊戲皮膚中新增的內容,其中包括玩家、敵人、炮彈和HUD元素等。HUD是抬頭顯示裝置的簡稱,這個術語常指位於遊戲螢幕上方的元素,如剩餘的生命條數和玩家得分等。
建立三個基本物件:Game物件(把所有東西捆綁在一起)、SpriteSheet物件(載入和繪製精靈)以及GameBoard(顯示、更新精靈元素和處理精靈元素碰撞)。該遊戲還需要一大群不同的精靈,如玩家、敵方飛船、導彈及諸如得分和剩餘生命條數一類的HUD物件等。
1.6 載入精靈表
SpriteSheet類
1.7 建立Game物件
主要目的是初始化遊戲引擎並執行遊戲迴圈,以及提供一種機制來改變所顯示的主場景。
1.8 新增滾動背景
每幀繪製太多精靈會降低遊戲在移動裝置上的執行速度。一種解決方法是建立畫布的離屏緩衝區,在緩衝區中隨機繪製一堆星星,然後簡單地繪製慢慢向下移過畫布的星空。
http://jsperf.com/prerendered-starfield測試HTML5效能
StarField類需要完成的事情主要有三項,第一項是建立離屏畫布。
1.9 插入標題畫面
一個文字標題和一個副標題
Bangers字型賦予遊戲一種很好的復古風格
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Bangers" type="text/css" />
1.10 新增主角
第一步是新增一艘由玩家控制的飛船
建立PlayerShip物件:
index.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Alien Invasion</title>
<link rel="stylesheet" href="base.css" type="text/css" />
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Bangers" type="text/css" />
</head>
<body>
<div id="container">
<canvas id='game' width='480' height="600"></canvas>
</div>
<script src="engine.js"></script>
<script src="game.js"></script>
</body>
</html>
base.css
#container {
padding-top: 50px;
margin: 0 auto;
width: 480px;
}
canvas {
background-color: black;
}
engine.js
var Game = new function() {
this.initialize = function(canvasElementId,sprite_data,callback) {
this.canvas = document.getElementById(canvasElementId);
this.width = this.canvas.width;
this.height = this.canvas.height;
// Setu up the rendering context
this.ctx = this.canvas.getContext && this.canvas.getContext('2d');
if (!this.ctx) {
return alert("Please upgrade your browser to play");
}
this.setupInput();
this.loop();
SpriteSheet.load(sprite_data, callback);
};
// Handle Input
var KEY_CODES = { 37:'left', 39:'right', 32:'fire' };
this.keys = {};
this.setupInput = function() {
window.addEventListener('keydown', function(e) {
if (KEY_CODES[event.keyCode]) {
Game.keys[KEY_CODES[event.keyCode]] = true;
e.preventDefault();
}
}, false);
window.addEventListener('keyup', function(e) {
if (KEY_CODES[event.keyCode]) {
Game.keys[KEY_CODES[event.keyCode]] = false;
e.preventDefault();
}
}, false);
}
// 存放的是已更新並已繪製到畫布上的遊戲的各塊內容,一個可能的皮膚例子是背景或標題畫面
var boards = [];
this.loop = function() {
var dt = 30/1000;
for (var i=0, len=boards.length; i<len; i++) {
if (boards[i]) {
boards[i].step(dt);
boards[i] && boards[i].draw(Game.ctx);
}
}
setTimeout(Game.loop, 30);
};
// Change an active game board
this.setBoard = function(num, board) {
boards[num] = board;
};
};
var SpriteSheet = new function() {
this.map = { };
this.load = function(spriteData, callback) {
this.map = spriteData;
this.image = new Image();
this.image.onload = callback;
this.image.src = 'images/sprites.png';
};
this.draw = function(ctx,sprite,x,y,frame) {
var s = this.map[sprite];
if (!frame) {
frame = 0;
}
//console.log(s);
ctx.drawImage(this.image,
s.sx + frame * s.w,
s.sy,
s.w, s.h,
x, y,
s.w, s.h);
};
};
var TitleScreen = function TitleScreen(title, subtitle, callback) {
this.step = function(dt) {
if (Game.keys['fire'] && callback)
callback();
};
this.draw = function(ctx) {
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
ctx.font = "bold 40px bangers";
ctx.fillText(title, Game.width/2, Game.height/2);
ctx.font = "bold 20px bangers";
ctx.fillText(subtitle, Game.width/2, Game.height/2 + 40);
}
};
game.js
var sprites = {
ship: { sx: 0, sy: 0, w: 38, h: 42, frames: 1 }
};
function startGame() {
//SpriteSheet.draw(Game.ctx, "ship", 100, 100, 1);
Game.setBoard(0, new Starfield(20,0.4,100,true))
Game.setBoard(1, new Starfield(50,0.6,100))
Game.setBoard(2, new Starfield(100,1.0,50));
Game.setBoard(3, new TitleScreen("Alien Invasion",
"Press space to start playing",
playGame));
}
window.addEventListener("load", function() {
Game.initialize("game", sprites, startGame);
});
var Starfield = function(speed,opacity,numStars,clear){
// Set up the offscreen canvas
var stars = document.createElement("canvas");
stars.width = Game.width;
stars.height = Game.height;
var starCtx = stars.getContext("2d");
var offset = 0;
// If the clear option is set,
// make the background black instead of transparent
if (clear) {
starCtx.fillStyle = "#000";
starCtx.fillRect(0,0,stars.width,stars.height);
}
// Now draw a bunch of random 2 pixel
// rectangles onto the offscreen canvas
starCtx.fillStyle = "#FFF";
starCtx.globalAlpha = opacity;
for (var i=0; i<numStars; i++) {
starCtx.fillRect(Math.floor(Math.random()*stars.width),
Math.floor(Math.random()*stars.height),
2,
2);
}
// This method is called every frame
// to draw the starfield onto the canvas
this.draw = function(ctx) {
var intOffset = Math.floor(offset);
var remaining = stars.height - intOffset;
// Draw the top half of the starfield
if (intOffset > 0) {
ctx.drawImage(stars,
0, remaining,
stars.width, intOffset,
0, 0,
stars.width, intOffset);
}
// Draw the bottom half of the starfield
if (remaining > 0) {
ctx.drawImage(stars,
0, 0,
stars.width, remaining,
0, intOffset,
stars.width, remaining);
}
}
// This method is called to update
// the starfield
this.step = function(dt) {
offset += dt * speed;
offset = offset % stars.height;
}
}
var playGame = function() {
Game.setBoard(3, new PlayerShip());
}
var PlayerShip = function() {
this.w = SpriteSheet.map['ship'].w;
this.h = SpriteSheet.map['ship'].h;
this.x = Game.width/2 - this.w/2;
this.y = Game.height - 10 - this.h;
this.vx = 0;
this.step = function(dt) {
//
this.maxVel = 200;
this.step = function(dt) {
if (Game.keys['left']) {
this.vx = -this.maxVel;
} else if (Game.keys['right']) {
this.vx = this.maxVel;
} else {
this.vx = 0;
}
this.x += this.vx * dt;
if (this.x < 0) {
this.x = 0;
} else if (this.x > Game.width - this.w) {
this.x = Game.width - this.w;
}
}
};
this.draw = function(ctx) {
SpriteSheet.draw(ctx, 'ship', this.x, this.y, 1);
};
};
相關文章
- HTML5移動遊戲開發高階程式設計 3:試飛結束,向移動進發HTML遊戲開發程式設計
- HTML5移動遊戲開發高階程式設計 9:自建Quintus引擎(1)HTML遊戲開發程式設計UI
- HTML5移動遊戲開發高階程式設計 2:從玩具到遊戲HTML遊戲開發程式設計
- HTML5移動遊戲開發高階程式設計 10:自建Quintus引擎(2)HTML遊戲開發程式設計UI
- HTML5移動遊戲開發高階程式設計 11:自建Quintus引擎(3)HTML遊戲開發程式設計UI
- Kitten程式設計貓裡如何先後播放不同的背景音樂程式設計
- JavaScript高階程式設計(讀後感-持續更新)JavaScript程式設計
- 《JavaScript高階程式設計(第3版)》讀後感JavaScript程式設計
- css3 列表按先後順序移動過來顯示CSSS3
- 【高薪誠聘】永先創新-J2EE後臺高階工程師高薪工程師
- 《Unity移動遊戲開發》讀後感Unity遊戲開發
- 高階bash指令碼程式設計(1)指令碼程式設計
- 野生程式設計師:優先招聘程式設計師
- 【野生程式設計師】:優先招聘程式設計師
- asp:Button 先js驗證,然後到後臺JS
- 先奮鬥後躺平,學習風變程式設計開啟了我的“躺平”生涯程式設計
- HTML5遊戲開發進階 7 :單位智慧移動HTML遊戲開發
- 淺析《先驅者》出色的難度系統設計
- 程式設計入門先學什麼?程式設計
- 學習程式設計先學什麼程式設計
- 是先做資料庫設計還是先建模資料庫
- 新手程式設計入門先學什麼?程式設計
- SAI:移動優先也許並不好AI
- Flash遊戲開發專家Gary Rosenzweig:先成為程式設計師,再做AS程式設計師(圖靈訪談)遊戲開發ROS程式設計師圖靈
- .NET進階篇-醜話先說,Flag先立
- 鷹抓無人機一擊致命 制服無人機後飛走無人機
- 【疑問】《JavaScript高階程式設計(第3版)》(1)JavaScript程式設計
- 【筆記】《JavaScript高階程式設計(第3版)》(1)筆記JavaScript程式設計
- python if語句有先後順序嗎Python
- 回合制遊戲先後手平衡簡析遊戲
- Rust 程式設計影片教程(進階)——027_1 高階特性Rust程式設計
- 程式設計師的人生······我先酸為敬程式設計師
- 程式程式設計1 – Unix環境高階程式設計7章讀書筆記程式設計筆記
- 《HTML5移動Web開發實戰》——第1章 移動Web設計趨勢HTMLWeb
- Oracle 高階程式設計 01 ~Oracle程式設計
- 移動優先的響應式佈局
- Oracle建完庫後必須先設好的三個引數Oracle
- 程式設計師修煉之道讀後感(1)程式設計師