//创建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 -> {}
}
}