背景
前面我们已经实现了,关卡内的角色显示和场景显示。接下来,我们来环视一下我们创建的这个虚拟世界,通过摄像头的角度和转变。这也是为后面,玩家自定义装饰家园做准备。
目标
玩家可以自由切换观察角度,左右滑动预览游戏内的场景。
开发过程
首先建立一个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()
}
}
}