您现在的位置是:首页 >学无止境 >TypeScript实现贪吃蛇游戏网站首页学无止境
TypeScript实现贪吃蛇游戏
简介TypeScript实现贪吃蛇游戏
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',// 设置mode mode: 'production',// 设置mode //指定webpack打包时要使用的模块 module: { //指定要加载的规则 rules: [ { //test指定的是规则生效的文件 test: /.ts$/, //要使用的loader use: [ //配置babel 适配更多浏览器 { //指定加载器 loader: "babel-loader", //设置babel options: { //设置预定义的环境 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) * 15
let 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: HTMLCollection
element: HTMLElement
constructor() {
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: ScorePanel
direction: string = ""
time: any
isLive: boolean = true
constructor(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;
}
break
case "ArrowDown":
case "Down":
case "s":
switch (key) {
case "ArrowUp":
case "Up":
case "w":
return;
}
break
case "ArrowLeft":
case "Left":
case "a":
switch (key) {
case "ArrowRight":
case "Right":
case "d":
return;
}
break
case "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 += 15
break;
case "ArrowLeft":
case "Left":
case "a":
x -= 15
break;
case "ArrowRight":
case "Right":
case "d":
x += 15
break;
}
try {
this.snake.X = x;
this.snake.Y = y;
//判断是否吃到食物
this.checkEat(x, y);
this.snake.moveBody(x1, y1)
} catch (e: any) {
this.isLive = false
alert(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);
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。