使四排 - 第9部分:再次播放(结局)
#javascript #教程 #gamedev #games

介绍

在上一篇博客文章中,您将状态区域添加到了游戏中。现在,玩家和观众清楚地了解游戏期间的任何时候发生了什么。

在这篇文章中,您将添加重新启动游戏的能力而无需刷新浏览器。

您将通过在游戏中添加“再次播放”按钮来做到这一点。

什么是播放按钮?

让我们再看一场完成游戏的模型:
Image of game mockup

“再次播放”按钮是一个按钮:

  • 只有在游戏结束时才能看到(玩家获胜或以平局结束)。
  • 单击时重新启动游戏并消失。

创建Playagainbutton课程

就像您到目前为止添加的其他组件一样,“再次播放”按钮也是游戏对象。

src/components目录中,创建一个文件创建一个名为PlayAgainButton.js的文件。

在该文件中,创建一个名为PlayAgainButton的类,该类从GameObject延伸3:

import GameObject from "./GameObject.js";

export default class PlayAgainButton extends GameObject {

}

您也将需要一些特定于此组件的常数。导入PlayAgainButtonConfig

import GameObject from "./GameObject.js";
import { PlayAgainButtonConfig } from "../constants/index.js";

export default class PlayAgainButton extends GameObject {

}

接下来,为按钮的背景添加图形逻辑。将renderBackground()添加到PlayAgainButton类:

export default class PlayAgainButton extends GameObject {
    renderBackground() {
        const backgroundGradient = this.context.createLinearGradient(this.x, this.y, this.x, this.y + this.height);
        backgroundGradient.addColorStop(0, PlayAgainButtonConfig.BACKGROUND_START_COLOR);
        backgroundGradient.addColorStop(1, PlayAgainButtonConfig.BACKGROUND_END_COLOR);

        this.context.fillStyle = backgroundGradient;
        this.context.strokeStyle = `${PlayAgainButtonConfig.BORDER_WIDTH}px black`;
        this.context.fillRect(this.x, this.y, this.width, this.height);
        this.context.strokeRect(this.x, this.y, this.width, this.height);
    }    
}

然后,从GameObject覆盖clear()方法也考虑到按钮的边界:

export default class PlayAgainButton extends GameObject {
    // ..

    clear() {
        const clearRectX = this.x - PlayAgainButtonConfig.BORDER_WIDTH;
        const clearRectY = this.y - PlayAgainButtonConfig.BORDER_WIDTH;
        const clearRectWidth = this.width + PlayAgainButtonConfig.BORDER_WIDTH * 2;
        const clearRectHeight = this.height + PlayAgainButtonConfig.BORDER_WIDTH * 2;

        this.context.clearRect(clearRectX, clearRectY, clearRectWidth, clearRectHeight);
    }
}

现在要将文本添加到按钮,将renderText()添加到PlayAgainButton类:

export default class PlayAgainButton extends GameObject {
    // ..

    renderText() {
        this.context.fillStyle = "white";
        this.context.font = "16px Arial";
        this.context.textAlign = "center";
        this.context.textBaseline = "top";

        const textMetrics = this.context.measureText(PlayAgainButtonConfig.TEXT);
        const textHeight = textMetrics.actualBoundingBoxDescent;

        // Calculation ensures that text is displayed at the vertical center of the button
        const finalTextY = this.y + (this.height / 2) - textHeight / 2;

        this.context.fillText(PlayAgainButtonConfig.TEXT, this.x + PlayAgainButtonConfig.WIDTH / 2, finalTextY);
    }
}

render()方法添加到PlayAgainButton类:

export default class PlayAgainButton extends GameObject {
    render() {
        this.context.save();
        this.renderBackground();
        this.context.restore();

        this.context.save();
        this.renderText();
        this.context.restore();
    }

    // ..
}

渲染按钮

现在您将在游戏中添加“再次播放”按钮。
首先,将PlayAgainButton类添加到src/components/index.js,以便可以从那里导入它:

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


export { Board, StatusArea, PlayAgainButton };

现在切换到src/FrontEnd.js。导入PlayAgainButtonPlayAgainButtonConfig

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

export default class FrontEnd {
    // ..
}

playAgainButton字段添加到FrontEnd类:


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

    // ..

}

然后将createPlayAgainButton()添加到FrontEnd类:

export default class FrontEnd {
    // ..

    createPlayAgainButton() {
        let buttonX = this.width / 2 - PlayAgainButtonConfig.WIDTH / 2;
        let buttonY = this.height - PlayAgainButtonConfig.MARGIN_BOTTOM;
        let button = new PlayAgainButton(this.context, buttonX, buttonY, PlayAgainButtonConfig.WIDTH, PlayAgainButtonConfig.HEIGHT);
        button.render();
        return button;
    }
}

start()方法中,将playAgainButton字段设置为从createPlayAgainButton()返回的值:

export default class FrontEnd {
    // ..

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

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

如果您使用运行服务器在浏览器中检查游戏,则您将在画布上看到“再次播放”按钮:

Image of start of the game with "Play Again" button displayed at the bottom of the canvas

出现按钮很棒,但是当您单击它时什么也不会发生。它不应该在游戏的这个阶段出现。接下来您将解决这些问题。

处理输入

“再次播放”按钮只能在游戏结束时可见。另外,PlayAgainButton类处理与Board类同样的单击。

要开始这一点,请返回src/components/PlayAgainButton.js。将buttonClickedisEnabled字段添加到PlayAgainButton类:

export default class PlayAgainButton extends GameObject {
    buttonClicked;
    isEnabled;

    // ..
}

然后将构造函数添加到PlayAgainButton类中。它将调用父构建器,然后将isEnabled字段设置为false

export default class PlayAgainButton extends GameObject {
    constructor(context, x, y, width, height) {
        super(context, x, y, width, height);
        this.isEnabled = false;
    }

    // ..
}

更新render(),以便将isEnabled字段设置为true

export default class PlayAgainButton extends GameObject {
    // .. 

    render() {
        this.context.save();
        this.renderBackground();
        this.context.restore();

        this.context.save();
        this.renderText();
        this.context.restore();

        this.isEnabled = true;
    }
}

要设置单击按钮时运行的逻辑,请将setClickHandler()添加到PlayAgainButton类:

export default class PlayAgainButton extends GameObject {
    // ..

    setClickHandler(handler) {
        this.buttonClicked = handler;
    }
}

添加handleClick()来处理将传递的点击事件:

export default class PlayAgainButton extends GameObject {
    // ..

    handleClick(clickEvent) {
        if (!this.isEnabled) {
            return;
        }

        const wasButtonClicked = clickEvent.offsetX >= this.x
            && clickEvent.offsetX <= this.x + this.width
            && clickEvent.offsetY >= this.y
            && clickEvent.offsetY <= this.y + this.height;

        if (!wasButtonClicked) {
            return;
        }

        this.buttonClicked();
    }
}

它检查播放器点击的位置是否实际上在按钮的边界内。

现在还有一件事要添加到PlayAgainButton类。这是hide()方法:

export default class PlayAgainButton extends GameObject {
    // ..

    hide() {
        this.isEnabled = false;
        this.clear();
    }
}

您添加了很多东西到PlayAgainButton类。类似于您处理板上的点击时,在使用这些更改的FrontEnd类中,这一切都会有意义。

FrontEnd类中,更新start()方法,以便单击事件处理程序回调还会在playAgainButton字段上拨打handleClick(),然后单击事件传递:

export default class FrontEnd {
    // .. 

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

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

然后在processMoveResult()中,如果游戏结束了,请在playAgainButton字段上致电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.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;
        }

        if (this.gameOver) {
            this.playAgainButton.render();
        }
    }
}

另外,导入FourInARowGame

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


export default class FrontEnd {
    // ..
}

然后,在FrontEnd类中添加reset()方法:

export default class FrontEnd {
    // ..

    reset() {
        this.game = new FourInARowGame();
        this.gameOver = false;

        this.playAgainButton.hide();
        this.statusArea.render(this.game.currentTurn, this.pickStatusMessage(this.game.status));
        this.board.render(this.game.currentBoard);
    }
}

这是重新启动游戏的主要方法。

最后,在createPlayAgainButton()中,删除了buttonrender()的呼叫,然后在button上致电setClickHandler()。在回调中致电reset()

export default class FrontEnd {
    // ..

    createPlayAgainButton() {
        let buttonX = this.width / 2 - PlayAgainButtonConfig.WIDTH / 2;
        let buttonY = this.height - PlayAgainButtonConfig.MARGIN_BOTTOM;
        let button = new PlayAgainButton(this.context, buttonX, buttonY, PlayAgainButtonConfig.WIDTH, PlayAgainButtonConfig.HEIGHT);
        button.setClickHandler(() => this.reset());
        return button;
    }
}

现在,如果您使用运行服务器在浏览器中检查游戏,则您将拥有一个完全可玩的四英寸游戏游戏,可以在完成游戏后重播。

结论

恭喜!您已经完成了游戏。随时可以随意修改它。也许会更改一些组件的外观?您也可以添加动画!

游戏的源代码可在此处找到:https://github.com/colinkiama/four-in-a-row-game-walkthrough

感谢您的阅读! ð