TS实现贪吃蛇游戏
文章目录
- TS实现贪吃蛇游戏
- @[toc]
- 1.项目效果
- 2.项目梳理
- 3.项目准备
- 4.主体页面结构
- 5.CSS样式
- 6.TS逻辑
- 6.1 食物逻辑
- 6.2 蛇逻辑
- 6.3 记分板逻辑
- 6.4 游戏控制器逻辑
- 6.5 程序入口ts
文章目录
- TS实现贪吃蛇游戏
- @[toc]
- 1.项目效果
- 2.项目梳理
- 3.项目准备
- 4.主体页面结构
- 5.CSS样式
- 6.TS逻辑
- 6.1 食物逻辑
- 6.2 蛇逻辑
- 6.3 记分板逻辑
- 6.4 游戏控制器逻辑
- 6.5 程序入口ts
1.项目效果
项目体验
2.项目梳理
这个小游戏主要包括积分面板,食物,蛇,还有我们的游戏控制器这四个部分,分为四个类来写。
3.项目准备
-
1.项目环境
- node环境
- 采用webpack进行打包
- 采用less进行书写样式表
- ts用来编写逻辑
-
2.项目配置
-
package.json
{"name": "demo","version": "1.0.0","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack","start": "webpack serve"},"author": "","license": "ISC","keywords": [],"description": "","devDependencies": {"@babel/core": "^7.21.8","@babel/preset-env": "^7.21.5","babel-loader": "^9.1.2","clean-webpack-plugin": "^4.0.0","core-js": "^3.30.2","css-loader": "^6.7.4","html-webpack-plugin": "^5.5.1","less": "^4.1.3","less-loader": "^11.1.0","postcss": "^8.4.23","postcss-loader": "^7.3.0","postcss-preset-env": "^8.4.1","style-loader": "^3.3.3","ts-loader": "^9.4.3","typescript": "^5.0.4","webpack": "^5.83.1","webpack-cli": "^5.1.1","webpack-dev-server": "^4.15.0"} }
-
tsconfig.json
{"compilerOptions": {"module": "es2015","target": "es2015","strict": true,"sourceMap": false,//当有错误时不生成编译后文件"noEmitOnError": true},"include": ["./src/**/*"],"exclude": ["node_modules"] }
-
webpack.config.js 打包配置
//引入一个包 const path = require("path"); //引入html插件 const HTMLWebpackPlugin = require("html-webpack-plugin") //引入编译清除上一次文件插件 const {CleanWebpackPlugin} = require("clean-webpack-plugin")//webpack中所有的配置文件都放在module.exports中 module.exports = {entry: "./src/index.ts",//指定打包文件所在目录output: {//指定打包文件的目录path: path.resolve(__dirname, "dist"),//打包后的文件filename: "bundle.js",//告诉webpack不使用箭头environment: {arrowFunction: false}},// mode: 'development',// 设置modemode: 'production',// 设置mode//指定webpack打包时要使用的模块module: {//指定要加载的规则rules: [{//test指定的是规则生效的文件test: /\.ts$/,//要使用的loaderuse: [//配置babel 适配更多浏览器{//指定加载器loader: "babel-loader",//设置babeloptions: {//设置预定义的环境presets: [[//指定环境的插件"@babel/preset-env",//配置信息{targets: {"chrome": 88,},//指定corejs的版本"corejs": "3",//使用core js的方式"usage"表示按需加载"useBuiltIns": "usage"}]]}}, "ts-loader"],//要排除的文件exclude: /node-modules/},//设置less文件的处理{test: /\.less$/,use: ["style-loader","css-loader",//引入postcss{loader: "postcss-loader",options: {postcssOptions: {plugins: [["postcss-preset-env",{browsers: "last 2 versions"}]]}}},"less-loader"]}]},plugins: [new CleanWebpackPlugin(),new HTMLWebpackPlugin({// title:"这是一个自定义title"template: "./src/index.html"})],//用来设置引用模块resolve: {extensions: [".ts", ".js"]} }
-
4.主体页面结构
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>贪吃蛇</title></head><body><div id="main"><div class="screen"><!--设置蛇的样式--><div id="snake"><div></div><!-- <div></div>--><!-- <div></div>--><!-- <div></div>--></div><!--食物--><div id="food"><div></div><div></div><div></div><div></div></div></div><div class="count"><h4 class="countText">SCORE: <span id="score">0</span></h4><h4 class="countText">LEVEL: <span id="level">1</span></h4></div></div></body>
</html>
5.CSS样式
index.less
@backColor: #cefdb5;
* {margin: 0;padding: 0;box-sizing: border-box;
}body {//background-color: @backColor;text-align: center;padding-top: 100px;//display: flex;
}#main {display: inline-block;width: 560px;height: 620px;background-color: @backColor;border: #0e0e0e 18px solid;padding: 20px;border-radius: 40px;text-align: center;.screen {height: 415px;width: 460px;margin: 12px auto auto;border: #0e0e0e 4px solid;position: relative;#snake {& > div {width: 15px;height: 15px;background-color: #000;border: 1px @backColor solid;position: absolute;}div:first-child {background-color: #67d01b;border-radius: 4px;}}#food {width: 15px;height: 15px;//background-color: red;border: 1px @backColor solid;position: absolute;left: 40px;display: flex;flex-flow: row wrap;justify-content: space-between;align-content: space-between;& > div {width: 6px;height: 6px;background-color: #0a7142;transform: rotate(45deg);}}}.count {margin-top: 50px;padding: 0 10px;display: flex;width: 100%;flex-direction: row;justify-content: space-between;font-size: 20px;font-weight: 600;h4 {display: inline-block;}}
}
6.TS逻辑
6.1 食物逻辑
export class Food {//定义一个属性表示食物对应的元素elementD: HTMLElement;constructor(elemet: HTMLElement) {//获取页面中的元素给elementD// document.getElementById("#food")!;this.elementD = elemet;this.change()}get X() {return this.elementD.offsetLeft;}get Y() {return this.elementD.offsetTop;}//修改食物位置change() {//生成一个随机的位置// console.log(this.elementD)//食物的位置最小是 0 x 最大是460 y最大是420 390 435;let top = Math.round(Math.random() * 26) * 15let left = Math.round(Math.random() * 29) * 15;this.elementD.style.left = left + 'px'this.elementD.style.top = top + 'px'}}
6.2 蛇逻辑
我们的主角,主要逻辑难点在蛇身的移动与碰撞检验
export class Snake {//表示蛇头元素head: HTMLElement;//蛇的身体bodies: HTMLCollectionelement: HTMLElementconstructor() {this.element = document.getElementById("snake")!this.head = document.querySelector("#snake>div") as HTMLElement;this.bodies = this.element.getElementsByTagName('div');}get X() {return this.head.offsetLeft}get Y() {return this.head.offsetTop}//设置蛇头的坐标set X(val: number) {if (this.X == val) {return}if (val < 0 || val > 435) {throw new Error("你撞墙了")}this.head.style.left = val + "px"}set Y(val: number) {if (this.Y == val) {return}if (val < 0 || val > 390) {throw new Error("你撞墙了")}this.head.style.top = val + "px"}//蛇增加身体addBody() {this.element.insertAdjacentHTML("beforeend", `<div></div>`)}moveBody(x: number, y: number) {//遍历所有的身体 头已经改过了 不需要改for (let i = 1; i < this.bodies.length - 1; i++) {let currentEle = this.bodies[i] as HTMLElement;let x1 = parseInt(currentEle.style.left.substring(0, currentEle.style.left.indexOf("px")))let y1 = parseInt(currentEle.style.top.substring(0, currentEle.style.top.indexOf("px")))if (this.X == x1 && this.Y == y1 && i > 1) {throw new Error("你撞上了自己的身体!")}}for (let i = this.bodies.length - 1; i > 0; i--) {let houEle = this.bodies[i] as HTMLElement;let qianEle = this.bodies[i - 1] as HTMLElement;if (i == 1) {houEle.style.left = x + 'px';houEle.style.top = y + 'px';} else {houEle.style.left = qianEle.style.left;houEle.style.top = qianEle.style.top;}}}
}
6.3 记分板逻辑
export class ScorePanel {score: number = 0;level: number = 1;scoreEle: HTMLElement;levelEle: HTMLElement;MaxLevel:number;UpScore:number;constructor(scoreEle: HTMLElement, levelEle: HTMLElement,maxLevel:number=10,upScore:number=5) {this.scoreEle = scoreEle;this.levelEle = levelEle;this.MaxLevel=maxLevel;this.UpScore=upScore;}addScore() {this.score++;this.scoreEle.innerHTML = this.score + ""if (this.score%this.UpScore==0){this.addLevel()}}addLevel() {if (this.level >= this.MaxLevel) {return}this.level++;this.levelEle.innerHTML = this.level + ""}}
6.4 游戏控制器逻辑
整个游戏的运行与控制逻辑都在这里,里面有按键触发逻辑等,是用来调度蛇与食物和记分板的一个控制类
//游戏控制器import {Food} from "./Food";
import {Snake} from "./Snake";
import {ScorePanel} from "./ScorePanel";export class GameControl {food: Food;snake: Snake;scorePanel: ScorePaneldirection: string = ""time: anyisLive: boolean = trueconstructor(food: Food, scorePanel: ScorePanel, snake: Snake) {this.food = food;this.snake = snake;this.scorePanel = scorePanel;this.init();this.run();}init() {//banging键盘按下的事件document.addEventListener("keydown", this.keydownHandler.bind(this))}/*** ArrowUp* ArrowDown* ArrowLeft* ArrowRight*/keydownHandler(event: KeyboardEvent) {let a = " ArrowUp ArrowDown ArrowLeft ArrowRight "let b = "wasd"let key = event.key;if (a.indexOf(" " + key + " ") != -1 || b.indexOf(key) != -1) {// console.log(event.key)//上的时候不可以按下//左的时候不可以按右switch (this.direction) {case "ArrowUp":case "Up":case "w":switch (key) {case "ArrowDown":case "Down":case "s":return;}breakcase "ArrowDown":case "Down":case "s":switch (key) {case "ArrowUp":case "Up":case "w":return;}breakcase "ArrowLeft":case "Left":case "a":switch (key) {case "ArrowRight":case "Right":case "d":return;}breakcase "ArrowRight":case "Right":case "d":switch (key) {case "ArrowLeft":case "Left":case "a":return;}break}this.direction = event.key// this.run()}}/*** 蛇移动方法**/run() {//获取蛇现在的坐标let x = this.snake.X;let y = this.snake.Y;let x1 = this.snake.X;let y1 = this.snake.Y;switch (this.direction) {case "ArrowUp":case "Up":case "w":// 向上移动top减少y -= 15;break;case "ArrowDown":case "Down":case "s":y += 15break;case "ArrowLeft":case "Left":case "a":x -= 15break;case "ArrowRight":case "Right":case "d":x += 15break;}try {this.snake.X = x;this.snake.Y = y;//判断是否吃到食物this.checkEat(x, y);this.snake.moveBody(x1, y1)} catch (e: any) {this.isLive = falsealert(e.message)return}// console.log(x, y)if (this.time && this.isLive) {clearTimeout(this.time)}this.time = setTimeout(this.run.bind(this), 250 - (this.scorePanel.level - 1) * 50)}checkEat(X: number, Y: number) {if (this.food.X == X && this.food.Y == Y) {//吃到食物了this.scorePanel.addScore()//重新刷新食物点位this.food.change()//蛇要增加一节this.snake.addBody()}}
}
6.5 程序入口ts
用来引入所有需要的模块,包括样式模块
import './style/index.less'
import {Food} from "./modules/Food";
import {ScorePanel} from "./modules/ScorePanel";
import {GameControl} from "./modules/GameControl";
import {Snake} from "./modules/Snake";let foodEle = document.getElementById("food")!;
let food = new Food(foodEle);
let scorePanel = new ScorePanel(document.getElementById("score")!,document.getElementById("level")!)let snake = new Snake();let gameControl = new GameControl(food,scorePanel,snake);