制作四排 - 第8部分:状态更新
#javascript #教程 #gamedev #games

介绍

欢迎回来!在上一篇博客文章中,您绘制了游戏板,并通过单击板列,使游戏可玩。在这篇文章中,您将在四轮比赛中添加状态区域组件。

分解状态区域组件

让我们参考完成游戏的模型:

Image of game mockup

状态面积在顶部。它分为两个部分:

  • 播放器转弯指标:通过显示的相关玩家的颜色指示当前玩家的转弯。
  • 状态消息:描述游戏的每个阶段发生了什么(转弯是谁?哪个球员赢了?等)

他们一起将游戏中发生的事情告知玩家和观众。

创建现状课程

要开始,在称为StatusArea.jssrc/components目录下创建一个文件。

在您刚创建的文件中,创建一个名为StatusArea的空类。此类将继承GameObject

import GameObject from "./GameObject.js";

export default class StatusArea extends GameObject {

}

播放器转向指示器

播放器转向指示器是一个在状态区域中出现的小圆圈。它可能具有这些状态:

  • 黄色 - 在黄色的球员轮到或黄色玩家赢得了比赛时,具有黄色。
  • 红色 - 当轮到红色玩家或红色玩家赢得了比赛时,有红色。
  • 看不见 - 游戏以平局结束时,指示器不可见。

现在您知道玩家转动指示器的行为,下一步是将其添加到游戏中。

开始绘制播放器转弯指示器

首先,从gameLogic目录以及StatusAreaConfigTokenColorconstants目录导入Constants对象:

import { Constants } from "../gameLogic/index.js";
import { StatusAreaConfig, TokenColor } from "../constants/index.js";
import GameObject from "./GameObject.js";

export default class StatusArea extends GameObject {

}

然后,将renderPlayerTurnIndicator()添加到StatusArea类:

export default class StatusArea extends GameObject {
    renderPlayerTurnIndicator(indicatorColor) {
        let indicatorColorValue;

        switch (indicatorColor) {
            case Constants.PlayerColor.YELLOW:
                indicatorColorValue = TokenColor.YELLOW;
                break;
            case Constants.PlayerColor.RED:
                indicatorColorValue = TokenColor.RED;
                break;
            default:
                // Unknown color. Do not attempt to render player turn indicator.
                return;
        }

        this.context.fillStyle = indicatorColorValue;
        const indicatorY = this.y + StatusAreaConfig.INDICATOR_WIDTH / 2 + StatusAreaConfig.PADDING_TOP;
        this.context.beginPath();

        // Draws a circle using CanvasDrawingContext2D.arc(): https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc
        this.context.arc(this.width / 2, indicatorY, StatusAreaConfig.INDICATOR_WIDTH / 2, 0, Math.PI * 2);

        this.context.closePath();
        this.context.fill();
    }
}

接下来,将render()添加到StatusArea类:

export default class StatusArea extends GameObject {
    render(indicatorColor) {
        this.context.save();
        this.clear();

        if (indicatorColor !== Constants.PlayerColor.NONE) {
            this.renderPlayerTurnIndicator(indicatorColor);
        }

        this.context.restore();
    }

    // ..
}

在继续进一步之前,您需要将StatusArea类作为模块通过src/components/index.js

import Board from "./Board.js";
import StatusArea from './StatusArea.js';


export { Board, StatusArea };

现在您实现了播放器转向指示器的渲染逻辑,并通过components目录公开了StatusArea类,您现在准备开始渲染播放器转向指示器。

渲染播放器转向指示器

src/FrontEnd.js inoctem inocte StatusAreaConfigStatusArea

import { FrontEndConfig, BoardConfig, StatusAreaConfig } from "./constants/index.js";
import { Board, StatusArea } from "./components/index.js";
import { Constants } from "./gameLogic/index.js";

export default class FrontEnd {
    // ..
}

接下来,将statusArea字段添加到FrontEnd类:

export default class FrontEnd {
    game;
    canvas;
    width;
    height;
    context;
    board;
    statusArea;
    gameOver;

    // ..

}

createStatusArea()添加到FrontEnd类:

export default class FrontEnd {
    // ..

    createStatusArea() {
        let statusArea = new StatusArea(this.context, 0, 0, this.width, StatusAreaConfig.HEIGHT);
        statusArea.render(this.game.currentTurn);
        return statusArea;
    }
}

start()中,将statusArea设置为从createStatusArea()返回的新状态区域:

export default class FrontEnd {
    // ..

    start() {
        this.statusArea = this.createStatusArea();
        this.board = this.createBoard();

        document.body.addEventListener('click', (clickEvent) => {
            this.board.handleClick(clickEvent);
        });
    }
}

determineIndicatorColor()添加到FrontEnd类:

export default class FrontEnd {
    // ..

    determineIndicatorColor(moveResult) {
        if (moveResult.status.value === Constants.MoveStatus.DRAW) {
            return Constants.PlayerColor.NONE
        } else if (moveResult.status.value === Constants.MoveStatus.WIN) {
            return moveResult.winner;
        } else {
            return this.game.currentTurn;
        }
    }
}

然后,更新processMoveResult(),以便它也确定下一个播放器的回合,并以下一个播放器的颜色传递到statusArea上的render()方法的呼叫:

export default class FrontEnd {
    // ..

    processMoveResult(moveResult) {
        if (this.gameOver || moveResult.status.value === Constants.MoveStatus.INVALID) {
            return;
        }

        const indicatorColor = this.determineIndicatorColor(moveResult);

        this.statusArea.render(indicatorColor);
        this.board.render(this.game.currentBoard);

        if (moveResult.status.value === Constants.MoveStatus.WIN || moveResult.status.value === Constants.MoveStatus.DRAW) {
            this.gameOver = true;
        }
    }
}

最后,在createBoard()中,将调用对Board构造函数更新为,以便在渲染游戏板时板,它的位置下降到状态下:

export default class FrontEnd {
    // ..

    createBoard() {
        let board = new Board(this.context, BoardConfig.MARGIN_LEFT, this.statusArea.height + BoardConfig.MARGIN_TOP, BoardConfig.WIDTH, BoardConfig.HEIGHT);
        board.setColumnSelectionHandler((columnIndex) => this.playMove(columnIndex));
        board.render(this.game.currentBoard);
        return board;
    }
}

如果服务器运行时在浏览器中检查游戏,则会看到指示器颜色正在呈现。

它将根据游戏的当前状态进行更新。

Red player's turn on game board with player turn indicator displayed with a red colour

状态消息

状态消息是状态区域的文本部分。

它用于:

  • 显示当前玩家的转弯
  • 揭示哪个玩家赢得了游戏
  • 显示游戏何时在抽奖中结束

开始实施状态消息

renderMessage()方法添加到StatusArea类:

export default class FrontEnd {
    // ..

    renderMessage(message) {
        this.context.fillStyle = "white";
        this.context.font = "bold 16px Arial";
        this.context.textBaseline = "top";
        this.context.textAlign = "center"; // Default value had vertical alignment issues. "center" fixes those in this case
        const messageY = this.y + StatusAreaConfig.PADDING_TOP + StatusAreaConfig.INNER_MARGIN;
        this.context.fillText(message, this.width / 2, messageY);
    }
}

继续将message参数添加到render()方法,然后在render()中调用renderMessage()

export default class StatusArea extends GameObject {
    render(indicatorColor, message) {
        this.context.save();
        this.clear();

        if (indicatorColor !== Constants.PlayerColor.NONE) {
            this.renderPlayerTurnIndicator(indicatorColor);
        }

        this.renderMessage(message);
        this.context.restore();
    }

    // ..

完成此操作后,切换回src/FrontEnd.js文件。从constants目录导入StatusMessages

import { FrontEndConfig, BoardConfig, StatusAreaConfig, StatusMessages } from "./constants/index.js";
import { Board } from "./components/index.js";
import { Constants } from "./gameLogic/index.js";

现在,添加逻辑,该逻辑根据游戏的当前状态确定要显示的状态消息。将pickStatusMessage()添加到FrontEnd类:

export default class FrontEnd {
    // ..

    pickStatusMessage(status) {
        switch (status) {
            case Constants.GameStatus.WIN:
                // The game is on the the next turn but the somebody has won from the previous turn. The winning player is the opposite of the player who currently has a turn.
                return this.game.currentTurn === Constants.PlayerColor.YELLOW ? StatusMessages.RED_WIN : StatusMessages.YELLOW_WIN;
            case Constants.GameStatus.DRAW:
                return StatusMessages.DRAW;
        }

        // At this point, we can assume that the game is either has just started 
        // or is still in progress.
        return this.game.currentTurn === Constants.PlayerColor.YELLOW ? StatusMessages.YELLOW_TURN : StatusMessages.RED_TURN;
    }
}

然后在processMoveResult()方法中,在statusArea上更新render()呼叫,以便它传递了一个其他参数。此参数是对pickStatusMessage()的方法调用,其中moveResult的状态值传递给了:

export default class FrontEnd {
    // ..

    processMoveResult(moveResult) {
        if (this.gameOver || moveResult.status.value === Constants.MoveStatus.INVALID) {
            return;
        }

        const indicatorColor = this.determineIndicatorColor(moveResult);

        this.statusArea.render(indicatorColor, this.pickStatusMessage(moveResult.status.value))
        this.board.render(this.game.currentBoard);

        if (moveResult.status.value === Constants.MoveStatus.WIN || moveResult.status.value === Constants.MoveStatus.DRAW) {
            this.gameOver = true;
        }
    }
}

最后在createStatusArea()中重复相同的更改,只是传递给pickStatusMessage()的参数将是游戏的当前状态:

export default class FrontEnd {
    // ..

    createStatusArea() {
        let statusArea = new StatusArea(this.context, 0, 0, this.width, StatusAreaConfig.HEIGHT);
        statusArea.render(this.game.currentTurn, this.pickStatusMessage(this.game.status));
        return statusArea;
    }
}

如果您使用运行服务器在浏览器中检查游戏,您会发现状态区域将始终反映游戏的当前状态:

  • 播放器转弯指示器将出现在特定玩家的状态中
  • 状态消息将描述游戏的当前状态

Screenshot of yellow player's run with player turn indicator and status message indicating this in status area

结论

恭喜您走了这么远!现在很清楚地了解游戏过程中发生了什么。

不幸的是,当游戏结束时,就无法启动新游戏而不刷新页面。

在本教程的下一个(也是最后)部分中,您将通过在游戏中添加最终组件,再次播放按钮来解决此问题。

感谢您的阅读! ð