“灵剑出鞘”小游戏开发日志(6)---- 摄像头角度的转变及场景的滑动预览

背景

前面我们已经实现了,关卡内的角色显示和场景显示。接下来,我们来环视一下我们创建的这个虚拟世界,通过摄像头的角度和转变。这也是为后面,玩家自定义装饰家园做准备。

目标

玩家可以自由切换观察角度,左右滑动预览游戏内的场景。

开发过程

首先建立一个node节点,命名为CameraCenterNode,摄像头的中心始终将对准这个node,为后期的装修定位用

接着开发玩家滑动移动组件。

为了防止移动时摄像头又卡顿的现象,将以前裁剪的“缓动系统”重新打开,这样就能使用tween等缓动函数

接下来上代码


export class PlayerTouchMoveSystem {

    // 系统定期触发
    apply(entity: Entity) {

    }

    static startMove(event: EventTouch) {
        let comp: PlayerTouchMoveComp = (this as unknown as PlayerTouchMoveComp)
        if (comp) {
            comp.touchStartX = event.getLocationX()
            comp.touchStartY = event.getLocationY()
        }
    }

    static endMove(event: EventTouch) {
        let comp: PlayerTouchMoveComp = (this as unknown as PlayerTouchMoveComp)
        if (comp) {
            let endX = event.getLocationX()
            let endY = event.getLocationY()
            let x_c = endX - comp.touchStartX
            let y_c = endY - comp.touchStartY

            let toVec3 = v3(0, 0, 0)
            let offset = v3(0, 0, 0)

            let _yuEurler = ooxh.game.cameraNode.eulerAngles.z % 360
            if (_yuEurler < 0) {
                _yuEurler = 360 + _yuEurler
            }
            if (_yuEurler == 0) {
                offset = v3(-x_c, -y_c, 0)
            }
            if (_yuEurler == 90) {
                offset = v3(y_c, -x_c, 0)
            }
            if (_yuEurler == 180) {
                offset = v3(x_c, y_c, 0)
            }
            if (_yuEurler == 270) {
                offset = v3(-y_c, x_c, 0)
            }

            Vec3.add(toVec3, offset, ooxh.game.cameraCenterNode.position);

            BattleViewSystem.cameraMoveTo(toVec3)
        }

    }

    static listenTouchMove(entity: Entity) {
        const comp = entity.getComponent(PlayerTouchMoveComp);
        if (comp) {
            input.on(Input.EventType.TOUCH_START, this.startMove, comp);
            input.on(Input.EventType.TOUCH_END, this.endMove, comp);
        }
    }
    static unListenTouchMove(entity: Entity) {
        const comp = entity.getComponent(PlayerTouchMoveComp);
        if (comp) {
            input.off(Input.EventType.TOUCH_START);
            input.off(Input.EventType.TOUCH_END);
        }
    }

}

export class PlayerTouchMoveComp extends PooledComponent {

    callback: Function = null
    touchStartX: number = 0
    touchStartY: number = 0

    reset() {
        this.callback = null
        this.touchStartX = 0
        this.touchStartY = 0
    }

    onAttach(entity) {
        this.entity = entity
        console.log('实体', entity, '挂载了 PlayerTouchMoveComp')
        PlayerTouchMoveSystem.listenTouchMove(entity)
    }

    onDetach(entity) {
        this.entity = null
        console.log('实体', entity, '移除了 PlayerTouchMoveComp')
        PlayerTouchMoveSystem.unListenTouchMove(entity)
    }
}

Vec3.add(toVec3, offset, ooxh.game.cameraCenterNode.position);

是将滑动的偏移加到原有的坐标上,得到toVec3

接着就是缓动效果移动摄像头的node

然后再视图层监听 转换按钮,并实现角度转换

PS:后期优化后,摄像头的控制被转义到ooxh.game上面


export class BattleViewSystem {

    apply(entity: Entity) {
        const comp = entity.getComponent(BattleViewComp);
        if (comp && comp.isMoving) {
            ooxh.game.cameraCenterNode.setPosition(ooxh.game.cameraNode.position);
        }
    }

    static listenClick(entity: Entity) {
        const comp = entity.getComponent(BattleViewComp);
        if (comp) {
            comp.node.getChildByName('help_move').getChildByName('move_left_btn').on(Node.EventType.TOUCH_END, this.leftJD, comp);
            comp.node.getChildByName('help_move').getChildByName('move_right_btn').on(Node.EventType.TOUCH_END, this.rightJD, comp);
        }
    }
    static unListenClick(entity: Entity) {
        const comp = entity.getComponent(BattleViewComp);
        if (comp) {
            comp.node.getChildByName('help_move').getChildByName('move_left_btn').off(Node.EventType.TOUCH_END);
            comp.node.getChildByName('help_move').getChildByName('move_right_btn').off(Node.EventType.TOUCH_END);
        }
    }

    static leftJD(event: EventTouch) {
        let comp: BattleViewComp = (this as unknown as BattleViewComp)
        if (comp.isRotating == false) {
            const ea = ooxh.game.cameraNode.eulerAngles;
            let newEulerAngles = v3(ea.x, ea.y, ea.z - 90)
            BattleViewSystem.trunLeftRightJD(newEulerAngles)
        }
    }

    static rightJD(event: EventTouch) {
        let comp: BattleViewComp = (this as unknown as BattleViewComp)
        if (comp.isRotating == false) {
            const ea = ooxh.game.cameraNode.eulerAngles;
            let newEulerAngles = v3(ea.x, ea.y, ea.z + 90)
            BattleViewSystem.trunLeftRightJD(newEulerAngles)
        }
    }

    static trunLeftRightJD(newEulerAngles: Vec3) {
        let comp: BattleViewComp = ooxh.game.entity.getComponent(BattleViewComp)
        if (comp) {
            Logger.logBusiness(`摄像头转个方位`)
            comp.isRotating = true
            tween(ooxh.game.cameraNode).to(1, {
                eulerAngles: newEulerAngles
            }, { easing: easing.sineOut })
                .call(() => {
                    comp.isRotating = false;
                }).start();
            Logger.logBusiness(`所有单位换个方位`)
            ooxh.game.unitsNode.children.forEach((_node) => {
                tween(_node).to(1, {
                    eulerAngles: newEulerAngles
                }, { easing: easing.sineOut })
                    .call(() => {
                    }).start();

            })
        }
    }

    static cameraMoveTo(toVec3: Vec3) {
        let comp: BattleViewComp = ooxh.game.entity.getComponent(BattleViewComp)
        if (comp) {
            comp.isMoving = true;
            comp.moveNum++
            tween(ooxh.game.cameraNode).to(1, {
                position: toVec3
            }, { easing: easing.sineOut })
                .call(() => {
                    comp.moveNum--
                    if (comp.moveNum == 0) {
                        comp.isMoving = false;
                    }
                }).start();
        }
    }

}

export class BattleViewComp extends PooledComponent {

    node: Node = null
    callback: Function = null
    isMoving: boolean = false;
    isRotating: boolean = false;
    moveNum: number = 0

    reset() {
        this.node = null
        this.callback = null
        this.isMoving = false
        this.isRotating = false
        this.moveNum = 0
    }

    onAttach(entity: Entity) {
        this.entity = entity
        console.log('实体', entity, '挂载了 BattleViewComp')
        resources.load('gui/battle_index', Prefab, (err, prefab) => {
            if (err) {
                console.error('Failed to load prefab:', err);
                return;
            }
            this.node = instantiate(prefab);
            ooxh.gui.root.addChild(this.node);
            BattleViewSystem.listenClick(entity)
            this.callback && this.callback()
        });
    }

    onDetach(entity: Entity) {
        this.entity = null
        console.log('实体', entity, '移除了 BattleViewComp')
        this.node.destroy()
        BattleViewSystem.unListenClick(entity)
    }
}

测试效果

下图的帧率设置的是40

下一个开发计划

“剑出鞘”的打击玩法

PS:2023年6月7日补充:

后期把BattleViewComp  组件移除改为了 BattleView的cocos通用组件。并将摄像头的方法转移到ooxh.game上面

/**
 * 战役界面
 */
@ccclass('BattleView')
export class BattleView extends Component {


    vm: any = null
    isChuQiaoing: boolean = false
    isClearDirectionUnits: boolean = false

    start() {
        ooxh.game.playerEntity.getComponent(PlayerStateComp).touchMoveType = TouchMoveType.FOR_BATTLE
    }

    onEnable() {
        this.isChuQiaoing = false
        this.isClearDirectionUnits = false
        // this.node.getChildByName('bottom').getChildByName('startGame').on(Node.EventType.TOUCH_END, this.onStartGameClick, this);
        // 监听按钮
        this.onClicks()
        // 摄像头初始化
        this.initCamera()
        // 胜利失败监听
        this.checkWinLose()
        // 先开始显示网格 加入技能网格组件
        ooxh.game.battleEntity.attachComponent(BattleGridComp);
        // 监听玩家触控
        PlayerTouchMoveSystem.listenTouchMove(this.node.getChildByName('battle_touch_move_area'))
        // vm
        this.onVMData(() => {
            ooxh.game.battleEntity.getComponent(BattleOverLoseComp).conditionItems.forEach((_item) => {
                if (_item.event == EventBusType.ChuQiao) {
                    this.vm.remain_chuqiao = _item.value - _item.finished
                }
            })
            // 自动录制
            this.autoStartRecord()
            // load
            this.loadUIs()

        })
        // guide
        ooxh.game.battleEntity.attachComponent(BattleGuideComp);
        ooxh.game.isGuideing = true // 进来都提示
        let _guideItem = GuideItem.createItem('move_up')
        _guideItem.node.position = v3(0, -300, 0)
        ooxh.gui.root.addChild(_guideItem.node)

    }

    onDisable() {
        PlayerTouchMoveSystem.unListenTouchMove(this.node.getChildByName('battle_touch_move_area'))
    }

    openDialogWhenChuQiaoOver(callback) {
        if (this.isChuQiaoing == false) {
            callback && callback()
        } else {
            setTimeout(() => {
                this.openDialogWhenChuQiaoOver(callback)
            }, 1000)
        }
    }

    checkWinLose() {
        // 胜利与失败条件
        const battleInfo = ooxh.game.battleEntity.getComponent(BattleStateComp).battleInfo
        let _BattleOverWinComp = ooxh.game.battleEntity.attachComponent(BattleOverWinComp);
        _BattleOverWinComp.conditionItems = battleInfo.winCondition
        _BattleOverWinComp.callback = () => {
            this.openDialogWhenChuQiaoOver(() => {
                this._postAndShowDialog(OverType.Win)
            })
        }
        let _BattleOverLoseComp = ooxh.game.battleEntity.attachComponent(BattleOverLoseComp);
        _BattleOverLoseComp.conditionItems = battleInfo.loseCondition
        _BattleOverLoseComp.callback = () => {
            this.openDialogWhenChuQiaoOver(() => {
                // 第一次失败,给一次复活机会
                if (!ooxh.game.battleEntity.getComponent(BattleStateComp).isRevive) {
                    ooxh.gui.attachUICallback(UIID.Get_Revive, (uiView) => {
                        uiView.getComponent(GetReviveView).callback = (isOk) => {
                            if (!isOk) {
                                this._postAndShowDialog(OverType.Lose)
                            } else {
                                ooxh.game.battleEntity.getComponent(BattleOverLoseComp).conditionItems.forEach((_item) => {
                                    if (_item.event == EventBusType.ChuQiao) {
                                        this.vm.remain_chuqiao = _item.value - _item.finished
                                    }
                                })
                                ooxh.game.battleEntity.getComponent(BattleStateComp).isRevive = true
                            }
                        }
                    })
                } else {
                    this._postAndShowDialog(OverType.Lose)
                }
            })
        }
    }


    private _postAndShowDialog(overType: OverType) {
        // 记录
        HttpUtil.Post('/appletapi/jianchuqiao/overBattle',
            {
                battleLogId: ooxh.game.playerEntity.getComponent(PlayerStateComp).battleLogId,
                overType: overType
            },
            (res) => {
                if (res.code == 200) {
                    if (overType == OverType.Lose) {
                        // 显示失败结算页面
                        ooxh.gui.attachUICallback(UIID.Battle_Lose, (uiView) => {
                            uiView.getComponent(BattleLoseView).goods = res.data.goods
                        })
                    }
                    if (overType == OverType.Win) {
                        // 显示胜利结算页面
                        ooxh.gui.attachUICallback(UIID.Battle_Win, (uiView) => {
                            uiView.getComponent(BattleWinView).goods = res.data.goods
                        })
                    }
                    // 
                    const playerInfo = ooxh.game.getPlayerInfo()
                    playerInfo.ps = res.data.ps
                    playerInfo.gold = res.data.gold
                    playerInfo.diamond = res.data.diamond
                    playerInfo.last_battle_id = res.data.last_battle_id
                } else {
                    ooxh.gui.toast(res.msg)
                }
            })
    }

    initCamera() {
        ooxh.game.cameraCenterNode.setPosition(v3(0, -600, 0))
        ooxh.game.cameraNode.setPosition(v3(0, -600, 0))
        ooxh.game.cameraNode.setRotationFromEuler(v3(45, 0, 0))
        ooxh.game.cameraMoveTo(v3(0, -800, 0))
        ooxh.game.trunLeftRightJD(ooxh.game.cameraNode.eulerAngles)
    }
    onClicks() {
        this.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').on(Node.EventType.TOUCH_END, this.changeRecordState, this);
        this.node.getChildByName('help_move').getChildByName('move_left_btn').on(Node.EventType.TOUCH_END, this.leftJD, this);
        this.node.getChildByName('help_move').getChildByName('move_right_btn').on(Node.EventType.TOUCH_END, this.rightJD, this);
    }
    offClicks() {

    }
    leftJD(event: EventTouch) {
        if (ooxh.game.isRotating == false) {
            const ea = ooxh.game.cameraNode.eulerAngles;
            let newEulerAngles = v3(ea.x, ea.y, ea.z - 90)
            ooxh.game.trunLeftRightJD(newEulerAngles)
        }
    }

    rightJD(event: EventTouch) {
        if (ooxh.game.isRotating == false) {
            const ea = ooxh.game.cameraNode.eulerAngles;
            let newEulerAngles = v3(ea.x, ea.y, ea.z + 90)
            ooxh.game.trunLeftRightJD(newEulerAngles)
        }
    }
    // 自动录制视频
    autoStartRecord() {
        if (BYTEDANCE) {
            DyUtil.init()
        }
        if (this.vm.recordState == RecordState.NotStart) {
            this.vm.recordState = RecordState.Doing
            if (BYTEDANCE) {
                DyUtil.startRecord(() => { });
            }
        }
    }
    loadUIs() {

    }
    onVMData(callback) {
        if (!ooxh.game || !ooxh.game.battleEntity.getComponent(BattleOverLoseComp) || ooxh.game.battleEntity.getComponent(BattleOverLoseComp).conditionItems.length == 0) {
            setTimeout(() => {
                this.onVMData(callback)
            }, 300)
            return
        }
        const that = this
        const vmData = {
            remain_chuqiao: 0,
            recordState: RecordState.NotStart
        }
        const handler = {
            set(obj, prop, value) {
                if (prop == 'remain_chuqiao') {
                    that.node.getChildByName('player_remain_chuqiao').getChildByName('remain_value').getComponent(Label).string = value
                }
                if (prop == 'recordState') {
                    assetManager.loadBundle('texture', (err, bundle) => {
                        let name = ''
                        switch (value) {
                            case RecordState.NotStart:
                                name = 'notRecord';
                                that.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').getComponentInChildren(Label).string = ''
                                break;
                            case RecordState.Doing:
                                name = 'startRecord';
                                that.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').getComponentInChildren(Label).string = '录制中'
                                break;
                            case RecordState.Over:
                                name = 'overRecord';
                                that.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').getComponentInChildren(Label).string = '录制结束'
                                break;
                            case RecordState.Pauseing:
                                name = 'pauseRecord';
                                that.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').getComponentInChildren(Label).string = '录制暂停'
                                break;
                        }
                        bundle.load('record/record/' + name, SpriteFrame, (error, _spriteFrame: SpriteFrame) => {
                            if (error) {
                                console.error(error)
                                return
                            }
                            that.node.getChildByName('player_remain_chuqiao').getChildByName('recordBtn').getComponent(Sprite).spriteFrame = _spriteFrame
                        });
                    });
                }
                obj[prop] = value;
                return true
            }
        };
        that.vm = new Proxy(vmData, handler);
        callback && callback()
    }



    changeRecordState(event: EventTouch) {
        if (ooxh.game.battleEntity.getComponent(BattleStateComp).isGameOver) {
            return
        }
        if (this.vm.recordState == RecordState.NotStart) {
            this.vm.recordState = RecordState.Doing
            if (BYTEDANCE) {
                DyUtil.startRecord(() => { });
            }
            return
        }
        if (this.vm.recordState == RecordState.Doing) {
            this.vm.recordState = RecordState.Pauseing
            if (BYTEDANCE) {
                DyUtil.pauseRecord(() => { }); // 暂停
            }
            return
        }
        if (this.vm.recordState == RecordState.Pauseing) {
            this.vm.recordState = RecordState.Doing
            if (BYTEDANCE) {
                DyUtil.resumeRecord(() => { });
            }
            return
        }
        if (this.vm.recordState == RecordState.Over) {
            ooxh.gui.toast('已录制结束')
            return
        }
    }


    update(dt: number) {
        if (ooxh.game.isMoving) {
            ooxh.game.cameraCenterNode.setPosition(ooxh.game.cameraNode.position);
        }
        if (this.isClearDirectionUnits && this.isChuQiaoing == false) {
            this.isClearDirectionUnits = false;
            ooxh.game.nextDirection()
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值