<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近在預研一款換裝的小遊戲,通過在積分樂園中兌換服裝,就可以在不同場景中展示穿上新服裝的角色。對於這類有主題形象的動畫,自然就想到了骨骼動畫,通過網格自由變形和蒙皮技術就能在視覺上呈現所需要的動畫效果,並且骨骼動畫也支援面板替換,或者插槽的圖片替換,對於換裝的需求比較友好。因此決定使用骨骼動畫來實現換裝小遊戲的Demo,以下就是在Android平臺上實現DragonBones換裝的過程。
對於DragonBones在Android端的渲染顯示,有多個方案可以選擇,例如:白鷺引擎或者Cocos2d遊戲引擎。最終選擇使用korge來進行渲染,為什麼拋棄Cocos2d這個廣泛使用的遊戲引擎來渲染呢?主要理由是:
最終,還是在官方的Github上發現這條Issue,從而找到了Android上渲染DragonBones的方式。Korge的介紹是這樣的
Modern Multiplatform Game Engine for Kotlin.
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的設定資料即可顯示骨骼動畫。
如果換裝的素材是固定的,可以預先放置在插槽裡,通過切換插槽的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) ) } ```
如果換裝的部位不包含動畫,則可以使用圖片做為換裝素材,具體實現方法如上。 如果換裝的部位包含動畫,則可以使用子骨架做為換裝的素材,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!!) }
之前說的都是區域性換裝,替換的是紋理集中的一塊子紋理,如果希望一次性替換整個紋理集也是支援的。但是紋理集的組態檔不能換(如果組態檔也要換的話,就直接重新構建骨架就好) 也就是說遊戲中可以有一套紋理集組態檔對應多個紋理集圖片,實現組態檔不變的情況下換整個紋理集。利用這個技術可以實現例如格鬥遊戲中同樣的角色穿不同顏色的衣服的效果。
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換裝內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45