前言
贪吃蛇小练习
1 搭建项目结构
设置配置文件
下载依赖
配置less
下载依赖
修改配置文件(webpack.config.js)
引入less文件
下载兼容css版本的依赖
修改配置文件(webpack.config.js)
2 编写代码
编写代码框架(html)
编写样式(less)
编写代码逻辑(ts)
编写食物(Food)模块
编写记分牌(ScorePanel)模块
编写蛇(Snake)模块
编写键盘控制(GameControl)模块
总结
前言
用typescript做一个贪吃蛇的小游戏
贪吃蛇小练习
1 搭建项目结构
设置配置文件
将前面配置好的tsconfig.json,webpack.config.js,package.json文件(文末)复制粘贴到新文件中并根据项目做简单修改
下载依赖
npm -i
配置less
下载依赖
npm i -D less less-loader css-loader style-loader
修改配置文件(webpack.config.js)
引入less文件
下载兼容css版本的依赖
npm i -D postcss postcss-loader postcss-preset-env
修改配置文件(webpack.config.js)
至此,项目结构搭建完成
2 编写代码
输入npm start开启监听便于实时更新代码
编写代码框架(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="main">
<!-- 设置游戏的舞台 -->
<div id="stage">
<!-- 设置蛇 -->
<div id="snake">
<!-- snake内部的div,表示蛇的各部分 -->
<div>
</div>
</div>
<!--设置食物-->
<div id="food">
<!-- 添加4个小div,设置食物的样式 -->
<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>
<!-- <script src="../dist/bundle.js"></script> -->
</body>
</html>
index.ts文件引入less样式
编写样式(less)
//设置变量
@bg-color:yellow;
//清除默认样式
*{
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 blue;
//设置圆角
border-radius: 20px;
//开启弹性盒模型
display: flex;
//设置主轴的方向
flex-flow: column;
//设置辅轴的对齐方式
align-items: center;
//设置主轴的对齐方式
justify-content: space-around;
//游戏舞台
#stage{
width: 304px;
height: 304px;
border: 2px solid black;
//开启相对定位
position: relative;
//设置蛇的样式
#snake{
&>div{
width: 10px;
height: 10px;
background-color: red;
border: 1px solid @bg-color;
//开启绝对定位 使蛇可以移动
position: absolute;
}
}
//设置食物
#food{
width: 10px;
height: 10px;
position: absolute;
left:40px;
top:100px;
//background-color: red;
display: flex;
//设置横轴为主轴,wrap表示自动换行
flex-flow: row wrap;
//设置主轴和侧轴的空白空间分配到元素之间
justify-content: space-between;
align-content: space-between;
&>div{
width: 5px;
height: 5px;
background-color: green;
border: 1px solid yellow;
//使4gediv旋转45度
transform: rotate(45deg);
}
}
}
//记分牌
#score-panel{
width: 300px;
display: flex;
//设置主轴的对齐方式
justify-content: space-between;
}
}
编写代码逻辑(ts)
按不同功能分成多个模块,便于代码的编写及修改。
将Food.ts,ScorePanel.ts,Snake.ts模块导入到GameControl.ts模块,由该模块来统一整合。
index.ts文件引入各模块
编写食物(Food)模块
//定义类
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(){
//生成一个随机的位置
//食物的位置最小是0,最大是290 300-10=290
//蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是10的倍数
//Math.round表示四舍五入
//Math.floor(Math.random()*30)*10;
//Math.floor表示向下取整
let top=Math.round(Math.random()*29)*10;//既包括0,又包括290,而且都是10的倍数
let left=Math.round(Math.random()*29)*10;
this.element.style.left=left+'px';
this.element.style.top=top+'px';
}
}
//测试代码
// const food=new Food()
// console.log(food.X,food.Y);
// food.change();
// console.log(food.X,food.Y);
export default Food;
编写记分牌(ScorePanel)模块
//定义表示记分牌的类
class ScorePanel{
//score和level用来记录分数和等级
score=0;
level=1;
//分数和等级所在的元素,在构造函数中进行初始化
scoreEle:HTMLElement;
levelEle:HTMLElement;
//设置变量限制等级
maxLevel:number;
//设置一个变量表示多少分时升级
upScore:number;
constructor(maxLevel:number=10,upScore:number=10){//不传默认10,穿了多少就多少
this.scoreEle=document.getElementById("score")!;
this.levelEle=document.getElementById("level")!;
this.maxLevel=maxLevel;
this.upScore=upScore;
}
//设置一个加分的方法
addScore(){
//使分数自增
//this.score++;
this.scoreEle.innerHTML=++this.score+'';//装换成字符串,innerHTML接收字符串
//判断分数是多少
if(this.score%this.upScore===0){
this.levelUp();
}
}
//提升等级的方法
levelUp(){
if(this.level<=this.maxLevel){
this.levelEle.innerHTML=++this.level+'';
}
}
}
//测试代码
//const scorePanel=new ScorePanel(1,0);
// scorePanel.addScore();
// scorePanel.addScore();
// scorePanel.addScore();
// for(let i=0;i<9;i++)
// {
// scorePanel.addScore();
// }
export default ScorePanel;//改为默认模块暴露出去
编写蛇(Snake)模块
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;
}
//获取蛇的Y轴坐标
get Y(){
return this.head.offsetTop;
}
//设置蛇头的坐标
set X(value:number){
//如果新值和旧值相同,则直接返回不在修改
if(this.X===value){
return;
}
//x的值的合法范围0-290
if(value<0||value>290){
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了");
}
//修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动式时,不能向右掉头,反之亦然
if(this.bodies[1]&&(this.bodies[1]as HTMLElement).offsetLeft===value){
//console.log("发生了水平掉头");
//如果发生了掉头,则应让蛇向反方向移动
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;
}
//y的值的合法范围0-290
if(value<0||value>290){
//进入判断说明蛇撞墙了
throw new Error("蛇撞墙了");
}
//修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动式时,不能向下掉头,反之亦然
if(this.bodies[1]&&(this.bodies[1]as HTMLElement).offsetTop===value){
//console.log("发生了垂直掉头");
//如果发生了掉头,则应让蛇向反方向移动
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(){
//将后边的身体设置为前边身体的位置
// 第4节=第3节的位置
// 第3节=第2节的位置
// 第2节=第1节的位置
//遍历获取所有的身体
for(let i=this.bodies.length-1;i>0;i--)
{
//获取前边身体的位置
let X=(this.bodies[i-1]as HTMLElement).offsetLeft;//没加类型断言报错是因为bodies类型是Element,另外一个是htmlelement
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 Snake from "./snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏的控制器,控制其他所有的类
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.keydownHandle.bind(this));//加了bind使得括号里的this指向keydownHandle
//调用run方法。使蛇移动,没开定时器时,蛇只动一次,因为只调用一次
this.run();
}
/*
ArrowUp Up
ArrowDown Down
ArrowLeft Left
ArrowRight Right
*/
//创建一个键盘按下的响应函数
keydownHandle(event:KeyboardEvent){
//console.log(this);没加bind之前 this指向是document
//需要检查event.key的值是否合法(用户是否按了正确的按键)
//修改direction属性
this.direction=event.key;
}
//创建一个控制蛇移动的方法
run(){
//根据方向(this.direction)来使蛇的位置改变
/*
向上:top减少
向下:top增加
向左: left减少
向右;left增加
*/
//获取蛇现在的坐标
let X=this.snake.X;
let Y=this.snake.Y;
//根据按键方向来修改x值和y值
switch(this.direction){
case "ArrowUp":
case "Up":
Y-=10;
break;
case "ArrowDown":
case "Down":
Y+=10;
break;
case "ArrowLeft":
case "Left":
X-=10;
break;
case "ArrowRight":
case "Right":
X+=10;
break;
}
//检查蛇是否吃到了食物
this.checkEat(X,Y);
//修改蛇的x值和y值
try{
this.snake.X=X;
this.snake.Y=Y;
}catch(e:any){
//进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
alert(e.message+"GAME OVER");
//将isLive设置为false
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;
至此,项目完成,输入npm run build即可运行代码
注:所需的配置文件如下
tsconfig.json
{
"compilerOptions": {
"module": "es2015",
"target": "es2015",
"strict": true,
"noEmitOnError": true
}
}
package.json
{
"name": "part2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode development",
"start": "webpack serve --open --mode development "
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.19.3",
"@babel/preset-env": "^7.19.4",
"babel-loader": "^8.2.5",
"clean-webpack-plugin": "^4.0.0",
"core-js": "^3.25.5",
"css-loader": "^6.7.1",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"postcss": "^8.4.18",
"postcss-loader": "^7.0.1",
"postcss-preset-env": "^7.8.2",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
}
webpack.config.js
//引入一个包
const path=require("path");
//引入html插件
const HTMLWebpackPlugin=require('html-webpack-plugin');
//引入clean插件
const {CleanWebpackPlugin}=require('clean-webpack-plugin');
const { resolve } = require("path");
//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:{
"edge":"106",
"ie":"11",
"chrome":"99"
},
//指定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"//loader执行顺序从下往上
]
}
]
},
//配置webpack插件
plugins:[
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
//title:"这是一个自定义的title"
template:"./src/index.html"//以他作为模板生成js文件
}),
],
//用来设置引用的模块
resolve:{
extensions:['.ts','.js']//以.ts ,.js结尾的文件都可以作为模块来使用
}
}
总结
做了小练习来熟悉typescript,对typescript语法及使用有了进一步的了解,提高了编程能力。