typescript 做一个贪吃蛇小游戏
搭建环境
创建 tscofig.json 文件
配置如下
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"strict": true,
"outDir": "./dist",
"noEmitOnError": true
}
}
创建 webpack.config.js 文件
-
安装 webpack npm i webpack webpack-cli -D
-
配置 配置 webpack 项目工程化,配置项目运行打包,兼容,处理 .ts .css .less html 文件 -
安装插件 npm i html-webpack-plugin webpack-dev-server -D
webpack-dev-server 作用
webpack 内部服务器,可以在开发阶段项目自动运行,比如修改了代码,会检测到改动并且自动运行,不需要每次都手动运行查看,利于开发效率
html-webpack-plugin
html 插件,可用来提供 html 模板,打包后的 dist 文件中的 html 会根据这个模板生成
配置如下
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlWebpackPlugin({
template: './src/index.html',
filename: './index.html'
})
module.exports = {
mode: 'development',
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
environment: {
arrowFunction: false,
const: false
}
},
module: {
rules: [{
test: /\.ts$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
"@babel/preset-env",
{
targets: {
"chrome": "58"
},
"corejs": "3",
"useBuiltIns": "usage"
}
]
]
}
}, 'ts-loader'
],
exclude: /node_modules/
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: 'last 2 versions'
}
]
]
}
}
},
"less-loader"
]
}
]
},
plugins: [htmlPlugin],
resolve: {
extensions: ['.ts', '.js']
}
}
创建 package.json 文件
在项目名称是英文的情况下使用
npm init -y
如果项目名称不是英文,使用如下命令,给它设置一个英文名称
npm init
安装项目所需的全部开发依赖,如下
配置就说这么多,下面开始项目代码
采用的是结构与数据分离
先布局基本样式 在项目根目录下创建 src 文件夹,在src 文件夹下创建 index.html 和 index.ts 文件
./src/index.html
<!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>贪吃蛇</title>
</head>
<body>
<div class="box">
<div class="top">
// 蛇
<div id="snake">
// 蛇的每一节
<div></div>
</div>
// 食物
<div id="food"></div>
</div>
// 记分牌
<div class="bottom">
<div>SCORE:<span id="score">3</span></div>
<div>LEVEL:<span id="level">1</span></div>
</div>
</div>
</body>
</html>
在 src 目录下创建 style 文件夹存放样式文件 index.less
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: bold 20px "Courier";
}
.box {
width: 360px;
height: 420px;
background-color: #b7d4a8;
border: 10px solid #000;
margin: 100px auto;
border-radius: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
}
.top {
width: 304px;
height: 304px;
border: 2px solid #000;
position: relative;
}
.bottom {
width: 304px;
display: flex;
justify-content: space-between;
}
#snake {
position: relative;
}
#snake div {
width: 10px;
height: 10px;
background-color: black;
border: 1px solid #b7d4a8;
position: absolute;
top: 0;
left: 0;
}
#food {
width: 10px;
height: 10px;
background-color: black;
position: absolute;
border: 1px solid #b7d4a8;
}
在 index.ts 文件中引入样式
import './style/index.less'
开始业务逻辑代码
在 src 文件下创建 modules 文件夹,存放每个类
Food.ts , 控制食物随机出现的位置
class Food {
element: HTMLElement
constructor() {
this.element = document.getElementById('food')!
}
get X() {
return this.element.offsetLeft
}
get Y() {
return this.element.offsetTop
}
change() {
this.element.style.top = Math.round(Math.random() * 29) * 10 + 'px'
this.element.style.left = Math.round(Math.random() * 29) * 10 + 'px'
}
}
export default Food
创建 ScorePanel.ts,控制记分牌的变化
class ScorePanel {
score = 0
level = 1
scoreEle: HTMLElement
levelEle: HTMLElement
maxLevel: number
maxScore: number
constructor(maxLevel: number = 10, maxScore: number = 10) {
this.scoreEle = document.getElementById('score')!
this.levelEle = document.getElementById('level')!
this.maxLevel = maxLevel
this.maxScore = maxScore
}
addScore() {
this.score++
this.scoreEle.innerHTML = this.score + ''
if (this.score % this.maxScore === 0) {
this.levelUp()
}
}
levelUp() {
if (this.level < this.maxLevel) {
this.level++
this.levelEle.innerHTML = this.level + ''
}
}
}
export default ScorePanel
创建 Snake.ts,控制蛇的长度/位置/是否撞墙或者撞到自己
class Snake {
head: HTMLElement
bodies: HTMLCollection
element: HTMLElement
constructor() {
this.element = document.getElementById('snake')!
this.head = document.querySelector('#snake > div')!
this.bodies = this.element.getElementsByTagName('div')
}
get X() {
return this.head.offsetLeft
}
get Y() {
return this.head.offsetTop
}
set X(value: number) {
if (this.X === value) return
if (value < 0 || value > 290) {
throw new Error("蛇撞墙了");
}
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
if (value > this.X) {
value = this.X -10
} else {
value = this.X + 10
}
}
this.moveBody()
this.head.style.left = value + 'px'
this.checkHeadBody()
}
set Y(value: number) {
if (this.Y === value) return
if (value < 0 || value > 290) {
throw new Error("蛇撞墙了");
}
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
if (value > this.Y) {
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() {
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;
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}
checkHeadBody() {
for (let i = 1; i < this.bodies.length; i++){
if (this.X === (this.bodies[i] as HTMLElement).offsetLeft && this.Y === (this.bodies[i] as HTMLElement).offsetTop) {
throw new Error("撞到自己了");
}
}
}
}
export default Snake
创建 GameControl.ts,控制蛇的移动,把蛇、食物、记分牌联系到一块
import Snake from "./snake";
import Food from "./food";
import ScorePanel from "./ScorePanel";
class GameControl {
snake: Snake
food: Food
scorePanel: ScorePanel
direction: string = 'Right'
isLive: boolean = true
constructor() {
this.snake = new Snake()
this.food = new Food()
this.scorePanel = new ScorePanel()
this.init()
}
init() {
document.addEventListener('keydown', this.keydownHandler.bind(this))
this.run()
}
keydownHandler(e: KeyboardEvent) {
this.direction = e.key
}
run() {
let X = this.snake.X
let Y = this.snake.Y
switch (this.direction) {
case "ArrowUp":
case "Up":
Y -= 10
break;
case "ArrowDown":
case "Down":
Y += 10
break;
case "ArrowRight":
case "Right":
X += 10
break;
case "ArrowLeft":
case "Left":
X -= 10
break;
default:
break;
}
this.checkEat(X, Y)
try {
this.snake.X = X
this.snake.Y = Y
} catch (e) {
alert(' OVER!')
this.isLive = false
}
this.isLive && 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()
this.snake.addBody()
}
}
}
export default GameControl
在 src 下的 index.ts 中引入 GameControl 并且实例化,就可以运行代码
import GameControl from './modules/GameControl'
new GameControl()
gitee 项目地址: https://gitee.com/jinyang465/snake-demo
|