返回顶部
首页 > 资讯 > 移动开发 >Android 上实现DragonBones换装功能
  • 809
分享到

Android 上实现DragonBones换装功能

2024-04-02 19:04:59 809人浏览 独家记忆
摘要

目录前言技术选型Korge的基本用法实现换装的多种实现静态换装 vs 动态换装静态换装动态换装包含动画 vs 不包含动画局部换装 vs 全局换装全局换装之Skin修改全局换装之纹理修

前言

最近在预研一款换装的小游戏,通过在积分乐园中兑换服装,就可以在不同场景中展示穿上新服装的角色。对于这类有主题形象的动画,自然就想到了骨骼动画,通过网格自由变形和蒙皮技术就能在视觉上呈现所需要的动画效果,并且骨骼动画也支持皮肤替换,或者插槽的图片替换,对于换装的需求比较友好。因此决定使用骨骼动画来实现换装小游戏的Demo,以下就是在Android平台上实现DraGonBones换装的过程。

技术选型

对于DragonBones在Android端的渲染显示,有多个方案可以选择,例如:白鹭引擎或者Cocos2d游戏引擎。最终选择使用korge来进行渲染,为什么抛弃Cocos2d这个广泛使用的游戏引擎来渲染呢?主要理由是:

  • Cocos2d 游戏引擎加载比较耗时,其首次加载时间无法接受;
  • Cocos2d 编译出来的底层依赖需要单独裁剪,裁剪后的libcocos.so依然较大;
  • Cocos2d 对于游戏动画的渲染,其渲染的载体是Activity,也就是编译出来的CocosActivity,这个是无法满足业务需要的。因此需要自定义游戏容器,并且需要改动画加载的容器载体和加载路径。简单点来说,可以从任意路径来加载游戏资源(例如网络或者本地,不仅仅是assets目录),并且可以在自定义View中进行渲染。解决思路可以参考:Android实战之Cocos游戏容器搭建

最终,还是在官方的GitHub上发现这条Issue,从而找到了Android上渲染DragonBones的方式。Korge的介绍是这样的

Modern MultiplatfORM Game Engine for Kotlin.

Korge的基本用法

1)创建 DragonBones Scene

class DisplayChangeImgScene : BaseDbScene() {
    compaNIOn object {
        private const val SKE_JSON = "mecha_1004d_show/mecha_1004d_show_ske.json"
        private const val TEX_JSON = "mecha_1004d_show/mecha_1004d_show_tex.json"
        private const val TEX_PNG = "mecha_1004d_show/mecha_1004d_show_tex.png"
    }
    private val factory = KorgeDbFactory()
    override suspend fun Container.createSceneArmatuRedisplay(): KorgeDbArmatureDisplay {
        val skeDeferred = asyncImmediately { res[SKE_JSON].readString() }
        val texDeferred = asyncImmediately { res[TEX_JSON].readString() }
        val imgDeferred = asyncImmediately { res[TEX_PNG].readBitmap().mipmaps() }
​
        val skeJsonData = skeDeferred.await()
        val texJsonData = texDeferred.await()
        factory.parseDragonBonesData(Json.parse(skeJsonData)!!)
        factory.parseTextureAtlasData(Json.parse(texJsonData)!!, imgDeferred.await())
​
        val armatureDisplay = factory.buildArmatureDisplay("mecha_1004d")!!.position(500, 700)
        armatureDisplay.animation.play("idle")
​
        return armatureDisplay
    }
}

2)使用KorgeAndroidView加载 Scene Module

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }​
    private val slotDisplayModule by sceneModule<DisplayChangeImgScene>()​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        binding.root.addView(KorgeAndroidView(this).apply { 
            loadModule(slotDisplayModule)
        })
    }
}

3)sceneModule 函数

@MainThread
inline fun <reified DS : BaseDbScene> Activity.sceneModule(
    windowWidth: Int = resources.displayMetrics.widthPixels,
    windowHeight: Int = resources.displayMetrics.heightPixels
): Lazy<Module> {
    return SceneModuleLazy(DS::class, windowWidth, windowHeight)
}
class SceneModuleLazy<DS : BaseDbScene>(
    private val dbSceneClass: KClass<DS>,
    private val width: Int,
    private val height: Int
) : Lazy<Module> {
    private var cached: Module? = null​
    override val value: Module
        get() {
            return cached ?: object : Module() {
                override val mainScene = dbSceneClass
                override suspend fun AsyncInjector.configure() {
                    mapPrototype(dbSceneClass) {
                        val sceneInstance = Class.forName(dbSceneClass.qualifiedName!!).newInstance()
                        sceneInstance as DS
                    }
                }
                override val fullscreen = true​
                override val size: SizeInt
                    get() = SizeInt(width, height)
                override val windowsize: SizeInt
                    get() = SizeInt(width, height)
            }
        }​
    override fun isInitialized(): Boolean = cached != null
}

上面就是最简单的Demo,通过加载DragonBones的配置数据即可显示骨骼动画。

实现换装的多种实现

静态换装 vs 动态换装

静态换装

如果换装的素材是固定的,可以预先放置在插槽里,通过切换插槽的displayIndex实现换装。

在骨骼动画设计时,每个slot可对应多个display,例如:

{
  "name": "weapon_hand_l",
  "display": [
    {
      "name": "weapon_1004_l",
      "transform": {
        "x": 91.22,
        "y": -30.21
      }
    },
    {
      "name": "weapon_1004b_l",
      "transform": {
        "x": 122.94,
        "y": -44.14
      }
    },
    {
      "name": "weapon_1004c_l",
      "transform": {
        "x": 130.95,
        "y": -56.95
      }
    },
    {
      "name": "weapon_1004d_l",
      "transform": {
        "x": 134.67,
        "y": -55.25
      }
    },
    {
      "name": "weapon_1004e_l",
      "transform": {
        "x": 155.62,
        "y": -59.2
      }
    }
  ]
}

在代码中,可直接切换display进行换装,即:

    private var leftWeaponIndex = 0
    private val leftDisplayList = listOf(
        "weapon_1004_l", "weapon_1004b_l", "weapon_1004c_l", "weapon_1004d_l", "weapon_1004e_l"
    )
    override suspend fun Container.createSceneArmatureDisplay(): KorgeDbArmatureDisplay {
        val skeDeferred = asyncImmediately { Json.parse(res["mecha_1004d_show/mecha_1004d_show_ske.json"].readString())!! }
        val texDeferred = asyncImmediately { res["mecha_1004d_show/mecha_1004d_show_tex.json"].readString() }
        val imgDeferred = asyncImmediately { res["mecha_1004d_show/mecha_1004d_show_tex.png"].readBitmap().mipmaps() }
        factory.parseDragonBonesData(skeDeferred.await())
        factory.parseTextureAtlasData(Json.parse(texDeferred.await())!!, imgDeferred.await())​
        val armatureDisplay = factory.buildArmatureDisplay("mecha_1004d")!!.position(500, 700)
        armatureDisplay.animation.play("idle")
​
        val slot = armatureDisplay.armature.getSlot("weapon_hand_l")!!
        mouse {
            upAnywhere {
                leftWeaponIndex++;
                leftWeaponIndex %= leftDisplayList.size
​
                factory.replaceSlotDisplay(
                    dragonBonesName = "mecha_1004d_show",
                    armatureName = "mecha_1004d",
                    slotName = "weapon_hand_l",
                    displayName = leftDisplayList[leftWeaponIndex],
                    slot = slot
                )
            }
        }​
        return armatureDisplay
    }

动态换装

如果换装的素材是不固定的,需要动态获取资源,或者通过一张外部图片来实现换装效果,可以通过修改slot的显示纹理即可实现。

```
// 换装原理是:通过factory.parseTextureAtlasData来解析纹理数据,纹理为外部图片,纹理配置为Mock数据
private fun changeSlotDisplay(slot: Slot, replaceBitmap: Bitmap) {
    // 使用 HashCode 来作为 骨架名称 和 骨骼名称
    val replaceArmatureName = replaceBitmap.hashCode().toString()
    // 需要替换的插槽所包含的显示对象
    val replaceDisplayName = slot._displayFrames.first { it.rawDisplayData != null }.rawDisplayData!!.name
    // 通过factory解析纹理数据
    val mockTexModel = mockTexModel(replaceArmatureName, replaceDisplayName, replaceBitmap.width, replaceBitmap.height)
    val textureAtlasData = Json.parse(gson.toJson(mockTexModel))!!
    factory.parseTextureAtlasData(textureAtlasData, replaceBitmap.mipmaps())
​
    // 替换 Display 的纹理,替换的图片和原图大小、位置一致
    val replaceTextureData = getReplaceDisplayTextureData(replaceArmatureName, replaceDisplayName)
    slot.replaceTextureData(replaceTextureData)
​
    slot._displayFrame?.displayData?.transform?.let {
        // 修改 display 相对于 slot 的位置、初始缩放等配置
    }
}
private fun getReplaceDisplayTextureData(replaceArmatureName: String, replaceDisplayName: String): TextureData {
    val data = factory.getTextureAtlasData(replaceArmatureName)
    data!!.fastForEach { textureAtlasData ->
        val textureData = textureAtlasData.getTexture(replaceDisplayName)
        if (textureData != null) {
            return textureData
        }
    }
    throw Exception("getNewDisplayTextureData null")
}
private fun mockTexModel(armatureName: String, displayName: String, imgW: Int, imgH: Int): DragonBonesTexModel {
    val originTexModel = gson.fromJson(texJsonData, DragonBonesTexModel::class.java)
​
    val subTexture: DragonBonesTexModel.SubTexture = run loop@{
        originTexModel.subTexture.forEach { subTexture ->
            if (subTexture.name == displayName) {
                return@loop subTexture.apply {
                    this.x = 0
                    this.y = 0
                }
            }
        }
        throw Exception("Can not find replace display!")
    }
    return DragonBonesTexModel(
        name = armatureName,
        width = imgW,
        height = imgH,
        subTexture = listOf(subTexture)
    )
}
```

包含动画 vs 不包含动画

如果换装的部位不包含动画,则可以使用图片做为换装素材,具体实现方法如上。 如果换装的部位包含动画,则可以使用子骨架做为换装的素材,api调用方法和换图片是一样的,只不过换进去的是子骨架的显示对象,在引擎层面,图片和子骨架的显示对象都是显示对象,所以处理起来是一样的,唯一不同的是子骨架不需要考虑轴点,也不能重新设置轴点,因为他自身有动画数据相当于已经包含轴点信息。

先将原始骨骼动画文件中,该slot的display信息定义为空。例如:

{
  "name": "1036",
  "display": [
    {
      "name": "blank"
    }
  ]
},
{
  "name": "1082",
  "display": [
    {
      "name": "blank"
    }
  ]
},

在子骨架中定义 slot 的 display 信息。例如:

           "slot": [
                {
                    "name": "1019",
                    "parent": "root"
                }
            ],
            "skin": [
                {
                    "name": "",
                    "slot": [
                        {
                            "name": "1019",
                            "display": [
                                {
                                    "type": "mesh",
                                    "name": "glove/2080500b",
                                    "width": 159,
                                    "height": 323,
                                    "vertices": [
                                        104.98,
                                        -1078.6,
                                        108.08,
                                        -1094.03
                                    ],
                                    "uvs": [
                                        0.45257,
                                        0.1035,
                                        0.4721,
                                        0.15156,
                                        0.4234,
                                        0.05575
                                    ],
                                    "triangles": [
                                        7,
                                        11,
                                        18,
                                        20
                                    ],
                                    "weights": [
                                        2,
                                        3,
                                        0.92
                                    ],
                                    "slotPose": [
                                        1,
​
                                        0,
                                        0
                                    ],
                                    "bonePose": [
                                        6,
                                        0.193207,
​
                                        139.903737,
                                        -897.076346
                                    ],
                                    "edges": [
                                        19,
                                        18,
                                        18,
                                        20,
                                        19
                                    ],
                                    "userEdges": [
                                        16,
                                        11,
                                        7
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ],

使用子骨架的显示对象进行替换,以下是使用直接替换 skin 的方式,和替换 display 的原理相同。

private suspend fun replaceDragonBonesDisplay(armatureDisplay: KorgeDbArmatureDisplay) {
    val path = "you_xin/suit1/replace/"
    val dragonBonesJSONPath = path + "xx_ske.json"
    val textureAtlasJSONPath = path + "xx_tex.json"
    val textureAtlasPath = path + "xx_tex.png"
    // 加载子骨架数据
    factory.parseDragonBonesData(Json.parse(res[dragonBonesJSONPath].readString())!!)
    factory.parseTextureAtlasData(
        Json.parse(res[textureAtlasJSONPath].readString())!!,
        res[textureAtlasPath].readBitmap().mipmaps()
    )
    // 获取解析后的骨骼数据
    val replaceArmatureData = factory.getArmatureData("xx")
    // 通过 replaceSkin 的方式修改 slot display
    factory.replaceSkin(armatureDisplay.armature, replaceArmatureData!!.defaultSkin!!)
}

局部换装 vs 全局换装

之前说的都是局部换装,替换的是纹理集中的一块子纹理,如果希望一次性替换整个纹理集也是支持的。但是纹理集的配置文件不能换(如果配置文件也要换的话,就直接重新构建骨架就好) 也就是说游戏中可以有一套纹理集配置文件对应多个纹理集图片,实现配置文件不变的情况下换整个纹理集。利用这个技术可以实现例如格斗游戏中同样的角色穿不同颜色的衣服的效果。

全局换装之Skin修改

DragonBones支持多套皮肤的切换,如果皮肤时固定的,可预先配置在骨骼动画文件中,需要时直接切换即可。

private fun changeDragonBonesSkin(armatureDisplay: KorgeDbArmatureDisplay) {
    val replaceSkin = factory.getArmatureData("xxx")?.getSkin("xxx") ?: return
    factory.replaceSkin(armatureDisplay.armature, replaceSkin)
}

全局换装之纹理修改

如果皮肤并未固定的,需要动态配置或者网络下发,那么可以使用纹理替换的方式。

private suspend fun changeDragonBonesSkin() {
    val texDeferred = asyncImmediately { res["body/texture_01.png"].readBitmap().mipmaps() }
    factory.updateTextureAtlases(texDeferred.await(), "body")
}

总结

对于一款换装小游戏来讲,使用Spine或者是DragonBones的差异不大,其设计思路基本相同,而且Korge同样也是支持Spine的渲染。从技术实现上,换装的功能并不难实现,只是需要考虑的细节方面还有很多,例如:

  • 服装商城的在线配置和管理,并且有些服装还可能自带动画
  • 某些服装可能涉及多个插槽,例如:一套裙子,有一部分的层级在身体前面,另一部分的层级在身体后面,那就意味需要两个插槽才能实现
  • 如果该人物形象在多个界面或者应用中出现,动画效果不同,但是身上的服装相同,需要考虑处理换装后服装同步的问题

到此这篇关于Android 上实现DragonBones换装功能的文章就介绍到这了,更多相关Android  DragonBones换装内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Android 上实现DragonBones换装功能

本文链接: https://lsjlt.com/news/152899.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Android 上实现DragonBones换装功能
    目录前言技术选型Korge的基本用法实现换装的多种实现静态换装 vs 动态换装静态换装动态换装包含动画 vs 不包含动画局部换装 vs 全局换装全局换装之Skin修改全局换装之纹理修...
    99+
    2024-04-02
  • Android实现图片上传功能
    最近在开发中,涉及到用户的意见反馈功能这一方面的开发,需要用户输入的文字或者提交的图片,效果大概类似于微信朋友圈那样的图片选择器,一开始自己找了个用universal-imag...
    99+
    2022-06-06
    图片上传 图片 Android
  • Android实现上传图片功能
    本文实例为大家分享了Android实现上传图片功能的具体代码,供大家参考,具体内容如下 设定拍照返回的图片路径 protected void image(String...
    99+
    2024-04-02
  • Android编程实现换肤功能实例
    本文实例讲述了Android编程实现换肤功能的方法。分享给大家供大家参考,具体如下: 本系列专题培训适用范围:初级Android程序员,即有J2SE基础和Android初级水平...
    99+
    2022-06-06
    Android
  • android怎么实现换壁纸功能
    要在Android上实现换壁纸功能,可以按照以下步骤进行:1. 在应用的AndroidManifest.xml文件中添加`SET_W...
    99+
    2023-08-18
    android
  • android实现图片上传功能(springMvc)
    本文实例为大家分享了Android图片上传的具体代码,供大家参考,具体内容如下 Android端: String fileName = tvFilename.getText(...
    99+
    2022-06-06
    图片上传 图片 Android
  • Android实现系统语言切换功能
    简单介绍下这个需求的缘由,这段时间因公司业务需要,其中有一项“设置系统语言”功能,就是在使用APP的过程中,动态的去切换整个Android机器的语言,具体参照手机设置页面有语言...
    99+
    2022-06-06
    系统 Android
  • Android-实现切换Fragment页功能的实现代码
    场景:使用Fragment实现切页。 类结构: 一:Activity Activity中使用getSupportFragmentManager().beginTrans...
    99+
    2022-06-06
    fragment Android
  • Android基于ImageSwitcher实现图片切换功能
    左右切换图片控件大家都用ViewPager, ViewFipper比较多吧,我之前也用ViewPager实现了,使用ViewPager实现左右循环滑动图片,有兴趣的可以去看下,...
    99+
    2022-06-06
    图片 Android
  • Android实现Tab切换界面功能详解
    目录一、实验目的二、实验任务三、实验内容与要求四、实现效果五、代码实现 六、实验总结一、实验目的 1. 掌握各种高级UI控件的基本使用; 2. 能够实现Tab切换效果。 二...
    99+
    2024-04-02
  • Android使用ViewFlipper实现图片切换功能
    今天给大家简单的讲一下Android手势,目前市场上的App中手势的运用比较少。 Android提供了两种手势: ①.Android提供了手势检测,并为手势检测提供了相应的监听器 ②.Android允许开发者添加手势,并提供了相应的API识...
    99+
    2023-05-30
    android viewflipper 图片切换
  • Android如何实现Tab切换界面功能
    这篇文章主要介绍“Android如何实现Tab切换界面功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android如何实现Tab切换界面功能”文章能帮助大家解决问题。一、实验目的 掌握各种高级U...
    99+
    2023-06-30
  • Android实现夜间模式切换功能实现代码
    现在很多App都有夜间模式,特别是阅读类的App,夜间模式现在已经是阅读类App的标配了,事实上,日间模式与夜间模式就是给App定义并应用两套不同颜色的主题,用户可以自动或者手...
    99+
    2022-06-06
    Android
  • Android实现登录注册功能封装
    我们都知道Android应用软件基本上都会用到登录注册功能,那么对一个一个好的登录注册模块进行封装就势在必行了。这里给大家介绍一下我的第一个项目中所用到的登录注册功能的,已经对...
    99+
    2022-06-06
    封装 Android
  • Android实现上传文件功能的方法
    本文所述为一个Android上传文件的源代码,每一步实现过程都备有详尽的注释,思路比较清楚,学习了本例所述上传文件代码之后,你可以应对其它格式文件的上传。实例中主要实现上传文件...
    99+
    2022-06-06
    方法 上传文件 Android
  • Android基于OkHttp实现文件上传功能
    本文实例为大家分享了Android基于OkHttp实现文件上传的具体代码,供大家参考,具体内容如下 一、相关概述 Android请求访问服务端大多数情况下依旧是使用http协议,故而...
    99+
    2024-04-02
  • Android实现应用内置语言切换功能
    一、需求 有时候应用需要在内部切换语言但又不影响系统的语言,比如是应用现在是中文的,系统语言也是中文的,我把应用的切换成英文显示后系统语言还是中文的,系统语言切换后也不会被改...
    99+
    2022-06-06
    Android
  • Android app应用多语言切换功能实现
    本文实例为大家分享了Android app应用实现多语言切换功能,供大家参考,具体内容如下 1.添加多语言文件 在不同的 value 文件夹下(例如 value 、value...
    99+
    2022-06-06
    多语言 app Android
  • Android实现app应用多语言切换功能
    最近在做一个多语言切换的功能,类似于微信的语言切换,搜了下资料基本上都是以下这种: 1. 实现的效果   和微信类似,在设置界面打开切换语言的界面,选择语言后重...
    99+
    2022-06-06
    多语言 app Android
  • Android实现界面左右滑动切换功能
    相信大家一定都使用过手机QQ和微信之类的软件,当我们使用时不难发现其界面的切换不仅可以通过点击页标签来实现,还可以通过左右滑动来实现的,耗子君刚开始学Android时就觉得这样...
    99+
    2022-06-06
    界面 Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作