Unity實(shí)現(xiàn)簡(jiǎn)單換裝系統(tǒng)
關(guān)于Unity的換裝,網(wǎng)上有幾篇文章,我之前也簡(jiǎn)單的描述過實(shí)現(xiàn)。不過那個(gè)時(shí)候只是粗略的試驗(yàn)了下。今天好好梳理了下代碼。
先上代碼(自己的游戲項(xiàng)目,不是公司的,所以放心的貼上項(xiàng)目代碼了,部分引用到其他的功能文件,但是核心代碼無影響,這里主要看一下細(xì)節(jié)和思路)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum AvatarPart
{
helmet,
chest,
shoulders,
gloves,
boots,
}
// 人物換裝
public class ActorAvatar : MonoBehaviour
{
// 換裝的部件信息
public class AvatarInfo
{
public string partName;
public GameObject defaultPart;
public GameObject avatarPart;
}
protected int _bodyModelId;
protected GameObject _body; // 基礎(chǔ)模型動(dòng)畫
protected Dictionary<string, AvatarInfo> _avatarInfo = new Dictionary<string, AvatarInfo>(); // 換裝信息
private List<int> _avatarLoadQueue = new List<int>();
void Start()
{
}
void Update()
{
}
// 創(chuàng)建模型
public void LoadModel(int modelId)
{
_bodyModelId = modelId;
ResourceMgr.Instance.LoadModel(modelId, (GameObject obj) =>
{
_body = obj;
// 換裝請(qǐng)求
if (_avatarLoadQueue.Count > 0) {
foreach (var avatar in _avatarLoadQueue) {
LoadAvatar(avatar);
}
_avatarLoadQueue.Clear();
}
}, true);
}
// 給人物換裝
public void LoadAvatar(int avatarId)
{
// 如果還沒有加載完基礎(chǔ)模型,則等待
if (_body == null) {
_avatarLoadQueue.Add(avatarId);
return;
}
AvatarData adata = DataMgr.Instance.GetAvatarData(avatarId);
ResourceMgr.Instance.LoadModel(adata.model, (GameObject obj) => {
ChangeAvatar(obj, adata.addpart);
});
}
// 替換部件
public void ChangeAvatar(GameObject avatarModel, string partName)
{
// 先卸載當(dāng)前部件
AvatarInfo currentInfo;
if (_avatarInfo.TryGetValue(partName, out currentInfo)) {
if (currentInfo.avatarPart != null) {
Destroy(currentInfo.avatarPart);
currentInfo.avatarPart = null;
}
if (currentInfo.defaultPart != null) {
currentInfo.defaultPart.SetActive(true);
}
}
// avatarModel是一個(gè)resource,并沒有實(shí)例化
if (avatarModel == null) {
return;
}
// 需要替換的部件
Transform avatarPart = GetPart(avatarModel.transform, partName);
if (avatarPart == null) {
Debug.LogError(string.Format("Avatar Part Not Found: ", partName));
return;
}
// 將原始部件隱藏
Transform bodyPart = GetPart(_body.transform, partName);
if (bodyPart != null) {
bodyPart.gameObject.SetActive(false);
}
// 設(shè)置到body上的新物件
GameObject newPart = new GameObject(partName);
newPart.transform.parent = _body.transform;
SkinnedMeshRenderer newPartRender = newPart.AddComponent<SkinnedMeshRenderer>();
SkinnedMeshRenderer avatarRender = avatarPart.GetComponent<SkinnedMeshRenderer>();
// 刷新骨骼模型數(shù)據(jù)
SetBones(newPart, avatarPart.gameObject, _body);
newPartRender.sharedMesh = avatarRender.sharedMesh;
newPartRender.sharedMaterials = avatarRender.sharedMaterials;
// 記錄換裝信息
AvatarInfo info = new AvatarInfo();
info.partName = partName;
if (bodyPart != null) {
info.defaultPart = bodyPart.gameObject;
} else {
info.defaultPart = null;
}
info.avatarPart = newPart;
_avatarInfo[partName] = info;
}
// 遞歸遍歷子物體
public static Transform GetPart(Transform t, string searchName)
{
foreach (Transform c in t) {
string partName = c.name.ToLower();
if (partName.IndexOf(searchName) != -1) {
return c;
} else {
Transform r = GetPart(c, searchName);
if (r != null) {
return r;
}
}
}
return null;
}
public static Transform FindChild(Transform t, string searchName)
{
foreach (Transform c in t) {
string partName = c.name;
if (partName == searchName) {
return c;
} else {
Transform r = FindChild(c, searchName);
if (r != null) {
return r;
}
}
}
return null;
}
// 刷新骨骼數(shù)據(jù) 將root物體的bodyPart骨骼更新為avatarPart
public static void SetBones(GameObject goBodyPart, GameObject goAvatarPart, GameObject root)
{
var bodyRender = goBodyPart.GetComponent<SkinnedMeshRenderer>();
var avatarRender = goAvatarPart.GetComponent<SkinnedMeshRenderer>();
var myBones = new Transform[avatarRender.bones.Length];
for (var i = 0; i < avatarRender.bones.Length; i++) {
myBones[i] = FindChild(root.transform, avatarRender.bones[i].name);
}
bodyRender.bones = myBones;
}
}
1、Unity換裝有三種需求:
添加武器的掛載式換裝,這個(gè)只要?jiǎng)?chuàng)建對(duì)應(yīng)的模型,并且設(shè)置好transform.parent就可以了。
替換紋理,這個(gè)取到對(duì)應(yīng)的material,然后設(shè)置texture就可以了。
模型部件的替換,這個(gè)是此處處理的,也是相對(duì)最復(fù)雜的換裝。
2、最核心的部分是ChangeAvatar,它完成了模型換裝的功能。模型部件的替換其實(shí)就是替換SkinnedMeshRender中的sharedMesh和sharedMaterials。
(這里稍微插一下sharedMaterials sharedMaterial Materials Material這幾個(gè)變量的區(qū)別。sharedMaterials是共享和引用的關(guān)系,只要修改這個(gè),所有使用到這個(gè)material的模型都會(huì)受到影響。如果是在編輯器模式下,它還會(huì)修改實(shí)際material文件的屬性。Materials是sharedMaterials的一份拷貝,只有當(dāng)前模型使用。materia是materials數(shù)組中的第一個(gè)對(duì)象,這個(gè)僅僅是為了方便書寫而存在的。)
僅僅替換了sharedMesh還不夠,模型會(huì)變成一坨麻花。 還應(yīng)該修改SkinnedMeshRender中的bones屬性,它記錄了模型的骨骼信息(其實(shí)就是一大堆Transform)。 SetBones函數(shù)完成了骨骼替換的操作。它查找avatar部件中的所有骨骼名稱,然后查找當(dāng)前模型中的對(duì)應(yīng)骨骼名字,并存儲(chǔ)起來。這個(gè)數(shù)組就是新部件的骨骼信息。
3、一個(gè)邏輯上的處理細(xì)節(jié)。保留了原始模型的對(duì)應(yīng)部件,并沒有銷毀這個(gè)部件,僅僅是隱藏起來。這樣卸載裝備的時(shí)候,只需要?jiǎng)h掉裝備部件,然后把默認(rèn)部件設(shè)為可見就可以了。
4、可以考慮使用Unity的CombineInstance把模型合并,這樣的好處是可以提高運(yùn)行性能。但是只有材質(zhì)共用一個(gè)的時(shí)候才能真正起到優(yōu)化效果。有個(gè)MeshBaker的插件很酷。如果要進(jìn)行千人戰(zhàn),就必須考慮這方面的優(yōu)化。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Unity Shader實(shí)現(xiàn)線框效果的制作步驟
最近比較忙,今天抽空給大家分享一篇文章,關(guān)于Unity Shader實(shí)現(xiàn)線框效果,本文給大家分享詳細(xì)制作步驟,通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-06-06
C#實(shí)現(xiàn)簡(jiǎn)單播放mp3的方法
這篇文章主要介紹了C#實(shí)現(xiàn)簡(jiǎn)單播放mp3的方法,涉及C#播放多媒體文件的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
Unity3D游戲開發(fā)數(shù)據(jù)持久化PlayerPrefs的用法詳解
在本篇文章里小編給大家整理了關(guān)于Unity3D游戲開發(fā)之?dāng)?shù)據(jù)持久化PlayerPrefs的使用的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們參考下。2019-08-08
NPOI實(shí)現(xiàn)兩級(jí)分組合并功能(示例講解)
下面小編就為大家分享一篇NPOI實(shí)現(xiàn)兩級(jí)分組合并功能的示例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12
基于WPF實(shí)現(xiàn)Message消息提醒控件
這篇文章主要介紹了如何利用WPF實(shí)現(xiàn)Meesage消息提醒控件,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,需要的可以參考一下2023-07-07
C#使用DropDownList綁定添加新數(shù)據(jù)的方法匯總
這篇文章主要介紹了C#使用DropDownList綁定添加新數(shù)據(jù)的方法匯總的相關(guān)資料,需要的朋友可以參考下2016-03-03
Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用
這篇文章主要為大家介紹了Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

