1.创建场景

  创建scene对象

//创建scene对象
val scene = Scene()

  绑定道具和开启配置

val bg=FUBundleData(backgroundBundlePath)
scene.setBackground(bg)//设置背景
val light=FUBundleData(lightBundlePath)
scene.setLightingBundle(it)//设置灯光
val camera = FUAnimationBundleData(cameraBundlePath)
scene.cameraAnimation.setAnimation(camera)//设置相机位
scene.rendererConfig.setPostProcessMirrorParam(
    FUPostProcessMirrorParamData( // 开启地面反射
        maxTransparency = 0.7f,
        maxDistance = 30f
    )
)


2.创建形象

  初始化AvatarDecoder
    AvatarDecoder通过avatar_bundle_config.json默认配置的主要作用是:
if (FUAvatarDecoderHelper.getInstance().getAvatarDecoderOrNull() == null) {
   val readContentByPath =
       FUFileUtils.readContentByPath(DemoConstants.avatarDefaultBundleConfigPath)//avatar_bundle_config.json
   FUAvatarDecoderHelper.getInstance().initAvatarDecoder(readContentByPath!!)//初始化AvatarDecoder
}
//下载avatar_bundle_config.json中的默认道具
FUAvatarDecoderHelper.getInstance().getDefaultBundleList().let { bundles ->
   val countDownLatch = CountDownLatch(bundles.size)
   bundles.forEach { path ->
       FUResourceHelper.getSourceBeanRemote(path, object : OnLoadRemoteSourceListener {
           override fun onError(code: String, message: String) {
               FULogger.e(TAG) { "getSourceBeanRemote failed,path:$path code:$code message:$message" }
               countDownLatch.countDown()
           }

           override fun onFinish(fileSourceBean: FileSourceBean) {
               countDownLatch.countDown()
           }
       },resourceType)
   }
   countDownLatch.await()
   FUAvatarDecoderHelper.getInstance().initAvatarDecoderMetaData()//填充默认道具的meta信息到AvatarDecoder中去
}

 

创建avatar对象

val controlConfigBundle = getFUBundleData(avatarConfigSource)//controller_config.bundle
val avatar = Avatar(controlConfigBundle)//创建avatar对象

  

添加道具和配置

 (1)获取要加载的道具和配置
方式一:通过读取标准avatar.json(可参考demo中assets目录avatar目录下的配置)来获取,
推荐使用该方式
//解析avatar.json
val avatarInfo =FUSceneKit.getInstance().decodeAvatarFormJson(avatarJson)
方式二:通过代码生成要添加的道具列表和配置
//身上的道具列表(身体、衣服、五官等)
val bundles = listOf(
    "GAssets/AsiaMale/head/BaseModelAmale_head.bundle",
    "GAssets/AsiaMale/body/BaseModelAmale_body.bundle",
    "GAssets/AsiaMale/brow/brow000.bundle",
    "GAssets/AsiaMale/hair/hair001.bundle",
    "GAssets/AsiaMale/cloth/lower_inner/shorts/shorts001.bundle",
    "GAssets/AsiaMale/cloth/upper_inner/shirt/shirt003.bundle",
    "GAssets/AsiaMale/face_components/eye/eye201a.bundle",
    "GAssets/AsiaMale/face_components/face/face201b.bundle",
    "GAssets/AsiaMale/face_components/mouth/mouth201a.bundle",
    "GAssets/AsiaMale/face_components/nose/nose201b.bundle"
)
.....骨骼捏型,道具颜色配置类似这里就不一一列举了,可参考avatar.json自行实现

    

(2)将道具和配置添加到avatar对象中去(这里只拿avatar.json方式举例)

//解析avatar.json
val avatarInfo =FUSceneKit.getInstance().decodeAvatarFormJson(avatarJson)
/** 添加avatar.json中配置的bundle道具。*/
avatarInfo?.bundles?.apply {
    controlConfigBundle?.let { controlConfig ->
        /** 由于可能存在套装bundle(空bundle,内部记录了真实道具列表)
        ,所以需要通过读取bundle的meta信息来校验得到道具的完整列表,        在进行verifyAvatarJson校验之前需要确保完成了初始化AvatarDecoder,和待校验bundle的下载**/
        FUAvatarDecoderHelper.getInstance().verifyAvatarJson(this, controlConfig)
            ?.let { componentList ->
                if (componentList.isNotEmpty()) {
                    needCheckBundles.addAll(componentList)
                    val countDownLatch = CountDownLatch( componentList.size)
                    //拿到完成道具列表后,再次校验并下载可能本地不存在的道具
                    componentList.forEach {sourceId ->
                        FUResourceHelper.getSourceBeanRemote(sourceId,
                            object : OnLoadRemoteSourceListener {
                                override fun onError(code: String, message: String) {
                                    countDownLatch.countDown()
                                }

                                override fun onFinish(fileSourceBean: FileSourceBean) {
                                    countDownLatch.countDown()
                                    getFUBundleData(fileSourceBean)?.let { component ->
                                        avatar.addComponent(component)
                                    }
                                }
                            },resourceType)
                    }
                    countDownLatch.await()
                } else {
                    FULogger.d(TAG, "verifyAvatarJson but componentList is empty")
                }
            }
    }
}
//添加道具颜色配置
avatarInfo?.let {
    it.colorName.forEachIndexed { index, name ->
        it.colorData[index]?.let { color ->
            avatar.color.setColor(name, color)
        }
    }
}
//添加骨骼捏型配置
avatarInfo?.let {
    it.deformationName.forEachIndexed { index, name ->
        it.deformationData[index].let { value ->
            avatar.deformation.setDeformation(name, value)
        }
    }
}
//添加动画graph配置文件
 FUFileUtils.readContentByPath("graph/animGraph.json")?.let {
      animationGraph.setAnimationGraph(it)
 }
 FUFileUtils.readContentByPath("graph/animLogic.json")?.let {
      animationGraph.setAnimationLogic(it)
}
//配置动画
avatar.animation.playAnimation(FUAnimationBundleData("GAssets/animation/AsiaMale/common/ani_ptag_amale_huxi.bundle"))
    • 避免avatar出现裸体,兜底逻辑

    • 添加和替换道具时进行道具校验,处理套装bundle情况。


3、渲染场景及形象

  注意:在场景和形象渲染之前需要完成场景和形象的创建,以及渲染GLView创建的准备。

 

添加场景到渲染中

val loadSceneListener: OnExecuteListener = object : OnExecuteListener {
    override fun onCompleted() {
        FULogger.d(TAG, "loadScene Completed")
        //为了降低内存使用,create bundle 是把数据解压到内存上的,调用这个接口,就把数据解压到本地了。
        //但是解压bundle的时候短期内内存峰值会提高,所以在内存限制阈值较低的设备上可能会导致短期内存不足奔溃
        FURenderKit.getInstance().cacheBundleToExternalStorage()
        FUSceneKit.getInstance().setCurrentScene(scene)
    }
}
FUSceneKit.getInstance().addScene(scene, loadSceneListener, needBackgroundThread)

    添加虚拟人形象到场景中

    FUAvatarDecoderHelper.getInstance().bindAvatar(avatar!!)//兜底避免裸体,参考AvatarDecoder
    val replaceAvatarListener: OnExecuteListener = object : OnExecuteListener {
        override fun onCompleted() {
            FULogger.d(TAG, "replaceAvatar Completed")
            onCompleted.invoke()
        }
    }
    val oldAvatar = scene.getAvatars().getOrNull(0)
    if (oldAvatar == null) {
        FULogger.d(TAG, "addAvatar $avatar")
        scene.addAvatar(avatar)
    } else {
        FULogger.d(TAG, "replaceAvatar $oldAvatar to avatar $avatar")
        scene.replaceAvatar(oldAvatar, avatar, replaceAvatarListener, needBackgroundThread)
    }


4.切换场景背景

//设置bundle背景
scene.setBackground(FUBundleData(sceneResourceConfig.getScenePath()))
//设置color背景
scene.setBackgroundColor(FUColorRGBData(255,255,255,255))


5.切换场景灯光

//设置灯光
scene.setLightingBundle(FUBundleData(lightBundlePath))


6.切换场景机位

加载相机位

//这里的相机bundle可以是融合相机,也可以是普通相机
scene.cameraAnimation.setAnimation(FUAnimationBundleData(sceneDefaultCameraPath))

融合相机切换机位

融合相机是多个不同相机融合到一个bundle中,可提供不同相机位的切换动效,具有切换相机功能,CameraAnimation的 setAnimationModuleIndex方法即可实现相机的切换,cameraIndex即为内部相机的下标,type 为切换动效类型,transitionTime为动效过度时间
scene.cameraAnimation.setAnimationModuleIndex(
    cameraIndex,
    type = FUCinemachineBlendTypeEnum.EASE_IN_OUT,
    transitionTime = 0.5f
)

普通相机切换机位

普通相机是只有一个相机位的bundle
//重新为场景 scene加载新的相机位 即可实现普通相机的切换。
scene.cameraAnimation.setAnimation(FUAnimationBundleData(newCameraPath))


7.切换形象动画

普通动画切换

//预加载动画道具
avatar.animation.addAnimation(FUAnimationBundleData(animationBundlePath))
//播放动画
avatar.animation.playAnimation(FUAnimationBundleData(animationBundlePath))

组合动画切换

/**
*创建组合动画实例对象
 * @param avatarAnimation //形象的角色动画
 * @param subProps //子道具列表(如:配饰道具雨伞等,如果没有子道具就不添加)
 * @param subAnimations // 子道具动画
 */
fun createGroupAnimationBundle(avatarAnimation:String,subProps:List<String>,subAnimations:List<String>):FUGroupAnimationBundleData{
    val groupAnimation = FUGroupAnimationBundleData(avatarAnimation)
    if(subProps.isNotEmpty()){
        subProps.forEach {subPropPath->
            groupAnimation.subProps.add(FUBundleData(subPropPath))//添加子道具
        }
    }
    if(subAnimations.isNotEmpty()){
        subAnimations.forEach {subAnimation->
            groupAnimation.subAnimations.add(FUAnimationBundleData(path = subAnimation, nodeName = FULogicNodeEnum.ITEM.nodeName))//添加子道具动画
        }
    }
    return groupAnimation
}
//播放组合动画
avatar.animation.playAnimation(createGroupAnimationBundle(avatarAnimation,subProps,subAnimations))

通过切换动画节点切换动画

//给形象添加节点动画
/**
*@param animationPath 动画路劲
*@param nodeName 节点名称 ,参考FULogicNodeEnum
*@param probability 动画或该动画组被随机到的概率,取值为正整数
*@param repeatable  是否可重复播放
**/
val nodeAnimationBundle =  getFUAnimationBundleData(animationPath,FULogicNodeEnum.IDLE.nodeName,probability,repeatable)
avatar.animation.addAnimation(nodeAnimationBundle )//添加节点动画到avatar形象


/**
 * 切换至不同动画组
 */
fun switchAnimationNode(avatar: Avatar?,nodeName:String){
    when(nodeName){
        FULogicNodeEnum.DEFAULT.nodeName-> {
         //default动画不推荐使用切换节点的方式进行切换,需要使用playAnimation直接播放
           avatar?.animationGraph?.setAnimationGraphParam(FUAnimationLogicConstants.BLEND_ACTIVE_NODE_INDEX_KEY, 0)
        }
        FULogicNodeEnum.IDLE.nodeName-> {
            avatar?.animationGraph?.switchLogicNode(FULogicNodeSwitchEnum.IDLE)
        }
        FULogicNodeEnum.LISTEN.nodeName-> {
            avatar?.animationGraph?.switchLogicNode(FULogicNodeSwitchEnum.LISTEN)
        }
        FULogicNodeEnum.TALK.nodeName-> {
            avatar?.animationGraph?.switchLogicNode(FULogicNodeSwitchEnum.TALK)
        }
        else -> {}
    }
}


< 上一页: 快速开始
下一页: 进阶功能 >