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);
};
};
相關文章
- 《Unity移動遊戲開發》讀後感Unity遊戲開發
- 騰訊網易先後在北美設立遊戲工作室,意欲何為?遊戲
- 我先走? 你先走?
- 回合制遊戲先後手平衡簡析遊戲
- Kitten程式設計貓裡如何先後播放不同的背景音樂程式設計
- 如何設計高難度遊戲遊戲
- JavaScript高階程式設計(讀後感-持續更新)JavaScript程式設計
- 先奮鬥後躺平,學習風變程式設計開啟了我的“躺平”生涯程式設計
- IOS榜第二,霸榜TapTap,這款“狼人”遊戲如何後發先至?iOSAPT遊戲
- 聊聊先享後付
- css3 列表按先後順序移動過來顯示CSSS3
- 投放Facebook移動遊戲廣告如何少走彎路?(前/中/後期)遊戲
- 開發者分享如何在遊戲設計中做到難易程度的平衡遊戲設計
- 先驗概率 後驗概率 似然估計
- java程式設計師入門先學什麼開發者工具Java程式設計師
- 遊戲公司上市難?上市之後更加難!遊戲
- Vungle收購領先的SaaS平臺移動遊戲分析公司GameRefinery遊戲GAM
- 優先定義,使用滯後
- HTML5遊戲開發(五):飛機大戰之讓所有元素動起來HTML遊戲開發
- 學習程式設計先學什麼程式設計
- 程式設計入門先學什麼?程式設計
- 作為一個移動開發程式設計師,踏出這一步之後,雖然苦,但是從未後悔移動開發程式設計師
- Java後端高階開發面試技巧解析Java後端面試
- 高併發後端設計-限流篇後端
- 創意為先,萬物皆可遊戲——睡神飛工作室遊戲
- 淺析《先驅者》出色的難度系統設計
- 【程式設計師的遊戲開發之路】 遊戲架構程式設計師遊戲開發架構
- Rust 程式設計影片教程(進階)——026_1 高階 trait1Rust程式設計AI
- 新手程式設計入門先學什麼?程式設計
- 8 款主流 Scrum 敏捷開發工具評測,建議先馬後看!Scrum敏捷
- Rust 程式設計影片教程(進階)——027_1 高階特性Rust程式設計
- 網易生存奪金手遊《超凡先鋒》曝光,1月22日首測開啟
- 【譯】闖入遊戲開發 #3:程式設計遊戲開發程式設計
- Rust 程式設計視訊教程(進階)——026_1 高階 trait1Rust程式設計AI
- python if語句有先後順序嗎Python
- Rust 程式設計視訊教程(進階)——027_1 高階特性Rust程式設計
- 遊戲開發與設計遊戲開發
- Python後臺開發(第三章: Django高階)PythonDjango
- 退休後程式設計?程式設計