记录一下学习ts的过程
学习完ts的基本语法后,使用ts+oop思想来写一个贪吃蛇的网页小游戏
先看看效果图
一. 环境准备
编译器:WebStorm
- 创建一个GluttonousSnake目录,作为项目的根目录
- 打开命令行窗口,使用npm初始化项目,代码如下:
npm init -y
- 在项目中导入ts
npm install typescript -D
- ts编译配置
创建tsconfig.json文件,这个文件是ts编译器的配置文件,文件内容如下:
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"sourceMap": true,
"noEmitOnError": true
},
"exclude": [
"node_modules"
]
}
二. 创建结构
在根目录下创建index.html文件,作为游戏主页,创建index.css文件控制样式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>贪吃蛇</title>
<link rel="stylesheet" type="text/css" href="./index.css">
<script type="module" src="./index.js"></script>
</head>
<body>
<div id="main">
<!-- 游戏区域-->
<div id="stage">
<!-- 蛇-->
<div id="snake">
<div></div>
</div>
<!-- 食物-->
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- 面板区域-->
<div id="panel">
<div>SCORE:<span id="score">0</span></div>
<div>LEVEL:<span id="level">1</span></div>
</div>
</div>
</body>
</html>
index.css
*{
box-sizing: border-box;
}
#main{
width: 360px;
height: 420px;
background-color: #b7d4a8;
border:10px #000 solid;
border-radius: 20px;
margin: 100px auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
body{
font:bold 20px "Courier"
}
#stage{
width: 304px;
height: 304px;
border:2px #000 solid;
position: relative;
overflow: hidden;
}
#snake>div{
width: 10px;
height: 10px;
border:1px solid #b7d4a8;
background-color: #000;
border-radius: 1px;
position: absolute;
top:-20px;
}
#snake>div:first-child{
top:10px;
left: 10px;
transform: rotate(45deg);
border-radius: 5px;
}
#food{
width: 10px;
height: 10px;
border:1px solid #b7d4a8;
border-radius: 1px;
position: absolute;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
align-content: space-evenly;
}
#food>div{
width: 4px;
height: 4px;
background-color: #ce0f0f;
transform: rotate(45deg);
border-radius: 1px;
}
#panel{
width: 300px;
display: flex;
justify-content:space-between;
}
三. 编写模块
贪吃蛇游戏的结构搭建好了,现在来编写ts模块,实现游戏的功能。
在根目录下创建modules目录,用来存放ts文件。
在命令行中运行如下命令,开启ts编译监听
tsc -w
Food.ts
//食物类
class Food{
score:number=1;
element:HTMLElement;
constructor() {
this.element=document.getElementById('food');
this.changePosition();
}
//获取食物元素的左偏移量
get X(){
return this.element.offsetLeft;
}
//获取食物元素的上偏移量
get Y(){
return this.element.offsetTop;
}
//随机改变食物位置
changePosition(){
//最小偏移量为0,最大偏移量为舞台宽度-食物宽度
let stage=document.getElementById('stage')
this.element.style.top=10*Math.round(Math.random()*(stage.clientHeight-this.element.offsetHeight)/10)+'px';
this.element.style.left=10*Math.round(Math.random()*(stage.clientWidth-this.element.offsetWidth)/10)+'px';
}
}
export default Food;
Snake.ts
class Snake {
//存放蛇的容器
element: HTMLElement;
//蛇头部分
headEle: HTMLElement;
//身体部分
bodies: HTMLCollection;
constructor() {
this.element = document.getElementById('snake')
this.headEle = this.element.children[0] as HTMLElement;
// this.bodies=this.element.getElementsByClassName('body');
this.bodies = this.element.getElementsByTagName('div');
}
//获取蛇头坐标
get X() {
return this.headEle.offsetLeft;
}
get Y() {
return this.headEle.offsetTop;
}
set X(value: number) {
//判断是否掉头
if (this.bodies[1]&&value === (this.bodies[1] as HTMLElement).offsetLeft) {
if(value>this.X){
value=this.X-10
}else if(value<this.X){
value=this.X+10
}
}
if (this.X === value) return
this.moveBody()
this.headEle.style.left = value + 'px';
}
set Y(value: number) {
//判断是否掉头
if (this.bodies[1]&&value === (this.bodies[1] as HTMLElement).offsetTop) {
if(value>this.Y){
value=this.Y-10
}else if(value<this.Y){
value=this.Y+10
}
}
if (this.Y === value) return
this.moveBody()
this.headEle.style.top = value + 'px';
}
add() {
let div = document.createElement('div')
this.element.appendChild(div);
}
//移动身体
moveBody() {
for (let i = this.bodies.length - 1; i > 0; i--) {
let div = this.bodies[i] as HTMLElement;
let frontDiv = this.bodies[i - 1] as HTMLElement;
div.style.top = frontDiv.style.top;
div.style.left = frontDiv.style.left;
}
}
//检查是否撞到自己
checkSelf(){
for (let i = 1; i <this.bodies.length ; i++) {
let div = this.bodies[i] as HTMLElement;
if(this.headEle.offsetLeft===div.offsetLeft&&this.headEle.offsetTop===div.offsetTop){
throw new Error('蛇撞到了自己!');
}
}
}
//检查是否撞墙
checkBorder(X:number,Y:number){
// this.X=X>290?0:X;
// // this.X=X<0?290:X;
// this.Y=Y>290?0:Y;
// // this.Y=Y<0?290:Y;
if (X >= 300 || X < 0 || Y>=300 ||Y<0) {
throw new Error('蛇撞墙了')
}
}
}
export default Snake
ScorePanel.ts
//分数面板类
class ScorePanel{
//用来记录分数和等级
score:number=0;
level:number=1;
//等级上限
levelMax:number;
//每级提升的分数
upScore:number;
//用来指向分数和等级面板的dom元素
scoreEle:HTMLElement;
levelEle:HTMLElement;
constructor(levelMax:number=10,upScore:number=10){
this.scoreEle=document.getElementById('score');
this.levelEle=document.getElementById('level');
this.levelMax=levelMax;
this.upScore=upScore;
}
//加分的方法
addScore(){
this.scoreEle.innerText=++this.score+'';
if(this.score%this.upScore===0){
this.levelUp()
}
}
//升级的方法
levelUp(){
//设置等级上限
if(this.level<this.levelMax)
this.levelEle.innerText=++this.level+'';
}
}
export default ScorePanel;
GameControl.ts
//游戏控制类
import Snake from "./Snake.js";
import ScorePanel from "./ScorePanel.js";
import Food from "./Food.js";
class GameControl {
//蛇
snake: Snake;
//食物
food: Food;
//记分牌
scorePanel: ScorePanel;
//保存蛇的运动方向
direction: string = '';
//定时器对象
timer;
//记录是否存活
isLive:boolean = true;
lastObj:HTMLElement;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel(10,2);
this.init();
this.snakeMove();
}
//初始化游戏调用方法
init() {
document.addEventListener('keydown', this.keydownHandler);
document.addEventListener('keydown', this.spaceKeyHandler);
}
//监听方向键按下函数
keydownHandler = (event: KeyboardEvent) => {
let key = event.key;
if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight') {
this.direction = key;
}
}
//监听暂停按键(空格)
spaceKeyHandler= (event: KeyboardEvent) => {
//暂停游戏
if(event.keyCode==32){
this.isLive=!this.isLive;
if(!this.isLive) {
clearTimeout(this.timer);
document.removeEventListener('keydown',this.keydownHandler);
// this.direction='';
} else{
this.snakeMove();
document.addEventListener('keydown', this.keydownHandler);
}
}
}
//控制蛇的运动
snakeMove = () => {
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 {
this.lastObj=this.snake.element.cloneNode(true) as HTMLElement;
// //检查是否撞到边界
// this.snake.checkBorder(X,Y);
X=X>290?0:X;
X=X<0?290:X;
Y=Y>290?0:Y;
Y=Y<0?290:Y;
this.snake.X = X;
this.snake.Y = Y;
//检查是否撞到自身
this.snake.checkSelf();
} catch (e) {
this.snake.element.remove()
this.snake.element=this.lastObj;
document.getElementById('stage').appendChild(this.lastObj)
alert(e.message + ' GAME OVER')
this.isLive = false;
}
clearTimeout(this.timer);
this.isLive && (this.timer = setTimeout(this.snakeMove, 250 - (this.scorePanel.level - 1) * 20));
}
//检查蛇是否吃到食物
checkEat(X: number, Y: number) {
if (X === this.food.X && Y === this.food.Y) {
//重置食物位置
this.food.changePosition();
//加分
this.scorePanel.addScore();
console.log(1)
//给蛇的增加身体
this.snake.add();
}
}
}
export default GameControl
在根目录创建index.ts文件,导入GameControl
index.ts
import GameControl from "./modules/GameControl.js";
(function () {
window.onload=function () {
new GameControl()
}
})()
四. 效果演示
在浏览器上打开index.html文件,即可开始游戏
源码放在了gitee上: https://gitee.com/li-linjie0914/gluttonous-snake