项目搭建
我们以demo3的项目为基础,可以复制一份过来
在这个基础上添加less相关的处理
npm i -D less
然后添加postcss处理兼容性问题
npm
最后配置webpack
// 设置less文件的处理
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: "last 2 versions"
}
]
]
}
}
},
"less-loader"
]
}
能将我们写的less文件编译成css即可:
body {
background-color: #eee;
display: flex;
}
项目界面
界面效果如下:
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 id="app">
<div id="stage">
<div id="snake">
<div></div>
</div>
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div id="score-panel">
<div>SCORE: <span id="score">0</span></div>
<div>LEVEL: <span id="level">1</span></div>
</div>
</div>
</body>
</html>
样式代码
@bg-color: #ebebeb;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 20px bold "Courier";
}
#app {
display: flex;
flex-flow: column;
align-items: center;
justify-content: space-around;
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border-radius: 6px;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.4);
#stage {
width: 304px;
height: 304px;
border: 2px solid #c9c9c9;
position: relative;
#snake {
&>div {
width: 10px;
height: 10px;
background-color: blueviolet;
border: 1px solid @bg-color;
position: absolute;
}
}
#food {
width: 10px;
height: 10px;
position: absolute;
top: 100px;
left: 40px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
&>div {
width: 4px;
height: 4px;
background-color: green;
transform: rotate(45deg);
}
}
}
#score-panel {
display: flex;
justify-content: space-between;
width: 304px;
}
}
完成Food类
class Food{
// 定义一个属性表示食物所对应的元素
element: HTMLElement;
constructor() {
// 变量后使用 !:表示类型推断排除null、undefined
this.element = document.getElementById("food")!;
}
// 定义一个获取食物的X轴坐标的方法
get X() {
return this.element.offsetLeft;
}
// 定义一个获取食物的Y轴坐标的方法
get Y() {
return this.element.offsetTop;
}
// 修改食物位置
change() {
// 生成一个随机的位置
// 食物的位置最小是0,最大是290,一格是10
let left = Math.round(Math.random() * 29) * 10;
let top = Math.round(Math.random() * 29) * 10;
this.element.style.left = `${left}px`;
this.element.style.top = `${top}px`;
}
}
export default Food;
每次调用change都可以修改到食物的位置,让其在stage里随机显示
完成ScorePanel类
// 定义表示记分牌的类
class ScorePanel{
score = 0;
level = 1;
scoreEle: HTMLElement;
levelEle: HTMLElement;
// 最高等级
maxLevel: number;
// 多少分升级
upScore: number;
constructor(maxLevel: number = 10, upScore: number = 10) {
this.scoreEle = document.getElementById("score")!;
this.levelEle = document.getElementById("level")!;
this.maxLevel = maxLevel;
this.upScore = upScore;
}
// 设置一个加分方法
addScore() {
this.scoreEle.innerHTML = `${++this.score}`;
if(this.score % this.upScore === 0) {
this.levelUp();
}
}
// 提升等级
levelUp() {
if(this.level < this.maxLevel) {
this.levelEle.innerHTML = `${++this.level}`;
}
}
}
export default ScorePanel;
完成Snake类
// 定义蛇类
class Snake{
// 表示蛇头所对应的元素
head: HTMLElement;
// 蛇的身体(包含蛇头)
bodies: HTMLCollection;
// 获取蛇的容器
element: HTMLElement;
constructor() {
// 变量后使用 !:表示类型推断排除null、undefined
this.element = document.querySelector("#snake")!;
this.head = document.querySelector("#snake > div") as HTMLElement;
this.bodies = this.element.getElementsByTagName("div");
}
// 获取蛇头的X轴坐标
get X() {
return this.head.offsetLeft;
}
// 获取蛇头的Y轴坐标
get Y() {
return this.head.offsetTop;
}
// 设置蛇头的X轴坐标
set X(value: number) {
if(this.X === value) {
return;
}
// 撞墙
if(value < 0 || value > 290) {
throw new Error("蛇撞墙了");
}
// 修改x时,是在修改水平坐标, 蛇在左右移动,蛇在向左移动时,不能向右掉头, 反之亦然
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
console.log("水平方向发生了调头");
value = value > this.X ? this.X - 10 : this.X + 10;
}
// 移动身体
this.moveBody();
this.head.style.left = `${value}px`;
// 检查蛇头是否撞到身体
this.checkHeadBody();
}
// 设置蛇头的Y轴坐标
set Y(value: number) {
if(this.Y === value) {
return;
}
// 撞墙
if(value < 0 || value > 290) {
throw new Error("蛇撞墙了");
}
// 修改y时,是在修改垂直坐标, 蛇在上下移动,蛇在向上移动时,不能向下掉头, 反之亦然
if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
console.log("垂直方向发生了调头");
value = value > this.Y ? this.Y - 10 : this.Y + 10;
}
// 移动身体
this.moveBody();
this.head.style.top = `${value}px`;
// 检查蛇头是否撞到身体
this.checkHeadBody();
}
// 蛇增加身体的方法
addBody() {
// 向element添加一个div
// insertAdjacentElement 将一个给定的元素节点插入到相对于被调用的元素的给定的一个位置。
// beforeend 只在该元素当中,在该元素最后一个子孩子后面。
this.element.insertAdjacentElement("beforeend", document.createElement("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++) {
let bd = this.bodies[i] as HTMLElement;
if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
throw new Error("撞到自己了!");
}
}
}
}
export default Snake;
GameControl键盘事件以及使蛇移动
import Food from "./Food";
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
// 游戏控制器,控制其他的所有类
class GameControl {
food: Food;
snake: Snake;
scorePanel: ScorePanel;
// 方向
direction: String = "";
// 是否结束
isLive = true;
constructor() {
this.food = new Food();
this.snake = new Snake();
this.scorePanel = new ScorePanel(10, 1);
this.init();
}
// 游戏初始化
init() {
document.addEventListener("keydown", this.keydownHandler.bind(this));
this.run();
}
/**
* 按下事件
* ArrowUp Up
* ArrowRight Right
* ArrowDown Down
* ArrowLeft Left
*/
keydownHandler(event: KeyboardEvent) {
console.log(event);
// 检查是否合法
this.direction = event.key;
}
// 控制蛇移动,根据方向移动
run() {
/**
* ArrowUp Up:top -
* ArrowRight Right:left +
* ArrowDown Down:top +
* ArrowLeft Left:left -
*/
// 蛇现在的坐标
let X = this.snake.X;
let Y = this.snake.Y;
switch(this.direction){
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
}
// 检查是否吃到食物
this.checkEat(X, Y);
// 修改XY
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (e) {
alert(e);
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) {
console.log("吃到食物了!");
// 食物重置
this.food.change();
// 分数增加
this.scorePanel.addScore();
// 蛇增加一节
this.snake.addBody();
}
}
}
export default GameControl;
实例化GameControl
最后引入控制器实例化即可。
import "./style/index.less";
import GameControl from "./modules/GameControl";
new GameControl();
蛇撞墙和吃食检测效果
目录结构
整体的目录结构如下: