PlayerController

import { _decorator, Component, EventMouse, Node, input, Input, Animation, log, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

export const BLOCK_SIZE = 100;

@ccclass('PlayerController')
export class PlayerController extends Component {

    @property(Animation)
    BodyAnim: Animation = null;

    private _startJump: boolean = false;
    private _jumpStep: number = 0;
    private _jumpTime: number = 0;
    private _curJumpTime: number = 0;
    private _curJumpSpeed: number = 0;
    private _curPos: Vec3 = new Vec3();
    private _deltaPos: Vec3 = new Vec3(0, 0, 0);
    private _targetPos: Vec3 = new Vec3();
    private _number: number = 0;
    private _animationPlayed: boolean = false;
    private _curMoveIndex: number = 0;

    start() {
        // input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
    }

    setInputActive(active: boolean) {
        if (active) {
            input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
        } else {
            input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this);
        }
    }

    onMouseUp(event: EventMouse) {
        if (event.getButton() === 0) {
            this.jumpByStep(1);
        } else if (event.getButton() === 2) {
            this.jumpByStep(2);
        }
    }

    reset() {
        this._curMoveIndex = 0;
        this.node.getPosition(this._curPos);
        this._targetPos.set(0, 0, 0);
    }

    onOnceJumpEnd() {
        this.node.emit('JumpEnd', this._curMoveIndex);
    }

    jumpByStep(step: number) {
        if (this._startJump) {
            return;
        }
        this._startJump = true;
        this._jumpStep = step;
        this._curJumpTime = 0;

        // 速度 = 距离 / 时间
        this._curJumpSpeed = this._jumpStep * BLOCK_SIZE / this._jumpTime;
        this.node.getPosition(this._curPos);
        Vec3.add(this._targetPos, this._curPos, new Vec3(this._jumpStep * BLOCK_SIZE, 0, 0));

        // 读取动画时间
        const state = this.BodyAnim.getState('jump');
        this._jumpTime = state.duration;

        if (this.BodyAnim) {
            if (step === 1 || step === 2) {
                this.BodyAnim.play('jump');
            }
        }

        this._curMoveIndex += step;
    }

    update(deltaTime: number) {
        if (this._startJump) {
            this._curJumpTime += deltaTime;
            if (this._curJumpTime > this._jumpTime) {
                this._startJump = false;
                this.node.setPosition(this._targetPos);
                this.onOnceJumpEnd();
            } else {
                this.node.getPosition(this._curPos);
                // 位移 = 速度 * 时间间隔
                // 最终位置 = 当前位置 + 平均速度 * 时间间隔
                this._deltaPos.x = this._curJumpSpeed * deltaTime;
                Vec3.add(this._curPos, this._curPos, this._deltaPos);
                this.node.setPosition(this._curPos);
            }
        }

        if (!this.BodyAnim.getState('walk').isPlaying && !this.BodyAnim.getState('jump').isPlaying) {
            this.BodyAnim.play('walk');
        }
    }
}

GameManager

import { _decorator, CCInteger, Component, instantiate, Label, Node, Prefab, Vec3 } from 'cc';
import { BLOCK_SIZE, PlayerController } from './PlayerController';
const { ccclass, property } = _decorator;

enum BlockType {
    BT_NONE,
    BT_STONE,
};

enum GameState {
    GS_INIT,
    GS_PLAYING,
    GS_END,
};

@ccclass('GameManager')
export class GameManager extends Component {
    @property({ type: Prefab })
    public boxPerfab: Prefab | null = null;

    @property({ type: CCInteger })
    private roadLength: number = 50;
    private _road: BlockType[] = [];

    @property({ type: Node })
    public startMenu: Node | null = null; //开始的ui

    @property({ type: PlayerController })
    public playerCtrl: PlayerController | null = null; //角色控制器
    @property({ type: Label })
    public stepLabel: Label | null = null; //计步器

    start() {
        // 第一初始化要在 start 里面调用
        this.setCurState(GameState.GS_INIT);
        this.playerCtrl?.node.on('JumpEnd', this.onPlayerJumpEnd, this);
    }

    init() {
        // init 时我们先显示 StartMenu、创建地图以及重设角色的为和状态并禁用角色输入。
        if (this.startMenu) {
            this.startMenu.active = true;
        }

        this.generateRoad();

        if (this.playerCtrl) {
            this.playerCtrl.setInputActive(false);
            this.playerCtrl.node.setPosition(new Vec3(0, 100, 0));
            this.playerCtrl.reset();
        }
    }

    update(deltaTime: number) {

    }

    setCurState(value: GameState) {
        switch (value) {
            case GameState.GS_INIT:
                this.init();
                break;
            case GameState.GS_PLAYING:
                this.playing();
                break;
            case GameState.GS_END:
                break;
        }
    }

    onStartButtonClicked() {
        this.setCurState(GameState.GS_PLAYING);
    }

    onPlayerJumpEnd(moveIndex: number) {
        if (this.stepLabel) {
            this.stepLabel.string = '' + (moveIndex >= this.roadLength ? this.roadLength : moveIndex);
        }
        this.checkResult(moveIndex);
    }

    playing() {
        if (this.startMenu) {
            this.startMenu.active = false;
        }

        if (this.stepLabel) {
            this.stepLabel.string = '0';
        }

        //直接设置active会直接开始监听鼠标事件,做了一下延迟处理
        setTimeout(() => {
            if (this.playerCtrl) {
                this.playerCtrl.setInputActive(true);
            }
        });
    }

    // 判定角色是否跳跃到坑或者跳完所有地块的方法
    checkResult(moveIndex: number) {
        if (moveIndex < this.roadLength) {
            if (this._road[moveIndex] == BlockType.BT_NONE) {
                // 跳到了空方块
                this.setCurState(GameState.GS_INIT);
            }
        } else {
            this.setCurState(GameState.GS_INIT);
        }
    }

    generateRoad() {
        this.node.removeAllChildren();

        this._road = [];
        this._road.push(BlockType.BT_STONE);

        for (let i = 1; i < this.roadLength; i++) {
            if (this._road[i - 1] === BlockType.BT_NONE) {
                this._road.push(BlockType.BT_STONE);
            } else {
                // 从 [0, 2) 中随机取 1个数并向下取整,得到的结果是 0 或者 1
                this._road.push(Math.floor(Math.random() * 2));
            }
        }

        for (let j = 0; j < this.roadLength; j++) {
            let block: Node | null = this.spawnBlockByType(this._road[j]);
            if (block) {
                this.node.addChild(block);
                block.setPosition(j * BLOCK_SIZE, 0, 0);
            }
        }
    }

    spawnBlockByType(type: BlockType) {
        if (!this.boxPerfab) {
            return null;
        }

        let block: Node | null = null;
        switch (type) {
            case BlockType.BT_STONE:
                // instantiate: 是 Cocos Creator 提供的克隆预制体的方法
                block = instantiate(this.boxPerfab);
                break;
        }

        return block;
    }
}

参考 https://docs.cocos.com/creator/3.8/manual/zh/getting-started/first-game-2d/