使用webpack+ts做一個(gè)貪吃蛇游戲(四)---實(shí)現(xiàn)游戲功能

通過(guò)分析裆熙,將整個(gè)游戲拆分成四個(gè)對(duì)象端礼,食物、蛇入录、記分牌和游戲控制器

  • 食物類
    屬性:食物的dom
    方法:1. 獲取食物dom左上角坐標(biāo)
    2. 食物被吃后蛤奥,隨機(jī)修改食物位置的方法
// modules/food.ts
export 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的隨機(jī)數(shù),不包括0和1僚稿,乘以29就生成0~29的隨機(jī)數(shù)凡桥,不包括0和29,然后再四舍五入蚀同,就可以得到包括0~29的數(shù)字
        // stage的寬高是304唬血,減去邊框?yàn)?00,食物的寬高為10唤崭,一次移動(dòng)一格拷恨,所以食物左上角坐標(biāo)的范圍是0~290,且為整十的數(shù)
        const x = Math.round(Math.random() * 29) * 10;
        const y = Math.round(Math.random() * 29) * 10;

        // todo 食物也不應(yīng)該出現(xiàn)在蛇的身體上

        this.element.style.left = x + 'px';
        this.element.style.top = y + 'px';
    }
}
  • 記分牌類
    屬性:最大等級(jí)谢肾、升級(jí)需要的分?jǐn)?shù)腕侄、分?jǐn)?shù)的dom、等級(jí)的dom芦疏、分?jǐn)?shù)冕杠、等級(jí)
    方法:1. 加分
    2. 升級(jí)
// modules/scorePanel.ts
export 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()
        }
    }

    // 升級(jí)
    levelUp() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level + '';
        }
    }
}
  • 蛇類
    屬性:蛇頭dom、蛇身體dom(包括蛇頭)酸茴、蛇容器dom
    方法:1. 獲取蛇頭坐標(biāo)
    2. 設(shè)置蛇頭坐標(biāo)(檢查是否掉頭分预、檢查是否撞墻、移動(dòng)蛇身體(將每一節(jié)身體都移動(dòng)到前一節(jié)身體的位置上)薪捍、檢查是否撞到自己的身體)
    3. 增加蛇身體(吃到食物后)
// modules/snake.ts
export default class Snake {
    // 蛇的容器
    element: HTMLElement;
    // 蛇頭
    head: HTMLElement;
    // 蛇的身體
    bodies: HTMLCollection;

    constructor() {
        // 這里不加笼痹!會(huì)ts報(bào)錯(cuò),因?yàn)橛锌赡軟](méi)有#snake這個(gè)dom酪穿,但我們知道有凳干,通過(guò)加一個(gè)!阻止報(bào)錯(cuò)
        this.element = document.getElementById('snake')!;
        // querySelector方法獲取的是一個(gè)Element對(duì)象被济,與我們?cè)O(shè)置的HTMLElement不匹配救赐,所以這里我們使用類型斷言as
        this.head = document.querySelector('#snake > div') as HTMLElement;
        this.head.style.background = 'red';
        this.bodies = this.element.getElementsByTagName('div')!;
    }

    // get方法是ts類中的語(yǔ)法糖,你還是可以通過(guò)snake1.x的方法獲取該屬性只磷,但我們可以在這個(gè)get方法中寫入判斷邏輯经磅,將控制權(quán)握在開(kāi)發(fā)者手里
    // 獲取蛇頭的x坐標(biāo)
    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)
        // 移動(dòng)身體
        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)
        // 移動(dòng)身體
        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) {
            // 如果發(fā)生了掉頭,讓蛇向反方向繼續(xù)移動(dòng)
            if (value > currentValue) {
                value = currentValue - 10
            } else {
                value = currentValue + 10
            }
        }
        return value
    }

    // 檢查是否撞到自己的身體 這個(gè)方法要在本次蛇的坐標(biāo)更改后再進(jìn)行判斷
    checkBoomBody(value:number) {
        for(let i = 1; i < this.bodies.length; i++) {
            const bd = this.bodies[i] as HTMLElement
            if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                throw new Error('撞到了自己的身體预厌!');
            }
        }
    }

    addBodies() {
        this.element.insertAdjacentHTML('beforeend', "<div></div>")
    }

    moveBodies() {
        // 后面一格身體的坐標(biāo)修改為前一節(jié)身體的坐標(biāo)阿迈,以此類推
        // 這里for循環(huán)的停止條件是i > 0,因?yàn)樯哳^的位置在外面已經(jīng)設(shè)置了
        for(let i = this.bodies.length - 1; i > 0; i--) {
            // 獲取前一格身體的坐標(biāo)
            let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
            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';
        }
    }
}
  • 控制器類
    屬性:蛇、食物配乓、記分牌(引用并創(chuàng)建實(shí)例)、蛇移動(dòng)的方向(按鍵方向)惠毁、游戲是否結(jié)束
    方法:1. 開(kāi)始游戲(綁定鍵盤事件犹芹、開(kāi)啟定時(shí)器更改蛇的坐標(biāo))
    2. 蛇吃食物
// modules/gameController.ts
import Food from './food'
import Snake from './snake'
import ScorePanel from './scorePanel'

export default class GameController {
    food: Food;
    snake: Snake;
    scorePanel: ScorePanel;
    // 按鍵的方向(蛇移動(dòng)的方向)
    direction: string = '';
    // 游戲是否結(jié)束
    isLive = true;

    constructor() {
        this.food = new Food()
        this.snake = new Snake()
        this.scorePanel = new ScorePanel()
        // 創(chuàng)建對(duì)象后即游戲開(kāi)始
        this.init()
    }

    init() {
        console.log('游戲開(kāi)始')
        // 綁定鍵盤事件
        document.addEventListener('keydown', this.keydownHandler.bind(this))
        this.run()
    }

    /**
     * ArrowUp Up
     * ArrowDown Down
     * ArrowLeft Left
     * ArrowRight Right
     * 上面是鍵盤按上下左右后返回的event.key
     * 由于IE瀏覽器不合群,所以事件名稱和其他瀏覽器有區(qū)別
     * 
    */
    keydownHandler(event: KeyboardEvent) {
        // 根據(jù)event.key來(lái)判斷上下左右
        this.direction = event.key;
    }

    run() {
        // 獲取蛇現(xiàn)在的坐標(biāo)
        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
        }


        // 開(kāi)啟一個(gè)定時(shí)器
        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()
        }
    }
}

以上腰埂,整個(gè)貪吃蛇的游戲就完成了,大家可以在以下鏈接下載完成代碼
https://gitee.com/loversong/snake

npm i
npm run start

命令行執(zhí)行以上代碼即可體驗(yàn)蜈膨。
以上代碼還遺留了一個(gè)bug屿笼,就是在移動(dòng)食物的時(shí)候,沒(méi)有考慮到食物隨機(jī)分布到蛇身體上的情況翁巍,大家可以自己嘗試補(bǔ)一下驴一。
還有什么問(wèn)題,歡迎評(píng)論區(qū)指正灶壶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肝断,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子驰凛,更是在濱河造成了極大的恐慌胸懈,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恰响,死亡現(xiàn)場(chǎng)離奇詭異趣钱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胚宦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門首有,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人枢劝,你說(shuō)我怎么就攤上這事绞灼。” “怎么了呈野?”我有些...
    開(kāi)封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵低矮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我被冒,道長(zhǎng)军掂,這世上最難降的妖魔是什么轮蜕? 我笑而不...
    開(kāi)封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮蝗锥,結(jié)果婚禮上跃洛,老公的妹妹穿的比我還像新娘。我一直安慰自己终议,他們只是感情好汇竭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著穴张,像睡著了一般细燎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皂甘,一...
    開(kāi)封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天玻驻,我揣著相機(jī)與錄音,去河邊找鬼偿枕。 笑死璧瞬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渐夸。 我是一名探鬼主播嗤锉,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼墓塌!你這毒婦竟也來(lái)了档冬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桃纯,失蹤者是張志新(化名)和其女友劉穎酷誓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體态坦,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盐数,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伞梯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玫氢。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谜诫,靈堂內(nèi)的尸體忽然破棺而出漾峡,到底是詐尸還是另有隱情,我是刑警寧澤喻旷,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布生逸,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏槽袄。R本人自食惡果不足惜烙无,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遍尺。 院中可真熱鬧截酷,春花似錦、人聲如沸乾戏。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鼓择。三九已至三幻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惯退,已是汗流浹背赌髓。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工从藤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留催跪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓夷野,卻偏偏與公主長(zhǎng)得像懊蒸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悯搔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容