摘要:首先是繪制靜態(tài)的地面。上一篇下一篇無小恐龍游戲源碼探究二讓地面動(dòng)起來
文章首發(fā)于我的 GitHub 博客目錄
Chrome 小恐龍游戲源碼探究一 -- 繪制靜態(tài)地面
Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來
Chrome 小恐龍游戲源碼探究三 -- 進(jìn)入街機(jī)模式
Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵
Chrome 小恐龍游戲源碼探究五 -- 隨機(jī)繪制障礙
Chrome 小恐龍游戲源碼探究六 -- 記錄游戲分?jǐn)?shù)
Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替
Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍
Chrome 小恐龍游戲源碼探究九 -- 游戲碰撞檢測
Chrome 小恐龍游戲源碼探究完 -- 游戲結(jié)束和其他要素
前言當(dāng) Chrome 處于離線情況下,會(huì)顯示以下頁面:
當(dāng)按下空格鍵或者 ↑ 鍵,小恐龍游戲彩蛋就觸發(fā)啦 (??????)??
游戲雖然簡單,但源碼卻有三千多行,代碼嚴(yán)謹(jǐn)且富有邏輯,值得拿來學(xué)習(xí)研究。這個(gè)教程將會(huì)從零開始,一步步解讀源碼并最終實(shí)現(xiàn)這個(gè)游戲。
獲取源碼、素材要獲取游戲的源碼,可以通過下面幾種方式:
斷網(wǎng)后,訪問任意網(wǎng)址,進(jìn)入小恐龍頁面,用開發(fā)者工具獲取源碼
在瀏覽器地址欄輸入 chrome://dino,進(jìn)入小恐龍頁面,用開發(fā)者工具獲取源碼
官方提供的源碼網(wǎng)址
有人將源碼提取出來放在了 GitHub 上:t-rex-runner
游戲用到的雪碧圖,音頻文件可以在官方提供的源碼網(wǎng)址里獲取到。
為了方便食用,我將雪碧圖中各個(gè)小圖片的坐標(biāo)信息標(biāo)了出來(W: Width, H: Height, L: Left, T: Top):
關(guān)于上面雪碧圖的坐標(biāo)信息,我是用一個(gè)在線工具獲取的:http://www.spritecow.com/,個(gè)別坐標(biāo)信息通過這個(gè)網(wǎng)站獲取的不太準(zhǔn),這里我已經(jīng)通過參考源碼里的數(shù)據(jù)進(jìn)行了修正。
戳這里獲取上面這張圖片的 JPG 原圖和 PSD 原圖。開始探究
游戲源碼主要包括九個(gè)類:
游戲的主體類 Runner
背景類 Horizon
地面類 HorizonLine
云朵類 Cloud
障礙物類 Obstacle
晝夜更替類 NightMode
小恐龍類 Trex
分?jǐn)?shù)類 DistanceMeter
游戲結(jié)束面板類 GameOverPanel
這個(gè)教程并不會(huì)完全按照源碼來,而是抽取主要的內(nèi)容來一步步實(shí)現(xiàn)這個(gè)游戲。這樣做并不意味著改變?cè)创a的思路,而是去除了一些目前可以先不考慮的代碼,比如:去除了適配 HDPI 和 LDPI、適配移動(dòng)端等。
這個(gè)游戲源碼的探究已經(jīng)有前輩 @逐影 寫了系列教程。在這里,我寫這個(gè)教程的目的,一是當(dāng)做學(xué)習(xí)筆記,二是提供與前輩不一樣的源碼解讀思路。游戲主體搭建
游戲文件結(jié)構(gòu)目錄:
chrome-dino - index.html - index.css - index.js // JS 入口文件 - offline.js // 游戲邏輯實(shí)現(xiàn) - imgs - sounds
想要獲取整個(gè)教程的源代碼,戳這里:GitHub
HTML、CSS 就不過多解釋,直接貼代碼:
Chrome Dino
* { margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box; } #chrome-dino { width: 100%; max-width: 600px; margin: 0 auto; } #offline-resources { display: none; } .offline .runner-container { position: absolute; top: 35px; width: 100%; max-width: 600px; height: 150px; overflow: hidden; } .offline .runner-canvas { z-index: 10; position: absolute; top: 0; height: 150px; max-width: 600px; overflow: hidden; opacity: 1; }
下面來分析 JS 代碼:
首先看一下游戲的主體類 Runner,這個(gè)類用于控制游戲的主要邏輯:
/** * 游戲主體類,控制游戲的整體邏輯 * @param {String} containerSelector 畫布外層容器的選擇器 * @param {Object} opt_config 配置選項(xiàng) */ function Runner(containerSelector, opt_config) { // 獲取游戲的 “根” DOM 節(jié)點(diǎn),整個(gè)游戲都會(huì)輸出到這個(gè)節(jié)點(diǎn)里 this.outerContainerEl = document.querySelector(containerSelector); // canvas 的外層容器 this.containerEl = null; this.config = opt_config || Runner.config; this.dimensions = Runner.defaultDimensions; this.time = 0; // 時(shí)鐘計(jì)時(shí)器 this.currentSpeed = this.config.SPEED; // 當(dāng)前的速度 this.activated = false; // 游戲彩蛋是否被激活(沒有被激活時(shí),游戲不會(huì)顯示出來) this.playing = false; // 游戲是否進(jìn)行中 this.crashed = false; // 小恐龍是否碰到了障礙物 this.paused = false // 游戲是否暫停 // 加載雪碧圖,并初始化游戲 this.loadImages(); } window["Runner"] = Runner; // 將 Runner 類掛載到 window 對(duì)象上
相關(guān)的數(shù)據(jù)和配置參數(shù):
var DEFAULT_WIDTH = 600; // 游戲畫布默認(rèn)寬度 var FPS = 60; // 游戲默認(rèn)幀率 // 游戲配置參數(shù) Runner.config = { SPEED: 6, // 移動(dòng)速度 }; // 游戲畫布的默認(rèn)尺寸 Runner.defaultDimensions = { WIDTH: DEFAULT_WIDTH, HEIGHT: 150, }; // 游戲用到的 className Runner.classes = { CONTAINER: "runner-container", CANVAS: "runner-canvas", PLAYER: "", // 預(yù)留出的 className,用來控制 canvas 的樣式 }; // 雪碧圖中圖片的坐標(biāo)信息 Runner.spriteDefinition = { LDPI: { HORIZON: { x: 2, y: 54 }, // 地面 }, }; // 游戲中用到的鍵盤碼 Runner.keyCodes = { JUMP: { "38": 1, "32": 1 }, // Up, Space DUCK: { "40": 1 }, // Down RESTART: { "13": 1 }, // Enter }; // 游戲中用到的事件 Runner.events = { LOAD: "load", };
在 Runner 原型鏈上添加的方法:
Runner.prototype = { // 初始化游戲 init: function () { // 生成 canvas 容器元素 this.containerEl = document.createElement("div"); this.containerEl.className = Runner.classes.CONTAINER; // 生成 canvas this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH, this.dimensions.HEIGHT, Runner.classes.PLAYER); this.ctx = this.canvas.getContext("2d"); this.ctx.fillStyle = "#f7f7f7"; this.ctx.fill(); // 加載背景類 Horizon this.horizon = new Horizon(this.canvas, this.spriteDef); // 將游戲添加到頁面中 this.outerContainerEl.appendChild(this.containerEl); }, // 加載雪碧圖資源 loadImages() { // 圖片在雪碧圖中的坐標(biāo) this.spriteDef = Runner.spriteDefinition.LDPI; // 獲取雪碧圖 Runner.imageSprite = document.getElementById("offline-resources-1x"); // 當(dāng)圖片加載完成(complete 是 DOM 中 Image 對(duì)象自帶的一個(gè)屬性) if (Runner.imageSprite.complete) { this.init(); } else { // 圖片沒有加載完成,監(jiān)聽其 load 事件 Runner.imageSprite.addEventListener(Runner.events.LOAD, this.init.bind(this)); } }, };
其中 createCanvas 方法定義如下:
/** * 生成 canvas 元素 * @param {HTMLElement} container canva 的容器 * @param {Number} width canvas 的寬度 * @param {Number} height canvas 的高度 * @param {String} opt_className 給 canvas 添加的類名(可選) * @return {HTMLCanvasElement} */ function createCanvas(container, width, height, opt_className) { var canvas = document.createElement("canvas"); canvas.className = opt_className ? opt_className + " " + Runner.classes.CANVAS : Runner.classes.CANVAS; canvas.width = width; canvas.height = height; container.appendChild(canvas); return canvas; }地面類 HorizonLine
定義好 Runner 類之后,為了方便探究,接下來從簡單的背景開始說起。首先是繪制靜態(tài)的地面。
定義地面類 HorizonLine:
/** * 地面類 * @param {HTMLCanvasElement} canvas 畫布 * @param {Object} spritePos 雪碧圖中的位置 */ function HorizonLine(canvas, spritePos) { this.canvas = canvas; this.ctx = this.canvas.getContext("2d"); this.dimensions = {}; // 地面的尺寸 this.spritePos = spritePos; // 雪碧圖中地面的位置 this.sourceXPos = []; // 雪碧圖中地面的兩種地形的 x 坐標(biāo) this.xPos = []; // canvas 中地面的 x 坐標(biāo) this.yPos = 0; // canvas 中地面的 y 坐標(biāo) this.bumpThreshold = 0.5; // 隨機(jī)地形系數(shù),控制兩種地形的出現(xiàn)頻率 this.init(); this.draw(); } HorizonLine.dimensions = { WIDTH: 600, HEIGHT: 12, YPOS: 127, // 繪制到 canvas 中的 y 坐標(biāo) };
在 HorizonLine 原型鏈上添加方法:
HorizonLine.prototype = { // 初始化地面 init: function () { for (const d in HorizonLine.dimensions) { if (HorizonLine.dimensions.hasOwnProperty(d)) { const elem = HorizonLine.dimensions[d]; this.dimensions[d] = elem; } } this.sourceXPos = [this.spritePos.x, this.spritePos.x + this.dimensions.WIDTH]; this.xPos = [0, HorizonLine.dimensions.WIDTH]; this.yPos = HorizonLine.dimensions.YPOS; }, // 繪制地面 draw: function () { // 使用 canvas 中 9 個(gè)參數(shù)的 drawImage 方法 this.ctx.drawImage( Runner.imageSprite, // 原圖片 this.sourceXPos[0], this.spritePos.y, // 原圖中裁剪區(qū)域的起點(diǎn)坐標(biāo) this.dimensions.WIDTH, this.dimensions.HEIGHT, this.xPos[0], this.yPos, // canvas 中繪制區(qū)域的起點(diǎn)坐標(biāo) this.dimensions.WIDTH, this.dimensions.HEIGHT, ); this.ctx.drawImage( Runner.imageSprite, this.sourceXPos[1], this.spritePos.y, this.dimensions.WIDTH, this.dimensions.HEIGHT, this.xPos[1], this.yPos, this.dimensions.WIDTH, this.dimensions.HEIGHT, ); }, };
背景類 Horizon 負(fù)責(zé)管理 HorizonLine、Cloud、Obstacle、NightMode 這幾個(gè)類。
所以接下來需要通過 Horizon 類來調(diào)用 HorizonLine 類。
背景類 Horizon定義背景類 Horizon:
/** * 背景類 * @param {HTMLCanvasElement} canvas 畫布 * @param {Object} spritePos 雪碧圖中的位置 */ function Horizon(canvas, spritePos) { this.canvas = canvas; this.ctx = this.canvas.getContext("2d"); this.spritePos = spritePos; // 地面 this.horizonLine = null; this.init(); }
在 Horizon 原型鏈上添加方法:
Horizon.prototype = { // 初始化背景 init: function () { this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON); }, };
最后,通過調(diào)用 Runner 類來運(yùn)行游戲:
index.js:
window.onload = function () { var chromeDino = document.getElementById("chrome-dino"); chromeDino.classList.add("offline"); new Runner("#chrome-dino"); };
到這里,不出意外的話,就可以繪制出靜態(tài)的地面,如圖:
查看完整的代碼:戳這里
這里各個(gè)方法和類之間的調(diào)用邏輯是(箭頭代指調(diào)用):
new Runner() -> loadImage() // Runner -> init() // Runner -> new Horizon() -> init() // Horizon -> new HorizonLine() -> init() // HorizonLine -> draw() // HorizonLine
簡單來說就是:游戲主體類 Runner 控制背景類 Horizon,再由背景類 Horizon 控制地面類 HorizonLine。
遵循的思想就是把游戲?qū)訉映橄螅沙橄蟪潭雀叩念愐粚右粚酉蛳抡{(diào)用抽象程度低的類。這樣做的好處是,思路清晰并且易于擴(kuò)展。
上一篇 | 下一篇 | 無 ?????????? ?????????? | Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來 |
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103890.html
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究一繪制靜態(tài)地面中定義了游戲的主體類,并實(shí)現(xiàn)了靜態(tài)地面的繪制。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究一 -- 繪制靜態(tài)地面》 中定義了游戲的主體類 Runner,并實(shí)現(xiàn)了靜態(tài)地面的繪制。這一篇文章中,將實(shí)現(xiàn)效果:1、地面無限滾動(dòng)。2、剛開始地面不動(dòng),按下空格后地面滾動(dòng)。 地面無限滾動(dòng) ...
摘要:例如,將函數(shù)修改為小恐龍眨眼這樣小恐龍會(huì)不停的眨眼睛。小恐龍的開場動(dòng)畫下面來實(shí)現(xiàn)小恐龍對(duì)鍵盤按鍵的響應(yīng)。接下來還需要更新動(dòng)畫幀才能實(shí)現(xiàn)小恐龍的奔跑動(dòng)畫。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替》實(shí)現(xiàn)了游戲晝夜模式的交替,這一篇文章中,將實(shí)現(xiàn):1、小恐龍的繪制 2、鍵盤對(duì)小恐龍的控制 3、頁面失焦后,重新聚焦會(huì)重置...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究二讓地面動(dòng)起來實(shí)現(xiàn)了地面的移動(dòng)。街機(jī)模式的效果就是游戲開始后,進(jìn)入全屏模式。例如可以看到,進(jìn)入街機(jī)模式之前,有一段開場動(dòng)畫。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來》 實(shí)現(xiàn)了地面的移動(dòng)。這一篇文章中,將實(shí)現(xiàn)效果:1、瀏覽器失焦時(shí)游戲暫停,聚焦游戲繼續(xù)。 2、開場動(dòng)...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究四隨機(jī)繪制云朵實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn)仙人掌翼龍障礙物的繪制游戲速度的改變障礙物的類型有兩種仙人掌和翼龍。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵》 實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn):1、仙人掌、翼龍障礙物的繪制 2、游戲速度的改變 障礙物...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究三進(jìn)入街機(jī)模式實(shí)現(xiàn)了開場動(dòng)畫和街機(jī)模式。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究三 -- 進(jìn)入街機(jī)模式》 實(shí)現(xiàn)了開場動(dòng)畫和街機(jī)模式。這一篇文章中,將實(shí)現(xiàn)云朵的隨機(jī)繪制。 云朵類 Cloud 定義云朵類 Cloud: /** * 云朵類 * @param {HTMLCanvasEle...
閱讀 2267·2021-11-15 11:39
閱讀 1035·2021-09-26 09:55
閱讀 970·2021-09-04 16:48
閱讀 2908·2021-08-12 13:23
閱讀 956·2021-07-30 15:30
閱讀 2494·2019-08-29 14:16
閱讀 922·2019-08-26 10:15
閱讀 559·2019-08-23 18:40