“灵剑出鞘”小游戏开发日志(5)---- 游戏关卡内环境及单位的显示

背景 

前面已经设计了游戏的玩法并通过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)
        }
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值