我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android 上实现DragonBones换装功能

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android 上实现DragonBones换装功能

前言

最近在预研一款换装的小游戏,通过在积分乐园中兑换服装,就可以在不同场景中展示穿上新服装的角色。对于这类有主题形象的动画,自然就想到了骨骼动画,通过网格自由变形和蒙皮技术就能在视觉上呈现所需要的动画效果,并且骨骼动画也支持皮肤替换,或者插槽的图片替换,对于换装的需求比较友好。因此决定使用骨骼动画来实现换装小游戏的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换装内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android 上实现DragonBones换装功能

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

android怎么实现换壁纸功能

要在Android上实现换壁纸功能,可以按照以下步骤进行:1. 在应用的AndroidManifest.xml文件中添加`SET_WALLPAPER`权限。```xml```2. 创建一个按钮或者其他触发换壁纸的控件。```xmlandro
2023-08-18

Android编程实现换肤功能实例

本文实例讲述了Android编程实现换肤功能的方法。分享给大家供大家参考,具体如下: 本系列专题培训适用范围:初级Android程序员,即有J2SE基础和Android初级水平。J2SE基础是指掌握JAVA语法,1.5、1.6新增的语法不完
2022-06-06

Android实现图片上传功能

最近在开发中,涉及到用户的意见反馈功能这一方面的开发,需要用户输入的文字或者提交的图片,效果大概类似于微信朋友圈那样的图片选择器,一开始自己找了个用universal-image-loader框架写的,很容实现,但是容易出现内存溢出,并且不
2022-06-06

Android实现系统语言切换功能

简单介绍下这个需求的缘由,这段时间因公司业务需要,其中有一项“设置系统语言”功能,就是在使用APP的过程中,动态的去切换整个Android机器的语言,具体参照手机设置页面有语言切换功能。起初想来是很简单的事情嘛,不就是个简单的资源国际化嘛,
2022-06-06

Android-实现切换Fragment页功能的实现代码

场景:使用Fragment实现切页。类结构:一:ActivityActivity中使用getSupportFragmentManager().beginTransaction()来填充一个Fragment(管理用的FragmentA)Act
2022-06-06

android实现图片上传功能(springMvc)

本文实例为大家分享了Android图片上传的具体代码,供大家参考,具体内容如下 Android端:String fileName = tvFilename.getText().toString();RequestBody descripti
2022-06-06

Android如何实现Tab切换界面功能

这篇文章主要介绍“Android如何实现Tab切换界面功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android如何实现Tab切换界面功能”文章能帮助大家解决问题。一、实验目的1. 掌握各种高
2023-06-30

Android实现夜间模式切换功能实现代码

现在很多App都有夜间模式,特别是阅读类的App,夜间模式现在已经是阅读类App的标配了,事实上,日间模式与夜间模式就是给App定义并应用两套不同颜色的主题,用户可以自动或者手动的开启,今天用Android自带的support包来实现夜间模
2022-06-06

Android使用ViewFlipper实现图片切换功能

今天给大家简单的讲一下Android手势,目前市场上的App中手势的运用比较少。 Android提供了两种手势: ①.Android提供了手势检测,并为手势检测提供了相应的监听器 ②.Android允许开发者添加手势,并提供了相应的API识
2023-05-30

Android基于ImageSwitcher实现图片切换功能

左右切换图片控件大家都用ViewPager, ViewFipper比较多吧,我之前也用ViewPager实现了,使用ViewPager实现左右循环滑动图片,有兴趣的可以去看下,今天介绍的是基于ImageSwitcher实现的左右切换图片,先
2022-06-06

Android实现登录注册功能封装

我们都知道Android应用软件基本上都会用到登录注册功能,那么对一个一个好的登录注册模块进行封装就势在必行了。这里给大家介绍一下我的第一个项目中所用到的登录注册功能的,已经对其进行封装,希望能对大家有帮助,如果有什么错误或者改进的话希望各
2022-06-06

Android实现应用内置语言切换功能

一、需求有时候应用需要在内部切换语言但又不影响系统的语言,比如是应用现在是中文的,系统语言也是中文的,我把应用的切换成英文显示后系统语言还是中文的,系统语言切换后也不会被改变,还有就是有些机的系统是被改造精简过的,比如有些国产机的系统的语言
2022-06-06

Android实现app应用多语言切换功能

最近在做一个多语言切换的功能,类似于微信的语言切换,搜了下资料基本上都是以下这种:1. 实现的效果 和微信类似,在设置界面打开切换语言的界面,选择语言后重启 HomeActivity,语言切换完成,下次重新打开 App ,也是用户设置的语言
2022-06-06

Android实现界面左右滑动切换功能

相信大家一定都使用过手机QQ和微信之类的软件,当我们使用时不难发现其界面的切换不仅可以通过点击页标签来实现,还可以通过左右滑动来实现的,耗子君刚开始学Android时就觉得这样的滑动十分酷炫,十分想要自己来实现它。相信大家也和耗子君一样,想
2022-06-06

Android app应用多语言切换功能实现

本文实例为大家分享了Android app应用实现多语言切换功能,供大家参考,具体内容如下 1.添加多语言文件 在不同的 value 文件夹下(例如 value 、value-en、values-zh-rTW 文件夹)添加不同语言的 st
2022-06-06

Android实现上传文件功能的方法

本文所述为一个Android上传文件的源代码,每一步实现过程都备有详尽的注释,思路比较清楚,学习了本例所述上传文件代码之后,你可以应对其它格式文件的上传。实例中主要实现上传文件至Server的方法,允许Input、Output,不使用Cac
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录