背景
前面已经设计了游戏的玩法并通过loading将一些需要的配置和资源进行了加载,接下来就是开始正式的游戏环节。当然本次记录还未完全涉及到具体的游戏,只是显示游戏单位。主要是环境和角色单位。
目标
根据关卡配置信息,将关卡内的环境及敌对单位显示到地图上。
开发过程
关卡信息分地图信息及
在原先的 loading 业务稍微修改一下,添加一个BattleInitComp组件,来初始化战役
export class LoadingViewSystem {
apply(entity: Entity) {
const comp = entity.getComponent(LoadingViewComp);
if (comp && comp.node && comp.progress < 1) {
if (comp.progress < 1) {
comp.progress = comp.finished / comp.total;
comp.node.getChildByName('pro_progress').getComponent(ProgressBar).progress = comp.progress
}
if (comp.finished == comp.total - 1 && comp.isInitBattle == false) {
comp.isInitBattle = true
console.log('comp.isInitBattle = true')
// 最后一个才是初始化游戏
let _BattleInitComp = PooledComponent.requestComponent(BattleInitComp)
_BattleInitComp.callback = () => {
comp.finished++
ooxh.game.entity.detachComponent(BattleInitComp)
}
ooxh.game.entity.attachComponent(_BattleInitComp)
}
if (comp.progress >= 1) {
Logger.logBusiness('完成加载进度')
ooxh.entityManager.removeEntity(comp.entity)
}
}
}
..... 省略部分代码
}
接下来去实现 BattleInitComp ,主要是根据前面 loading时加载的资源赋值到BattleResComp,然后在战役Entity中去获取BattleResComp内的资源信息。
之后按照渲染的层次依次,从下往上依次加载各个层的资源
PS: 根据前面优化后的结构,用了单位 UnitItem 的对象及对象池进行管理(具体看最后面)
export class BattleInitSystem {
apply(entity: Entity) {
}
// 开始初始化战役
static startInit(entity: Entity, callback: Function) {
// 根据关卡设置,显示角色位置
let battleInfo = entity.getComponent(BattleResComp).battleInfo
// 地皮
this.showTiled(battleInfo)
// 技能网格
this.showSkillGrid();
// 单位
this.showUnit(battleInfo)
callback && callback()
}
// 显示地皮瓦片
static showTiled(battleInfo) {
let tiledData = battleInfo.tiledData
let _tiled_prefab = resources.get("prefabs/battle/tiledItem", Prefab)
let _tiled_pos_arr = [{ x: -900, y: -900, z: 0 }, { x: -900, y: 0, z: 0 }, { x: -900, y: 900, z: 0 }, { x: 0, y: -900, z: 0 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 900, z: 0 }, { x: 900, y: -900, z: 0 }, { x: 900, y: 0, z: 0 }, { x: 900, y: 900, z: 0 }]
tiledData.forEach((_tiled: ITiled) => {
let pos = _tiled_pos_arr[_tiled.index]
this._createTiledSprite(_tiled_prefab, new Vec3(pos.x, pos.y, pos.z))
})
}
// 开始显示技能网格
static showSkillGrid() {
let _GridsNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('GridsNode')
// 覆盖的技能网格
let _grid_prefab = resources.get("prefabs/battle/gridItem", Prefab)
let _node = instantiate(_grid_prefab);
_node.setPosition(v3(0, 0, 0))
_node.getComponent(UITransform).setContentSize(500, 700)
_GridsNode.addChild(_node);
// 玩家的技能网格
let _grid_own_prefab = resources.get("prefabs/battle/gridOwnItem", Prefab)
let _own_node = instantiate(_grid_own_prefab);
_own_node.setPosition(v3(0, -500, 0))
_own_node.getComponent(UITransform).setContentSize(500, 300)
_GridsNode.addChild(_own_node);
console.log('_GridsNode', _GridsNode)
}
// 显示各种单位
static showUnit(battleInfo) {
let roleData: any[] = battleInfo.roleData
let _unit_sprite_prefab = resources.get("prefabs/battle/unitSpriteItem", Prefab)
let _unit_spine_prefab = resources.get("prefabs/battle/unitSpineItem", Prefab)
roleData.forEach((_unit: IUnit) => {
// 此处最好在配置roleData时,将 sprite和spine 相同属性的排在一起。可以减少drawcall的次数
if (_unit.unitType == UnitType.UnitSprite) {
this._createUnitSprite(_unit_sprite_prefab, new Vec3(_unit.position.x, _unit.position.y, _unit.position.z))
}
if (_unit.unitType == UnitType.UnitSpine) {
this._createUnitSpine(_unit_spine_prefab, new Vec3(_unit.position.x, _unit.position.y, _unit.position.z))
}
})
}
// 创建单位(sprite)
static _createUnitSprite(_prefab, _v3pos) {
let _node = instantiate(_prefab);
_node.setPosition(_v3pos)
let cameraControllerNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('CameraNode')
_node.setRotationFromEuler(cameraControllerNode.eulerAngles); // 角色安摄像头角度立起来
let _UnitsNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('UnitsNode')
_UnitsNode.addChild(_node);
}
// 创建单位(spine)
static _createUnitSpine(_prefab, _v3pos) {
let _node = instantiate(_prefab);
_node.setPosition(_v3pos)
let cameraControllerNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('CameraNode')
_node.setRotationFromEuler(cameraControllerNode.eulerAngles); // 角色安摄像头角度立起来
let _UnitsNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('UnitsNode')
_UnitsNode.addChild(_node);
}
// 创建地图瓦片(sprite)
static _createTiledSprite(_prefab, _v3pos) {
let _node = instantiate(_prefab);
_node.setPosition(_v3pos)
let _MapsNode = ooxh.game.root.getChildByName('CenterNode').getChildByName('MapsNode')
_MapsNode.addChild(_node);
}
}
export class BattleInitComp extends PooledComponent {
callback: Function = null
reset() {
this.callback = null
}
onAttach(entity) {
this.entity = entity
console.log('实体', entity, '挂载了 BattleInitComp')
BattleInitSystem.startInit(entity, () => {
this.callback && this.callback()
})
}
onDetach(entity) {
this.entity = null
console.log('实体', entity, '移除了 BattleInitComp')
}
}
测试效果
目前spine基本是一个角色一个drawcall,其他sprite的单位基本相同类型的都只算一个
效果预期的达成情况
基本完成原先的环境显示,且性能上比以前好的不止一点点。
下一个开发计划
摄像头角度的转变及场景的滑动预览
PS:2023年6月7日补充:
上面的创建单位部分简化为:
// 显示各种单位
static showUnit(battleInfo) {
battleInfo.unitData.forEach((_unit: IUnit) => {
let unitItem = UnitItem.createItem(_unit.unitNo, _unit.unitType)
unitItem.hp = _unit.hp
unitItem.node.setPosition(v3(_unit.position.x, _unit.position.y, _unit.position.z))
unitItem.node.setRotationFromEuler(ooxh.game.cameraNode.eulerAngles); // 角色安摄像头角度立起来
ooxh.game.unitsNode.addChild(unitItem.node);
ooxh.game.battleEntity.getComponent(BattleStateComp).unitItemMap.set(unitItem.node.position.x + '_' + unitItem.node.position.y, unitItem)
})
}
UnitItem的对象及对象池
/**
* 单位item对象及对象池
*/
const { ccclass, property } = _decorator;
@ccclass('UnitItem')
export class UnitItem extends BaseItem {
//缓存池管理
static nodepools: Map<string, NodePool> = new Map();
static createItem(unitNo: string, unitType: UnitType) {
let nodepool = this.nodepools.get(unitType + unitNo);
if (nodepool == null) {
nodepool = new NodePool();
this.nodepools.set(unitType + unitNo, nodepool);
}
let node: Node;
if (nodepool.size() == 0) {
let prefab = null
if (unitType == UnitType.UnitSpine) {
prefab = resources.get("prefabs/battle/unitSpineItem", Prefab)
node = instantiate(prefab);
this.loadSkeleton(node, unitNo)
}
if (unitType == UnitType.UnitSprite) {
prefab = resources.get("prefabs/battle/unitSpriteItem", Prefab)
node = instantiate(prefab);
}
} else {
node = nodepool.get()!;
}
let unitItem = node.getComponent(UnitItem);
unitItem.unitNo = unitNo
unitItem.unitType = unitType
return unitItem;
}
static async loadSkeleton(node: Node, unitNo: string) {
// spine 特有
let skeleton = node.getChildByName('body').getComponent(sp.Skeleton);
// 因为有些spine带有多个皮肤 如unit_spine_001@skin1,其资源其实是在unit_spine_001内
let skdata = await SpineUtil.loadAndGetSkeletonData(unitNo)
if (skdata) {
skeleton.skeletonData = skdata;
skeleton.setSkin('default')
skeleton.setAnimation(5, 'idle', true)
}
}
static removeItem(unitItem: UnitItem) {
//压入缓存池管理节点
var np = this.nodepools.get(unitItem.unitType + unitItem.unitNo);
if (np) {
np.put(unitItem.node);
}
unitItem.reset()
unitItem.node.removeFromParent();
}
private _key: string = ''
private _hp: number = 0
public unitNo: string = ''
public unitType: UnitType = null
reset(): void {
this._key = ''
this._hp = 0
this.unitNo = ''
this.unitType = null
}
get gridKey() {
return this._key
}
set gridKey(key: string) {
this._key = key
}
get hp() {
return this._hp
}
set hp(val: number) {
this._hp = val
if (this.node.getChildByName('hp')) {
this.node.getChildByName('hp').getComponent(Label).string = val.toString()
}
}
subHp(hp: number) {
if (this._hp > hp) {
this.hp -= hp
} else {
this.hp = 0
ooxh.eventBus.emit(EventBusType.UnitDie, this.unitNo)
UnitItem.removeItem(this)
}
}
}