利用TypeScript編寫貪吃蛇游戲
先來康康效果圖
我接下來將講解相關(guān)配置和代碼,源碼鏈接放在最底下了,在GitHub上。
Explanation
1. tsconfig.json配置
{ "compilerOptions": { "target": "ES2015", "module": "ES2015", "strict": true, "noEmitOnError": true } }
此處是對編譯選項進行配置
- target: 我們將TS轉(zhuǎn)譯成JS的版本。
- module: 模塊化的版本。
- strict: 所有相關(guān)的嚴格模式是否開啟。
- noEmitOnError: 當出現(xiàn)錯誤時是否停止編譯。
2. HTML & CSS 布局相關(guān)
先來看看我們整體的布局
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body scroll="no"> <!-- 創(chuàng)建游戲的主容器 --> <div id="main"> <!-- 設(shè)置游戲舞臺 --> <div id="stage"> <!-- 設(shè)置蛇 --> <div id="snack"> <!-- snack內(nèi)部的div表示蛇的各部分 --> <div></div> </div> <div id="food"> <!-- 設(shè)置食物的樣式 --> <div></div> <div></div> <div></div> <div></div> </div> </div> <!-- 設(shè)置游戲的積分牌 --> <div id="scoreStage"> <div>SCORE:<span id="score">0</span></div> <div>LEVEL:<span id="level">1</span></div> </div> </div> </body> </html>
CSS代碼
// 設(shè)置變量 @bgColor: #b7d4a8; // 清除默認樣式清除默認樣式 * { margin: 0; padding: 0; // 怪異模式 box-sizing: border-box; } body { font: bold 20px "Courier"; width: 100%; height: 100%; overflow: hidden; } // 設(shè)置主窗口的樣式 #main { width: 360px; height: 420px; background-color: @bgColor; margin: 100px auto; border: 10px solid #000; border-radius: 40px; display: flex; flex-direction: column; align-items: center; // 主軸對齊方式 justify-content: space-around; } #stage { width: 304px; height: 304px; border: 2px solid black; position: relative; } #scoreStage { width: 300px; display: flex; justify-content: space-between; } #snack { &>div { width: 10px; height: 10px; background-color: #000; border: 1px solid @bgColor; position: absolute; } } // 食物 #food { width: 10px; height: 10px; position: absolute; left: 40px; top: 100px; display: flex; flex-flow: row wrap; justify-content: space-between; align-content: space-between; &>div { width: 4px; height: 4px; background-color: #000; transform: rotate(45deg); // border: 1px solid @bgColor; } } body { scroll-behavior: unset; }
這里面比較有意思是可以通過@xxx來設(shè)置CSS變量
比如這里的:@bgColor: #b7d4a8;
3. TS核心邏輯
TS的核心在于Class,所以我們需要定義出非常多的類來對這個貪吃蛇小游戲進行分析。
我們先來看看這個貪吃蛇小游戲有幾個主要的部分。
- 食物
- 蛇
- 分數(shù)版
- 游戲操控
食物
食物有幾個核心邏輯
食物這個類。
首先,我們需要獲取其中的橫縱坐標。可以設(shè)置get來獲取X與Y。
其次,當蛇蛇碰到食物的時候,這個食物的位置會改變,可以設(shè)置一個change()方法。
class Food { // 屬性 & 方法 // 定義食物所對應的元素 element: HTMLElement; constructor() { // 加一個 “!”表示這玩意不會為空 this.element = document.getElementById('food')!; } // 方法 // 1. 獲取食物的x坐標的方法 get X() { return this.element.offsetLeft; } // 2. 獲取食物的y坐標的方法 get Y() { return this.element.offsetTop; } // 修改食物位置的方法 change() { // 使用random,生成隨機位置 // 蛇移動一次就是一格,大小為10 const left = Math.round(Math.random()*29)*10; const top = Math.round(Math.random()*29)*10; this.element.style.left = left + 'px'; this.element.style.top = top + 'px'; } } export default Food
蛇
蛇的話,話頭就很多了。
首先,我們需要獲取到蛇頭的橫縱坐標,還要能夠給橫縱坐標賦值。
其次,我們需要有方法增加蛇的身子addBody
同時還需要增加身子移動的方式moveBody
當然還需要增加檢測機制,舌頭不能與身子重疊
這個比較復雜,我們分而析之
1.元素設(shè)置與constructor
// 表示蛇的元素 head: HTMLElement; // 蛇的身體,包括蛇頭 bodies: HTMLCollection; // 獲取蛇的容器 element: HTMLElement; constructor() { // 斷言一下 | 找到蛇頭 this.head = document.querySelector('#snack > div') as HTMLElement; this.element = document.getElementById('snack')! this.bodies = this.element.getElementsByTagName('div'); }
這里面的!是用來確定存在id為snack這個元素的。
2.增加身子
addBody() { this.element.insertAdjacentHTML("beforeend", "<div></div>") }
3.移動身子
moveBody() { /** * 將后邊身體設(shè)置為前邊身體的位置 * 第四節(jié) = 第三節(jié)的位置 * 第三節(jié) = 第二節(jié)的位置 * 第二節(jié) = 第一節(jié)的位置 */ // 調(diào)節(jié)每個位置 for(let i=this.bodies.length-1;i>0;i--) { // 獲取前邊身體的位置 let X = (this.bodies[i-1] as HTMLElement).offsetLeft; let Y = (this.bodies[i-1] as HTMLElement).offsetTop; // 將這個值設(shè)置到當前身體 (this.bodies[i] as HTMLElement).style.left = X + 'px'; (this.bodies[i] as HTMLElement).style.top = Y + 'px'; } }
4.檢測機制
checkHeadBody() { // 獲取所有的身體,檢查其是否和蛇頭的坐標發(fā)生重疊 for(let i=1;i<this.bodies.length;i++) { const bd = (this.bodies[i] as HTMLElement) if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) { // 說明出現(xiàn)了碰撞 throw Error('撞到自己了~~~') } } }
5.完整代碼
class Snack { // 表示蛇的元素 head: HTMLElement; // 蛇的身體,包括蛇頭 bodies: HTMLCollection; // 獲取蛇的容器 element: HTMLElement; constructor() { // 斷言一下 | 找到蛇頭 this.head = document.querySelector('#snack > div') as HTMLElement; this.element = document.getElementById('snack')! this.bodies = this.element.getElementsByTagName('div'); } // 獲取蛇的坐標 get X() { return this.head.offsetLeft } get Y() { return this.head.offsetTop } // 設(shè)置蛇的坐標 set X(value) { // 新值和舊值相同,直接返回,無需修改。 if(this.X === value) return if(value < 0 || value > 290) { throw new Error('您撞墻了') } // 蛇在往左走,不能往右走 if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) { // 讓蛇向反方向繼續(xù)移動 if(value > this.X) { // 如果新值大于舊值X,說明蛇在向右走,此時發(fā)生掉頭,應該使蛇繼續(xù)向左走 value = this.X - 10 } else { value = this.X + 10 } } this.moveBody() this.head.style.left = value + 'px' this.checkHeadBody() } set Y(value) { if(this.Y === value) return if(value < 0 || value > 290) { throw new Error('您撞墻了') } if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) { // 讓蛇向反方向繼續(xù)移動 if(value > this.Y) { // 如果新值大于舊值X,說明蛇在向右走,此時發(fā)生掉頭,應該使蛇繼續(xù)向左走 value = this.Y - 10 } else { // 不是 += 是等于 value = this.Y + 10 } } this.moveBody() this.head.style.top = value + 'px' this.checkHeadBody() } // 蛇增加一截 addBody() { this.element.insertAdjacentHTML("beforeend", "<div></div>") } // 添加一個蛇身體移動的方法 moveBody() { /** * 將后邊身體設(shè)置為前邊身體的位置 * 第四節(jié) = 第三節(jié)的位置 * 第三節(jié) = 第二節(jié)的位置 * 第二節(jié) = 第一節(jié)的位置 */ // 調(diào)節(jié)每個位置 for(let i=this.bodies.length-1;i>0;i--) { // 獲取前邊身體的位置 let X = (this.bodies[i-1] as HTMLElement).offsetLeft; let Y = (this.bodies[i-1] as HTMLElement).offsetTop; // 將這個值設(shè)置到當前身體 (this.bodies[i] as HTMLElement).style.left = X + 'px'; (this.bodies[i] as HTMLElement).style.top = Y + 'px'; } } checkHeadBody() { // 獲取所有的身體,檢查其是否和蛇頭的坐標發(fā)生重疊 for(let i=1;i<this.bodies.length;i++) { const bd = (this.bodies[i] as HTMLElement) if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) { // 說明出現(xiàn)了碰撞 throw Error('撞到自己了~~~') } } } } export default Snack
得分面板
這個就比較簡單了,主要是得分增加的方法與等級提升的方法。
class scorePanel { // score和level用來記錄分數(shù)和等級 score: number = 0; level: number = 1; scoreSpan: HTMLElement; levelSpan: HTMLElement; // 設(shè)置等級 maxLevel: number; // 設(shè)置一個變量表示多少分升級 upScore: number; // 給兩個需要修改的元素賦值 constructor(maxLevel: number = 10, upScore: number = 2) { this.scoreSpan = document.getElementById('score')!; this.levelSpan = document.getElementById('level')!; this.maxLevel = maxLevel this.upScore = upScore } // method // 設(shè)置加分的方法 addScore() { // 分數(shù)自增 this.score += 1 this.scoreSpan.innerHTML = this.score + ''; // 判斷一下分數(shù)是多少 if(this.score % this.upScore === 0) { this.levelUp() } } // 提升等級的方法 levelUp() { if(this.level < this.maxLevel) { this.level += 1 this.levelSpan.innerHTML = this.level + ''; } } } export default scorePanel
控制面板
這個邏輯的核心之一是整合,之二是監(jiān)控鍵盤keydown事件
關(guān)于整合:我們會把其中的蛇,面板,食物都整合到這個類中,所謂一個啟動游戲的開關(guān)。
snack: Snack; food: Food; scorePanel: scorePanel; arrowDirection: string = ''; // 創(chuàng)建一個屬性用來記錄游戲是否結(jié)束 isLeave: boolean = true constructor() { this.snack = new Snack(); this.food = new Food(); this.scorePanel = new scorePanel(); this.init(); }
關(guān)于監(jiān)控鍵盤事件
這里的核心邏輯就是監(jiān)控,看是上下左右中的哪一個,然后對應的改變蛇蛇的方向。
其中蛇的移動需要不斷的調(diào)用run這個函數(shù),所以我們使用isLeave作為開關(guān),用遞歸來多次調(diào)用run這個函數(shù)。
import Snack from './snack'; import Food from "./Food"; import scorePanel from './scorePanel'; class GameControl { snack: Snack; food: Food; scorePanel: scorePanel; arrowDirection: string = ''; // 創(chuàng)建一個屬性用來記錄游戲是否結(jié)束 isLeave: boolean = true constructor() { this.snack = new Snack(); this.food = new Food(); this.scorePanel = new scorePanel(); this.init(); } // 游戲的初始化方法 init() { // 綁定鍵盤按下的時間 // const _this = this // 如果不改變這個this,則會綁定到document上面 // document.addEventListener('keydown', _this.keyDownHandler) document.addEventListener('keydown', this.keyDownHandler.bind(this)); // 調(diào)用run方法 this.run(); } // 創(chuàng)建一個鍵盤按下的響應函數(shù) /** * ArrowRight Right ArrowLeft Left ArrowDown Down ArrowUp Up */ keyDownHandler(event: KeyboardEvent) { this.arrowDirection = event.key // this.run(); } // 創(chuàng)建一個控制蛇移動的方法 run() { // 根據(jù)方向(this.direction)來使蛇的位置改變 // 向上 top - // 向下 top + // 向左 left - // 向右 left + let X = this.snack.X; let Y = this.snack.Y; // 根據(jù)按鍵方向修改值 switch (this.arrowDirection) { case "ArrowUp": case "Up": // 向上移動 Y -= 10 break; case "ArrowDown": case "Down": Y += 10 break; case "ArrowLeft": case "Left": X -= 10 break case "ArrowRight": case "Right": X += 10 break } try { this.snack.X = X; this.snack.Y = Y; } catch(e: any) { alert(e.message) this.isLeave = false } this.checkEat(X, Y) // 開啟定時調(diào)用 // 這是遞歸調(diào)用 this.isLeave && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30); } // 定義一個方法,用來檢查蛇是否吃到食物 checkEat(x:number, y:number) { if(x === this.food.X && y === this.food.Y) { this.food.change() // 食物改變位置 this.scorePanel.addScore() // 分數(shù)增加 this.snack.addBody() } } } export default GameControl
項目源碼鏈接
以上就是利用TypeScript編寫貪吃蛇游戲的詳細內(nèi)容,更多關(guān)于TypeScript貪吃蛇游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Jquery 返回json數(shù)據(jù)在IE瀏覽器中提示下載的問題
Jquery 返回json數(shù)據(jù),IE瀏覽器提示下載的問題,當提交完數(shù)據(jù)后返回的本來是json數(shù)據(jù)的,在火弧里測試正常,解決方法如下2014-05-05Javascript Request獲取請求參數(shù)如何實現(xiàn)
使用Javascript Request獲取參數(shù)的時候總是提示出錯,本文為此問題提供詳細的解決方案,需要了解的朋友可以參考下2012-11-11微信小程序用戶授權(quán)環(huán)節(jié)實現(xiàn)過程
這篇文章主要介紹了微信小程序用戶授權(quán)環(huán)節(jié)實現(xiàn)過程,在商城項目中,我們需要對部分的頁面,進行一個授權(quán)的判別,例如購物車,及個人中心,需要完成用戶信息的授權(quán)后,獲取到相關(guān)信息2023-01-01