使用TypeScript实现一个贪吃蛇网页小游戏

记录一下学习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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值