不是簡單的換貼圖,談談u3d的人物換裝系統(仙劍demo整合換裝系統)

      u3d換裝,遊戲的換裝俗稱紙娃娃系統是遊戲,特別是網絡遊戲的一個比較重要的系統,因爲免費的遊戲是可以通過外裝來賣錢的,這兩年的單機遊戲也都以買豪華版送兩套外裝來吸引玩家,在遊戲裏面與衆不同,是多數玩家所追求的,尤其是,如果遊戲做的越好,數值做的越平衡,當到達版本鎖定的時候,比如魔獸世界現在100級的6.1版本,玩家無論怎麼努力裝等也只能達到接近700裝等,這個時候裝等相近的玩家,就會像不同方向尋求不同,比如坐騎,幻化等等,坐騎我們可以換裝系統的一個變種,幻化就不用說了,就是換裝系統的應用,u3d作爲現階段流行的3d多平臺引擎,這一塊當然必不可少,不過說實話,比起其他一些3d引擎,u3d的換裝系統,官方例子並不是那麼簡單明瞭,我們看下例子這個是我開始的時候修改的一個例子,怎麼做的呢?

很簡單

//換貼圖,直接把這段代碼加在要換貼圖的模型上即可;
private var eyeindex=0;
var eyestextures : Texture2D[];//貼圖集合
private var type:int[];
private var index:int[];
var face1textures : Texture2D[];
var face2textures : Texture2D[];
var hair1textures : Texture2D[];
var hair2textures : Texture2D[];
var pants1textures : Texture2D[];
var pants2textures : Texture2D[];
var shoes1textures : Texture2D[];
var shoes2textures : Texture2D[];
var top1textures : Texture2D[];
var top2textures : Texture2D[];

function Awake(){
	type=new int[5];
	index=new int[5];
	transform.Find("eyes").GetComponent(SkinnedMeshRenderer).material.mainTexture=eyestextures[0];
	transform.Find("face-2").GetComponent(SkinnedMeshRenderer).enabled=false;
	transform.Find("hair-2").GetComponent(SkinnedMeshRenderer).enabled=false;
	transform.Find("pants-2").GetComponent(SkinnedMeshRenderer).enabled=false;
	transform.Find("shoes-2").GetComponent(SkinnedMeshRenderer).enabled=false;
	transform.Find("top-2").GetComponent(SkinnedMeshRenderer).enabled=false;
}

function Update ()
{
}

//換裝方法因模型而異
function changeCloth(name1 : String,name2 : String,texture1:Texture2D[] ,texture2:Texture2D[] ,idx,lens){
	if(index[idx]<lens-1){
		index[idx]++;
	}else {
		index[idx]=0;
		if(type[idx]==0){
			transform.Find(name1).GetComponent(SkinnedMeshRenderer).enabled=false;
			transform.Find(name2).GetComponent(SkinnedMeshRenderer).enabled=true;
			type[idx]=1;
		}else{
			transform.Find(name2).GetComponent(SkinnedMeshRenderer).enabled=false;
			transform.Find(name1).GetComponent(SkinnedMeshRenderer).enabled=true;
			type[idx]=0;
		}
	}
	if(type[idx]==0){
	transform.Find(name1).GetComponent(SkinnedMeshRenderer).material.mainTexture=texture1[index[idx]];
	Debug.Log("name1index[idx]="+index[idx]);}
	else{
	transform.Find(name2).GetComponent(SkinnedMeshRenderer).material.mainTexture=texture2[index[idx]];
	Debug.Log("name2index[idx]="+index[idx]);}
	
}

function OnGUI(){
	GUILayout.Label("");
	if(GUILayout.Button ("eye")) {
		if(eyeindex<2)
		eyeindex++;
		else
		eyeindex=0;
		transform.Find("eyes").GetComponent(SkinnedMeshRenderer).material.mainTexture=eyestextures[eyeindex];
	}else if(GUILayout.Button ("face")){
		changeCloth("face-1","face-2",face1textures,face2textures,0,1);
	}else if(GUILayout.Button ("hair")){
		changeCloth("hair-1","hair-2",hair1textures,hair2textures,1,3);
	}else if(GUILayout.Button ("pant")){
		changeCloth("pants-1","pants-2",pants1textures,pants2textures,2,3);
	}else if(GUILayout.Button ("shoes")){
		changeCloth("shoes-1","shoes-2",shoes1textures,shoes2textures,3,3);
	}else if(GUILayout.Button ("top")){
		changeCloth("top-1","top-2",top1textures,top2textures,4,3);
	}
}
這個就是綁在身上的源碼,看到這些可能有些人不知道什麼東西了,我們看下具體的屬性參數,有人會說,這花花綠綠的何方妖孽,對,這個模型上我們看,好像很奇怪的感覺,是的,因爲這個模型本身就不是一個正常的人體模型,我們看下模型文件哇哦,這都是什麼,一個模型裏面包括了一個眼睛模型,兩張臉,兩個頭,兩個上身,兩種腿,還有2種腳,對,就是這樣,因爲我們事先將模型做了很多套,在用到的時候,纔可以想調用什麼就調用什麼,

好了,所有模型的skin都在這了,我們可以看到這些模型都綁定在avtar上面,然後每一個都有一個默認的材質,那我們只能使用這些嗎,比如有兩個上身,就只有兩套衣服,答案當然不是,我們看代碼綁定後是什麼樣的

看到了吧,我們設定的texture數組容器,都在這裏,我們展開看到了吧。每一個skin上面還有1到3種不同的貼圖,這樣,就做到了衣服的幾何數量搭配,上面的代碼中,changeCloth("face-1","face-2",face1textures,face2textures,0,1);參數的最後一個1,就是textrue的套數,換衣服時,會根據skin的數量先做遍歷,當第一套skin沒有texture了,我們換下一套,這就是我們換裝的一個簡單做法,下面我們將另外一種換裝方法

上面的方法我們實現了人物換裝,但我們遊戲demo裏並沒有使用這種方法,我們看下demo裏面的代碼

using UnityEngine;
using System.Collections.Generic;

public class AvatarSys : MonoBehaviour {

    private Transform source;
    private Transform target;

    private GameObject sourceobj;
    private GameObject targetobj;

    private Dictionary<string, Dictionary<string, Transform>> data = new Dictionary<string, Dictionary<string, Transform>>();
    private Transform[] hips;

    private Dictionary<string, SkinnedMeshRenderer> targetSmr = new Dictionary<string, SkinnedMeshRenderer>();

    private Animation mAnim;
    private AnimationClip mClip;

    public static AvatarSys instance;

	// Use this for initialization
	void Start () {
        instance = this;

       InstantiateSkeleton();
        InstantiateAvatar();
        LoadAvatarData(source);
        hips = target.GetComponentsInChildren<Transform>();
        InitAvatar();
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}

    void InstantiateAvatar()
    {
        sourceobj = Instantiate(Resources.Load("FemaleAvatar")) as GameObject;
        source = sourceobj.transform;
        sourceobj.SetActive(false);
    }

    void InstantiateSkeleton()
    {
        targetobj = Instantiate(Resources.Load("targetmodel")) as GameObject;
        target = targetobj.transform;
    }

    void LoadAvatarData(Transform source)
    {
        if (source == null)
            return;
        SkinnedMeshRenderer[] parts = source.GetComponentsInChildren<SkinnedMeshRenderer>(true);
        foreach (SkinnedMeshRenderer part in parts)
        {
            string[] partName = part.name.Split('-');
            if(!data.ContainsKey(partName[0]))
            {
                data.Add(partName[0], new Dictionary<string, Transform>());

                GameObject partObj = new GameObject();
                partObj.name = partName[0];
                partObj.transform.parent = target;

                targetSmr.Add(partName[0], partObj.AddComponent<SkinnedMeshRenderer>());
            }

            data[partName[0]].Add(partName[1], part.transform);
        }

    }

    public void ChangePart(string part, string item)
    {
        SkinnedMeshRenderer smr = data[part][item].GetComponent<SkinnedMeshRenderer>();

        List<Transform> bones = new List<Transform>();
        foreach (Transform bone in smr.bones)
        {
            foreach (Transform hip in hips)
            {
                if(hip.name != bone.name)
                {
                    continue;
                }
                bones.Add(hip);
                break;
            }
            
        }

        targetSmr[part].sharedMesh = smr.sharedMesh;
        targetSmr[part].bones = bones.ToArray();
        targetSmr[part].materials = smr.materials;
    }


    void InitAvatar()
    {
        ChangePart("foot", "003");
        ChangePart("coat", "003");
        ChangePart("head", "003");
        ChangePart("pant", "003");
        ChangePart("hand", "003");
        ChangePart("hair", "003");
    }

}
                            
這個代碼看上去跟上面的代碼差別很大,不僅僅是語言上的,上面用js,下面c#(這個沒關係,上面的c#我也會提供),下面的代碼實現的效果是什麼樣的呢?

細心的看,其實小姑娘在跳動過程中,已經換裝了,這個跟上面的有什麼區別呢?應該說沒啥區別,不過官方最新的例子是這種模式,具體原因不得而知,我說下這種換裝的原理,我們看下模型,怎麼會有兩個,上面的代碼中,也是有兩個模型 

void InstantiateAvatar()
    {
        sourceobj = Instantiate(Resources.Load("FemaleAvatar")) as GameObject;
        source = sourceobj.transform;
        sourceobj.SetActive(false);
    }


    void InstantiateSkeleton()
    {
        targetobj = Instantiate(Resources.Load("targetmodel")) as GameObject;
        target = targetobj.transform;
    }
也是兩個模型的加載代碼,這個又是爲什麼呢?我們看下下面的模型,上面的模型跟我們上面的例子模型是一樣的不說了,
驚奇的發現啥也沒有,就是一個avtar骨骼,沒有任何skin信息,對,我們看下他綁定的屬性,也是這樣,爲什麼呢,他的原理是在遊戲中,把化身就做成一個空的骨架,我們不考慮這個人物是男女,種族,高矮胖瘦,我們的人物設定是在開始時,我們通過加載模型生成字典賦值綁在這個骨架上的,如果我們賦值綁的是個男性,他就是男的,女的就是女的,這種方法的靈活度大大的加強了,我們不在需要把模型拖到場景中,向上面的例子一樣先設定texture,我們只要在模型中把textrue做好就行了,我們看下什麼樣的,我們看跟上面例子相比,我們衣服skin綁了多套材質球,不需要在像上面那樣,在texture數組中指定幾套貼圖了,從而大大簡化了流程,如果想在場景中變化出場人物,比如好仙劍中的tab鍵換人一樣,只要
sourceobj = Instantiate(Resources.Load("FemaleAvatar")) as GameObject;
這個載入的預製件換成其他人就好了,官方的例子也是一個人物可以男女間自由裝換,在試衣間裏面照鏡子,因爲我們實際需要下面的方法更適合,所以我們仙劍demo中整合了下面的方法,不過因爲現在網上下載的模型沒有現成的,要是自己做模型,可能做好要下個月了都不一定能完成,所以具體的先放着,以後再說吧,好了今天這個換裝系統就到這裏,對了我要說說這個大概在遊戲中怎麼用,首先,我們不可能在遊戲畫面中做幾個換裝按鈕,按正常的方式應該是我們把裝備放到裝備欄,然後身上的模型發生變化,我們仙劍demo中集成的揹包和裝備欄是這樣的,揹包中的裝備右鍵點擊,自動裝備到裝備欄的位置(具體裝備屬性是自動識別的),
因爲開始我們身上沒有裝備,所以爲了演示,先去鐵匠那買一把武器

不要吐槽武器店,這個是我整合的一個較爲成熟rpg輔助系統,因爲只是demo1.0所以只是把代碼整合到一起,界面貼圖還沒做修改(別人給了我一套仙劍5的貼圖,有時間我會換上),demo2.0的時候我們做界面和效果優化,所以,現在就先別吐槽了,揹包和裝備蘭下一步會整合進右上角頭像點擊後彈出的ngui裝備界面中,這是以後的事,今天先不考慮。我們還是說換裝,揹包的東西,直接右鍵裝備到裝備欄,這個時候我們可以向換裝系統發一個變量,通知換裝系統根據裝備列表的編號,來做到時時更新模型和貼圖,這篇就到這。後面還有回合戰鬥系統,序列化存儲,場景轉換特效(異步加載),現有揹包和裝備欄統一整合到ngui,迷宮關卡設定基礎等等內容,想想內容還挺多的,慢慢來吧,另外這個場景是最開始在網上下載的,後來我發現模型原來的碰撞盒子都使用了默認材質碰撞, 這個在效率上有些問題,後面場景可能會做些變化,不過那是以後2.0的事情了,佔時沒時間考慮。

後面方法的工程文件以後會跟demo一起發,上面的例子工程樣例下載在這裏



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章