//创建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 ) )
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中去
}
val controlConfigBundle = getFUBundleData(avatarConfigSource)//controller_config.bundle val avatar = Avatar(controlConfigBundle)//创建avatar对象
方式一:通过读取标准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情况。
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) }
//设置bundle背景 scene.setBackground(FUBundleData(sceneResourceConfig.getScenePath())) //设置color背景 scene.setBackgroundColor(FUColorRGBData(255,255,255,255))
//设置灯光 scene.setLightingBundle(FUBundleData(lightBundlePath))
//这里的相机bundle可以是融合相机,也可以是普通相机 scene.cameraAnimation.setAnimation(FUAnimationBundleData(sceneDefaultCameraPath))
:
方法即可实现相机的切换,cameraIndex即为内部相机的下标,type 为切换动效类型,transitionTime为动效过度时间scene.cameraAnimation.setAnimationModuleIndex( cameraIndex, type = FUCinemachineBlendTypeEnum.EASE_IN_OUT, transitionTime = 0.5f )
//重新为场景 scene加载新的相机位 即可实现普通相机的切换。 scene.cameraAnimation.setAnimation(FUAnimationBundleData(newCameraPath))
//预加载动画道具 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 -> {} } }