找回密码
 立即注册
查看: 332|回复: 0

使用webpack+ts做一个贪吃蛇游戏(四)---实现游戏功能

[复制链接]
发表于 2021-7-12 21:48 | 显示全部楼层 |阅读模式
通过分析,将整个游戏拆分成四个对象,食物、蛇、记分牌和游戏控制器

  • 食物类
    属性:食物的dom
    方法:1. 获取食物dom左上角坐标
    2. 食物被吃后,随机修改食物位置的方法
// modules/food.tsexport default class Food {    element: HTMLElement;    constructor() {        this.element = document.getElementById('food')!;    }    get X() {        return this.element.offsetLeft;    }    get Y() {        return this.element.offsetTop;    }    // 修改食物的位置    change() {        // Math.random()生成0~1的隨機數,不包括0和1,乘以29就生成0~29的随机数,不包括0和29,然后再四舍五入,就可以得到包括0~29的数字        // stage的宽高是304,减去边框为300,食物的宽高为10,一次移动一格,所以食物左上角坐标的范围是0~290,且为整十的数        const x = Math.round(Math.random() * 29) * 10;        const y = Math.round(Math.random() * 29) * 10;        // todo 食物也不应该出现在蛇的身体上        this.element.style.left = x + 'px';        this.element.style.top = y + 'px';    }}

  • 记分牌类
    属性:最大等级、升级需要的分数、分数的dom、等级的dom、分数、等级
    方法:1. 加分
    2. 升级
// modules/scorePanel.tsexport default class ScorePanel {    score = 0;    level = 1;    scoreEle: HTMLElement;    levelEle: HTMLElement;    maxLevel: number;    upScore: number;    constructor(maxLevel:number = 10, upScore:number = 5) {        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 + '';        }    }}

  • 蛇类
    属性:蛇头dom、蛇身体dom(包括蛇头)、蛇容器dom
    方法:1. 获取蛇头坐标
    2. 设置蛇头坐标(检查是否掉头、检查是否撞墙、移动蛇身体(将每一节身体都移动到前一节身体的位置上)、检查是否撞到自己的身体)
    3. 增加蛇身体(吃到食物后)
// modules/snake.tsexport default class Snake {    // 蛇的容器    element: HTMLElement;    // 蛇头    head: HTMLElement;    // 蛇的身体    bodies: HTMLCollection;    constructor() {        // 这里不加!会ts报错,因为有可能没有#snake这个dom,但我们知道有,通过加一个!阻止报错        this.element = document.getElementById('snake')!;        // querySelector方法获取的是一个Element对象,与我们设置的HTMLElement不匹配,所以这里我们使用类型断言as        this.head = document.querySelector('#snake > div') as HTMLElement;        this.head.style.background = 'red';        this.bodies = this.element.getElementsByTagName('div')!;    }    // get方法是ts类中的语法糖,你还是可以通过snake1.x的方法获取该属性,但我们可以在这个get方法中写入判断逻辑,将控制权握在开发者手里    // 获取蛇头的x坐标    get X() {        return this.head.offsetLeft;    }    get Y() {        return this.head.offsetTop;    }    set X(value: number) {        if (this.X === value) {            return;        }        // 检查是否掉头        value = this.checkIsTurn(value, this.X, 'X')        // 检查是否撞墙        this.checkWall(value)        // 移动身体        this.moveBodies()        this.head.style.left = value + 'px'        // 检查是否撞到自己的身体        this.checkBoomBody(value)    }    set Y(value: number) {        if (this.Y === value) {            return;        }        // 检查是否掉头        value = this.checkIsTurn(value, this.Y, 'Y')        // 检查是否撞墙        this.checkWall(value)        // 移动身体        this.moveBodies()        this.head.style.top = value + 'px'        // 检查是否撞到自己的身体        this.checkBoomBody(value)    }    // 检查是否撞墙    checkWall(value:number) {        if (value < 0 || value > 290) {            throw new Error('蛇撞墙了!');        }    }    // 检查是否掉头    checkIsTurn(value:number, currentValue: number, type:string) {        const firstBody = this.bodies[1] as HTMLElement;        const position = type === 'Y' ? firstBody?.offsetTop : firstBody?.offsetLeft;        if (value === position) {            // 如果发生了掉头,让蛇向反方向继续移动            if (value > currentValue) {                value = currentValue - 10            } else {                value = currentValue + 10            }        }        return value    }    // 检查是否撞到自己的身体 这个方法要在本次蛇的坐标更改后再进行判断    checkBoomBody(value:number) {        for(let i = 1; i < this.bodies.length; i++) {            const bd = this.bodies as HTMLElement            if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {                throw new Error('撞到了自己的身体!');            }        }    }    addBodies() {        this.element.insertAdjacentHTML('beforeend', "<div></div>")    }    moveBodies() {        // 后面一格身体的坐标修改为前一节身体的坐标,以此类推        // 这里for循环的停止条件是i > 0,因为蛇头的位置在外面已经设置了        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 as HTMLElement).style.left = X + 'px';            (this.bodies as HTMLElement).style.top = Y + 'px';        }    }}

  • 控制器类
    属性:蛇、食物、记分牌(引用并创建实例)、蛇移动的方向(按键方向)、游戏是否结束
    方法:1. 开始游戏(绑定键盘事件、开启定时器更改蛇的坐标)
    2. 蛇吃食物
// modules/gameController.tsimport Food from './food'import Snake from './snake'import ScorePanel from './scorePanel'export default class GameController {    food: Food;    snake: Snake;    scorePanel: ScorePanel;    // 按键的方向(蛇移动的方向)    direction: string = '';    // 游戏是否结束    isLive = true;    constructor() {        this.food = new Food()        this.snake = new Snake()        this.scorePanel = new ScorePanel()        // 创建对象后即游戏开始        this.init()    }    init() {        console.log('游戏开始')        // 绑定键盘事件        document.addEventListener('keydown', this.keydownHandler.bind(this))        this.run()    }    /**     * ArrowUp Up     * ArrowDown Down     * ArrowLeft Left     * ArrowRight Right     * 上面是键盘按上下左右后返回的event.key     * 由于IE浏览器不合群,所以事件名称和其他浏览器有区别     *     */    keydownHandler(event: KeyboardEvent) {        // 根据event.key来判断上下左右        this.direction = event.key;    }    run() {        // 获取蛇现在的坐标        let X = this.snake.X;        let Y = this.snake.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;            default:        }        // 检查蛇是否吃到了食物        this.checkEat(X, Y)        try {            this.snake.X = X;            this.snake.Y = Y;        } catch (e) {            alert(e.message+ 'Game Over!')            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.snake.addBodies()            this.food.change()            this.scorePanel.addScore()        }    }}
以上,整个贪吃蛇的游戏就完成了,大家可以在以下链接下载完成代码
https://gitee.com/loversong/snake
npm inpm run start
命令行执行以上代码即可体验。
以上代码还遗留了一个bug,就是在移动食物的时候,没有考虑到食物随机分布到蛇身体上的情况,大家可以自己尝试补一下。
还有什么问题,欢迎评论区指正。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 21:11 , Processed in 0.125492 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表