1.配置文件
配置文件写过一次之后,可以复制粘贴使用,修改部分细节就可以了。
package.json
{
"name": "snake",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/preset-env": "^7.12.7",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.8.0",
"css-loader": "^5.0.1",
"html-webpack-plugin": "^4.5.0",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"postcss": "^8.1.13",
"postcss-loader": "^4.1.0",
"postcss-preset-env": "^6.7.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.0.11",
"typescript": "^4.1.2",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
}
}
tsconfig.json
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
webpack.config.js
// 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
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,
const: false
}
},
// 指定webpack打包时要使用模块
module: {
// 指定要加载的规则
rules: [
{
// test指定的是规则生效的文件
test: /\.ts$/,
// 要使用的loader
use: [
// 配置babel
{
// 指定加载器
loader:"babel-loader",
// 设置babel
options: {
// 设置预定义的环境
presets:[
[
// 指定环境的插件
"@babel/preset-env",
// 配置信息
{
// 要兼容的目标浏览器
targets:{
"chrome":"58",
"ie":"11"
},
// 指定corejs的版本
"corejs":"3",
// 使用corejs的方式 "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"
]
}
]
},
// 配置Webpack插件
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
// title: "这是一个自定义的title"
template: "./src/index.html"
}),
],
// 用来设置引用模块
resolve: {
extensions: ['.ts', '.js']
}
};
然后使用命令:npm install 下载依赖
然后创建 src 文件夹 创建index.html文件和index.ts文件
然后使用命令:npm run build 打包
下载样式解析器:npm i -D less less-loader css-loader style-loader
下载postcss做样式兼容:npm i -D postcss postcss-loader postcss-preset-env
在webpack.config.js做如下配置:
// 设置less文件的处理
{
test: /\.less$/,
use:[
"style-loader",
"css-loader",
// 引入postcss
{
loader: "postcss-loader",
options: {
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers: 'last 2 versions'
}
]
]
}
}
2.搭建基础样式和结构
src/index.html
<body>
<div id="main">
<!-- 游戏舞台 -->
<div id="stage">
<!-- 设置蛇 -->
<div id="snake">
<!-- 蛇体 -->
<div></div>
<!-- 食物 -->
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<!-- 设置游戏记分牌 -->
<div id="score-panel">
<div>
SCORE:<span id="score">0</span>
</div>
<div id="level">
LEVEL:<span id="level"></span>1</span>
</div>
</div>
</div>
</body>
src/style/index.less
//设置变量
@bg-color: #b7d4a8;
//清除默认样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: bold 20px "Courier";
}
//主窗口的样式
#main {
width: 360px;
height: 420px;
background-color: @bg-color;
margin: 100px auto;
border: 10px solid black;
border-radius: 15px;
display: flex;
// flex-flow: column;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
// 游戏舞台样式
#stage {
width: 304px;
height: 304px;
border: 2px solid black;
position: relative;
// margin: 10px auto;
// 设置蛇的样式
#snake {
& > div {
width: 10px;
height: 10px;
background-color: black;
border: 1px solid @bg-color;
position: absolute;
}
}
// 设置食物
#food {
width: 10px;
height: 10px;
position: absolute;
left: 50px;
top: 80px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
& > div {
width: 4px;
height: 4px;
background-color: red;
transform: rotate(45deg);
}
}
}
//游戏记分牌
#score-panel {
width: 300px;
display: flex;
flex-direction: row;
justify-content: space-around;
}
3.实现food类
Food.ts
//定义食物类
class Food {
//定义一个属性表示食物所对应的元素
element: HTMLElement;
constructor() {
// 将页面中的food元素将其赋值给element
this.element = document.getElementById("food")!;
}
// 定义获取食物X轴坐标方法
get X() {
return this.element.offsetLeft;
}
// 定义获取食物Y轴坐标方法
get Y() {
return this.element.offsetTop;
}
//修改食物的位置
change() {
//随机生成
//舞台长度是300,食物的位置最小是0,最大是290
//蛇移动一次是一格,一格是10,所以要求食物的距离是整10
let top = Math.round(Math.random() * 29) * 10; //round向上取整,取得到1
let left = Math.round(Math.random() * 29) * 10;
this.element.style.left = left + "px";
this.element.style.top = top + "px";
}
}
export default Food;
4.实现scorePanel类
scorePanel.ts
//定义记分牌类
class scorePanel {
//分数和等级
score = 0;
level = 1;
//分数和等级所在的元素,在构造函数中进行优化
scorefile: HTMLElement;
levelEle: HTMLElement;
//设置一个变量限制等级
maxLevel: number;
//设置一个变量表示多少分升一级
upScore:number;
constructor(maxLevel: number = 10,upScore:number=10) {
this.scorefile = document.getElementById("score")!;
this.levelEle = document.getElementById("level")!;
this.maxLevel = maxLevel;
this.upScore=upScore;
}
//设置一个加分的方法
addScore() {
//使积分自增
this.score++;
this.scorefile.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;
index.ts
import "./style/index.less";
import Food from "./moduls/Food";
import scorePanel from "./moduls/ScorePanel";
5.初步完成snake类
Snake.ts
class Snake{
//表示蛇头的元素
head:HTMLElement;
//表示蛇身体(包括蛇头)
bodies:HTMLCollection;
//表示蛇的容器
element:HTMLElement;
constructor(){
this.element=document.getElementById("#snake")!;
this.head=document.getElementById("#snake>div") as HTMLElement;
this.bodies=this.element.getElementsByTagName("div");
}
//获取蛇的坐标(蛇头坐标)
get X(){
return this.head.offsetLeft;
}
get Y(){
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value:number){
this.head.style.left=value+'px'
}
set Y(value:number){
this.head.style.top=value+'px';
}
//蛇增加身体的方法
addBody(){
//向element中添加一个div
this.element.insertAdjacentHTML('beforeend','<div></div>');
}
}
export default Snake;
6.键盘事件GameControl
import Food from "./Food";
import scorePanel from "./ScorePanel";
import Snake from "./Snake";
class GameControl {
//蛇
snake: Snake;
//食物
food: Food;
//记分牌
scorePanel: scorePanel;
//创建一个属性来存储蛇移动的方向
direction: string = "";
//创建一个属性用来记录游戏是否结束
isLive = 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(event: KeyboardEvent) {
//需要验证event.key是否合法,用户是否按下了正确的按键
//修改direction的属性
this.direction = event.key;
console.log(event.key);
}
//获取蛇现在的坐标
run() {
let X = this.snake.X;
let Y = this.snake.Y;
switch (this.direction) {
case "ArrowUp":
Y -= 10;
break;
case "ArrowDown":
Y += 10;
break;
case "ArrowLeft":
X -= 10;
break;
case "ArrowRight":
X += 10;
break;
}
//判断蛇是否吃到食物,调用吃到食物的方法
this.checkEat(X,Y);
try {
//修改蛇的X和Y
this.snake.X = X;
this.snake.Y = Y;
} catch (error) {
alert(error);
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;
7.完善snake类
1.吃到食物之后,蛇体跟随增加,分数增加,食物变换位置
2.蛇撞墙游戏结束,蛇撞到自己身体,游戏结束
3.蛇在一个方向移动时不能掉头
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(value:number){
//蛇在移动时只会改变一个方向的坐标,另一个不改变
//如果新值和旧值相等,就直接返回不再修改
if(this.X===value){
return;
}
//蛇的合法值在0-290
if(value<0||value>290){
throw new Error("蛇撞墙了!!")
}
//修改x时,是在修改水平坐标,蛇在左右移动时,如果在左移动就不能向右掉头。反之亦然。
if(this.bodies[1]&&(this.bodies[1] as HTMLElement).offsetLeft===value){
// value大于当前的X,说明往右掉头了
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;
}
//蛇的合法值在0-290
if(value<0||value>290){
throw new Error("蛇撞墙了!!")
}
//防止掉头
if(this.bodies[1]&&(this.bodies[1] as HTMLElement).offsetTop===value){
// value大于当前的Y,说明往上掉头了
if(value>this.Y){
value=this.Y-10
}else{
value=this.Y+10
}
}
//移动身体
this.movebody();
this.head.style.top=value+'px';
//检查是否撞到身体
this.checkheadbody()
}
//蛇增加身体的方法
addBody(){
//向element中添加一个div
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;
console.log(X,Y);
//将值设置在当前的身体上
(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;
贪吃蛇项目就到这里啦!如果大家想要项目源文件的话,一键三连,评论区回复“学习”,看到就会发你哦