友快網

導航選單

【週六見分享】羽毛先生:換裝的需求,如何從換裝中獲取更多的資源

無論是 2D 或是 3D 遊戲,換裝類都是比較受歡迎的遊戲,也是遊戲開發者經常需要面對的開發需求。

本文主要介紹 3D 換裝需求,關於 2D 換裝(Spine 或龍骨)後續會向大家介紹。

本週六羽毛先生也將出席

Cocos Star Meetings 廣州站

,為大家帶來《3D 專案經驗分享》,歡迎大家到現場一起快樂交流玩耍~

需求

從換裝的方式分類,可以分為整體換裝以及區域性換裝。整體換裝較為簡單,我們就不做討論,本文主要介紹一下區域性換裝(其實理解過後也是非常的簡單)。

從換裝的模型分類, 主要分為兩種型別:

一種型別是對於靜態模型的換裝,就是直接將身體需要換的 Mesh 更新即可。

另一種型別是動態模型的換裝(有動作的模型)。

本文主要介紹

動態模型

的換裝實現。

效果展示

(素材僅用於學習交流)

原理介紹

在開始描述換裝前,首先要具備骨骼動畫的知識。

如果對骨骼動畫的原理不熟悉,換裝是比較難以理解的。換裝的核心其實並不在換上,而是要理解為什麼能換,而這些都和骨骼動畫密不可分。

骨骼動畫的組成:

圖 1(引用於 Shader 實驗室)

網格(Mesh):

模型(Model)是由一個個三角形組成的,而這種三角形的學名則是網格(Mesh)

網格蒙皮資料(Skin Info)

頂點的 Skin 資料包括頂點受哪些骨骼影響以及這些骨骼影響該頂點時的權重(Weight),另外對於每塊骨骼還需要骨骼偏移矩陣(BoneOffsetMatrix)用來將頂點從Mesh空間變換到骨骼空間。可簡單理解為:SkinMesh = Mesh+Skin Info

骨骼(Skeleton):

如圖 1,骨架由一系列具有層次關係的關節(骨骼)和關節鏈組成,是一種樹結構,選擇其中一個是根關節,其它關節是根關節的子孫,可以透過平移和旋轉根關節移動,並確定整個骨架在世界空間中的位置和方向。

骨骼的動畫(關鍵幀)資料

骨骼動畫是透過關鍵幀驅動骨骼運動,隨之依次調整每塊骨頭的朝向和座標,骨頭再帶動頂點運動,蒙皮資訊描述了每個頂點受哪些骨頭的影響,以及他們的權重,這樣骨骼動畫就實現了運動以及形變。

實現思路

匯入模型進入 Creator,可發現節點下含有 SkinnedMeshRenderer 元件,其中含有 Mesh 屬性,按照我的理解這裡的 Mesh 特指 SkinMesh = Mesh+Skin Info,而非普通的靜態 Mesh。

動態模型換裝需要更新 SkinnedMeshRenderer 元件的中 SkinMesh,Skeleton(骨骼資源), SkinningRoot(骨骼根節點的引用——控制此模型的動畫元件所在節點)。

本案例中採取直接更換蒙皮網格渲染器元件(SkinnedMeshRenderer)的方式實現換裝。

實現步驟

骨骼動畫及部位裝備 Prefab 的製作,核心——共享一套骨骼。動畫師製作時,同一部位的不同裝備繫結同一根骨骼,整體輸出,在 Creator 中將各部件裝備製作為 Prefab 後從主角刪除,主角只保留一套預設裝備。

主角節點需要關閉預烘焙功能,否則無法實時運算以實現換裝功能。

初始化模型。建立 Map,這一步是為了後續替換裝備時可以檢索到對應部位的節點。

替換裝備節點:

刪除舊裝備節點。檢索 Map,根據部位 key-PartName 獲得 OldNode 引用,移除 OldNode(保留骨骼根節點引用 SkinningRoot,後續備用)。

增加新裝備節點,載入部位 A 新裝備 Prefab 並例項化為 NewNode,新增 NewNode。

重新整理部位 key-PartName 的 value 值為 NewNode。

重新整理骨骼,取得步驟 1 中的 SkinningRoot 來重新整理 NewNode 的 SkinningRoot,完成(我實現到這步,後續步驟為了節省效能大家可以研究)。

合併 Mesh。

合併貼圖(貼圖的寬高最好是 2 的 N 次方的值)。

重新計算 UV。

核心程式碼

import { _decorator, Component, Node, resources, Prefab, instantiate, SkinnedMeshRenderer, EventTouch, SkeletalAnimation } from ‘cc’;

const { ccclass, property } = _decorator;

@ccclass(‘ChangeCloth’)

export class ChangeCloth extends Component {

@property({

type: Node

})

modelNode!: Node;

sex: string = “male”;

bodyPart: string[] = [“hair”, “top”, “pants”, “shoes”];

data: Map = new Map();

start() {

this。initAllData();

}

initAllData() {

this。data。clear();

for (let i = ; i

let partName = this。bodyPart[i];

let nodeName = `${this。sex}_$-1`;

let nodePart = this。modelNode。getChildByName(nodeName);

if (nodePart) {

console。debug(“init part”, nodeName);

this。data。set(partName, nodePart);

}

}

}

changeCloth(partName: string, index: number) {

resources。load(`prefab/${this。sex}_$-$`, Prefab, (err, prefab) => {

if (err) {

console。debug(err);

return;

}

let oldNode = this。data。get(partName);

let oldModel = oldNode?。getComponent(SkinnedMeshRenderer);

let newNode = instantiate(prefab);

let newModel = newNode。getComponent(SkinnedMeshRenderer);

if (oldModel?。skinningRoot && newModel) {

newModel。skinningRoot = oldModel?。skinningRoot;

oldNode?。removeFromParent();

this。modelNode。addChild(newNode);

this。data。set(partName, newNode);

}

})

}

onClickChange(touch: EventTouch, data: string) {

console。debug(“onClickChange”, data);

let params = data。split(“-”);

this。changeCloth(params[], parseInt(params[1]));

}

onClickAnimation(touch: EventTouch, animationName: string) {

console。debug(“onClickAnimation”, animationName);

this。modelNode。getComponent(SkeletalAnimation)!。play(animationName);

}

update(deltaTime: number) {

// [4]

}

}

小結

換裝的核心是要理解為什麼能換,理解了骨骼動畫的原理以及構成,一旦弄清“為什麼”?,換裝的實現就會是非常簡單的一件事了。

如果羽毛的理解存在錯誤,歡迎回復進行指導。

上一篇:許秀再現faker心酸一幕:我們不是死對頭,我們是死對頭,我們是死對
下一篇:韓涵的91gf都被他打趴了!看看這些主播帶著的比賽,你還記得嗎?