利用TypeScript編寫貪吃蛇游戲
先來康康效果圖

我接下來將講解相關(guān)配置和代碼,源碼鏈接放在最底下了,在GitHub上。
Explanation
1. tsconfig.json配置
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
此處是對編譯選項(xiàng)進(jìn)行配置
- target: 我們將TS轉(zhuǎn)譯成JS的版本。
- module: 模塊化的版本。
- strict: 所有相關(guān)的嚴(yán)格模式是否開啟。
- noEmitOnError: 當(dāng)出現(xiàn)錯(cuò)誤時(shí)是否停止編譯。
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è)置游戲舞臺(tái) -->
<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;
// 清除默認(rèn)樣式清除默認(rèn)樣式
* {
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,所以我們需要定義出非常多的類來對這個(gè)貪吃蛇小游戲進(jìn)行分析。
我們先來看看這個(gè)貪吃蛇小游戲有幾個(gè)主要的部分。
- 食物
- 蛇
- 分?jǐn)?shù)版
- 游戲操控
食物
食物有幾個(gè)核心邏輯
食物這個(gè)類。
首先,我們需要獲取其中的橫縱坐標(biāo)??梢栽O(shè)置get來獲取X與Y。
其次,當(dāng)蛇蛇碰到食物的時(shí)候,這個(gè)食物的位置會(huì)改變,可以設(shè)置一個(gè)change()方法。
class Food {
// 屬性 & 方法
// 定義食物所對應(yīng)的元素
element: HTMLElement;
constructor() {
// 加一個(gè) “!”表示這玩意不會(huì)為空
this.element = document.getElementById('food')!;
}
// 方法
// 1. 獲取食物的x坐標(biāo)的方法
get X() {
return this.element.offsetLeft;
}
// 2. 獲取食物的y坐標(biāo)的方法
get Y() {
return this.element.offsetTop;
}
// 修改食物位置的方法
change() {
// 使用random,生成隨機(jī)位置
// 蛇移動(dòng)一次就是一格,大小為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
蛇
蛇的話,話頭就很多了。
首先,我們需要獲取到蛇頭的橫縱坐標(biāo),還要能夠給橫縱坐標(biāo)賦值。
其次,我們需要有方法增加蛇的身子addBody
同時(shí)還需要增加身子移動(dòng)的方式moveBody
當(dāng)然還需要增加檢測機(jī)制,舌頭不能與身子重疊
這個(gè)比較復(fù)雜,我們分而析之
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這個(gè)元素的。
2.增加身子
addBody() {
this.element.insertAdjacentHTML("beforeend", "<div></div>")
}
3.移動(dòng)身子
moveBody() {
/**
* 將后邊身體設(shè)置為前邊身體的位置
* 第四節(jié) = 第三節(jié)的位置
* 第三節(jié) = 第二節(jié)的位置
* 第二節(jié) = 第一節(jié)的位置
*/
// 調(diào)節(jié)每個(gè)位置
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;
// 將這個(gè)值設(shè)置到當(dāng)前身體
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}4.檢測機(jī)制
checkHeadBody() {
// 獲取所有的身體,檢查其是否和蛇頭的坐標(biāo)發(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');
}
// 獲取蛇的坐標(biāo)
get X() {
return this.head.offsetLeft
}
get Y() {
return this.head.offsetTop
}
// 設(shè)置蛇的坐標(biāo)
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ù)移動(dòng)
if(value > this.X) {
// 如果新值大于舊值X,說明蛇在向右走,此時(shí)發(fā)生掉頭,應(yīng)該使蛇繼續(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ù)移動(dòng)
if(value > this.Y) {
// 如果新值大于舊值X,說明蛇在向右走,此時(shí)發(fā)生掉頭,應(yīng)該使蛇繼續(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>")
}
// 添加一個(gè)蛇身體移動(dòng)的方法
moveBody() {
/**
* 將后邊身體設(shè)置為前邊身體的位置
* 第四節(jié) = 第三節(jié)的位置
* 第三節(jié) = 第二節(jié)的位置
* 第二節(jié) = 第一節(jié)的位置
*/
// 調(diào)節(jié)每個(gè)位置
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;
// 將這個(gè)值設(shè)置到當(dāng)前身體
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}
checkHeadBody() {
// 獲取所有的身體,檢查其是否和蛇頭的坐標(biāo)發(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
得分面板
這個(gè)就比較簡單了,主要是得分增加的方法與等級提升的方法。
class scorePanel {
// score和level用來記錄分?jǐn)?shù)和等級
score: number = 0;
level: number = 1;
scoreSpan: HTMLElement;
levelSpan: HTMLElement;
// 設(shè)置等級
maxLevel: number;
// 設(shè)置一個(gè)變量表示多少分升級
upScore: number;
// 給兩個(gè)需要修改的元素賦值
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() {
// 分?jǐn)?shù)自增
this.score += 1
this.scoreSpan.innerHTML = this.score + '';
// 判斷一下分?jǐn)?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
控制面板
這個(gè)邏輯的核心之一是整合,之二是監(jiān)控鍵盤keydown事件
關(guān)于整合:我們會(huì)把其中的蛇,面板,食物都整合到這個(gè)類中,所謂一個(gè)啟動(dòng)游戲的開關(guān)。
snack: Snack;
food: Food;
scorePanel: scorePanel;
arrowDirection: string = '';
// 創(chuàng)建一個(gè)屬性用來記錄游戲是否結(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)控,看是上下左右中的哪一個(gè),然后對應(yīng)的改變蛇蛇的方向。
其中蛇的移動(dòng)需要不斷的調(diào)用run這個(gè)函數(shù),所以我們使用isLeave作為開關(guān),用遞歸來多次調(diào)用run這個(gè)函數(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)建一個(gè)屬性用來記錄游戲是否結(jié)束
isLeave: boolean = true
constructor() {
this.snack = new Snack();
this.food = new Food();
this.scorePanel = new scorePanel();
this.init();
}
// 游戲的初始化方法
init() {
// 綁定鍵盤按下的時(shí)間
// const _this = this
// 如果不改變這個(gè)this,則會(huì)綁定到document上面
// document.addEventListener('keydown', _this.keyDownHandler)
document.addEventListener('keydown', this.keyDownHandler.bind(this));
// 調(diào)用run方法
this.run();
}
// 創(chuàng)建一個(gè)鍵盤按下的響應(yīng)函數(shù)
/**
* ArrowRight Right
ArrowLeft Left
ArrowDown Down
ArrowUp Up
*/
keyDownHandler(event: KeyboardEvent) {
this.arrowDirection = event.key
// this.run();
}
// 創(chuàng)建一個(gè)控制蛇移動(dò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":
// 向上移動(dòng)
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)
// 開啟定時(shí)調(diào)用
// 這是遞歸調(diào)用
this.isLeave && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
}
// 定義一個(gè)方法,用來檢查蛇是否吃到食物
checkEat(x:number, y:number) {
if(x === this.food.X && y === this.food.Y) {
this.food.change() // 食物改變位置
this.scorePanel.addScore() // 分?jǐn)?shù)增加
this.snack.addBody()
}
}
}
export default GameControl
項(xiàng)目源碼鏈接
以上就是利用TypeScript編寫貪吃蛇游戲的詳細(xì)內(nèi)容,更多關(guān)于TypeScript貪吃蛇游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript設(shè)計(jì)模式適配器模式實(shí)例
這篇文章主要介紹了JavaScript設(shè)計(jì)模式適配器模式實(shí)例,適配器設(shè)計(jì)模式可以讓彼此不兼容的功能在一塊工作,有助于避免大規(guī)模的修改代碼,并且易于擴(kuò)展和兼容2022-06-06
Jquery 返回json數(shù)據(jù)在IE瀏覽器中提示下載的問題
Jquery 返回json數(shù)據(jù),IE瀏覽器提示下載的問題,當(dāng)提交完數(shù)據(jù)后返回的本來是json數(shù)據(jù)的,在火弧里測試正常,解決方法如下2014-05-05
微信小程序之ES6與事項(xiàng)助手的功能實(shí)現(xiàn)
本篇文章主要介紹了微信小程序之ES6與事項(xiàng)助手的功能實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的同學(xué)可以了解一下。2016-11-11
Javascript Request獲取請求參數(shù)如何實(shí)現(xiàn)
使用Javascript Request獲取參數(shù)的時(shí)候總是提示出錯(cuò),本文為此問題提供詳細(xì)的解決方案,需要了解的朋友可以參考下2012-11-11
javascript實(shí)現(xiàn)圖片輪換動(dòng)作方法
這篇文章主要介紹了javascript實(shí)現(xiàn)圖片輪換動(dòng)作方法,文章通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
js實(shí)現(xiàn)點(diǎn)擊按鈕彈出上傳文件的窗口
本文主要介紹了js實(shí)現(xiàn)點(diǎn)擊按鈕彈出上傳文件的窗口的實(shí)例方法。具有很好的參考價(jià)值,需要的朋友一起來看下吧2016-12-12
微信小程序用戶授權(quán)環(huán)節(jié)實(shí)現(xiàn)過程
這篇文章主要介紹了微信小程序用戶授權(quán)環(huán)節(jié)實(shí)現(xiàn)過程,在商城項(xiàng)目中,我們需要對部分的頁面,進(jìn)行一個(gè)授權(quán)的判別,例如購物車,及個(gè)人中心,需要完成用戶信息的授權(quán)后,獲取到相關(guān)信息2023-01-01

