在游戏中,换装效果主要在人物更换服装或者更换皮肤的时候用到。换装主要有三个要点:材质,网格,模型。我们所说的换装其实就是改变角色各个部位上skinnedMeshRender组件的属性。
我们先来看一下实现之后的效果:
我们首先更换Mesh,然后重新将Mesh和骨骼进行绑定。最后进行蒙皮材质的更换就完成了换装。
下面是代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CombinePlayer : MonoBehaviour
{
public GameObject[] bodys;
// Start is called before the first frame update
void Start()
{
//1.网格合并
Mesh mesh = new Mesh();
List<CombineInstance> combines = new List<CombineInstance>();
for (int i = 0; i < bodys.Length; i++)
{
CombineInstance combine = new CombineInstance();
combine.mesh = bodys[i].GetComponentInChildren<SkinnedMeshRenderer>().sharedMesh;
combines.Add(combine);
}
mesh.CombineMeshes(combines.ToArray(), false, false);
GetComponent<SkinnedMeshRenderer>().sharedMesh = mesh;
//2.材质球
List<Material> materials = new List<Material>();
for (int i = 0; i < bodys.Length; i++)
{
//注意 body如果已经实例化可以使用material,如果没实例化请使用sharedMaterial,
//sharedMaterial是材质球本体,不是材质球的复制品
materials.Add(bodys[i].GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial);
}
GetComponent<SkinnedMeshRenderer>().materials = materials.ToArray();
//骨骼
//找到自己的全部骨头
Transform[] allbones = GetComponentsInChildren<Transform>();
//每块骨骼都有单独的名字,要使用名字相同的骨骼来匹配网格
//所以把骨骼以名字为键存入字典,方便查找
Dictionary<string, Transform> dicbones = new Dictionary<string, Transform>();
foreach (var bone in allbones)
{
dicbones.Add(bone.name, bone);
}
//建立一个集合,存储我需要使用的骨骼
List<Transform> mybones = new List<Transform>();
//循环所有部位,找到该部位要使用的骨骼,并将该骨骼对应名称的自己的骨骼存入集合
for (int i = 0; i < bodys.Length; i++)
{
//该部位要使用的骨骼
Transform[] bodybones = bodys[i].GetComponentInChildren<SkinnedMeshRenderer>().bones;
for (int j = 0; j < bodybones.Length; j++)
{
if (dicbones.ContainsKey(bodybones[j].name))
{
//字典里的才是自己的骨骼
mybones.Add(dicbones[bodybones[j].name]);
}
}
}
//给骨骼赋值
GetComponent<SkinnedMeshRenderer>().bones = mybones.ToArray();
}
// Update is called once per frame
void Update()
{
}
}
我们在这里是通过轮转图进行人物的换装的,轮转图的实现原理是:
利用父物体生成子物体选项,子物体利用x轴上的间距和 最中心靠前的物体最大显示(缩放最大),两侧物体逐渐变小(缩放逐渐变小)的原理制造视觉差,从而形成3D效果,之后利用拖拽事件制作拖动旋转。
下面是轮转图的代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 2D轮转图脚本
/// </summary>
public class RotateMap : MonoBehaviour, IDragHandler, IEndDragHandler
{
public GameObject cube;
public ChangeAvator change;
public float spacing; //空隙
int num; //数量
float c; //周长
float r; //半径
float ang;
List<GameObject> cubes = new List<GameObject>(); //存储所有预制体的集合
List<Transform> sorts = new List<Transform>();
float distance = 0; //距离
float nowtime = 0; //现在时间
float min = 0.5f; //最小值
float max = 1; //最大值
float cutspeed = 100; //减速度
string str = "";
int TouIndex = 10; //头的下标
int TuiIndex = 4; //腿的下表
int YiFuIndex = 3; //衣服的下标
// Start is called before the first frame update
void Start()
{
str = gameObject.name.Split('-')[1]; //获取是哪个部位
if (str == "Tou")
num = TouIndex;
else if (str == "Tui")
num = TuiIndex;
else if (str == "YiFu")
num = YiFuIndex;
c = (cube.GetComponent<RectTransform>().rect.width + spacing) * num; //周长
r = c / (Mathf.PI * 2); //半径
ang = Mathf.PI * 2 / num; //每份的弧度
Move();
}
/// <summary>
/// 移动的方法
/// </summary>
private void Move()
{
float moveang = distance / r;
for (int i = 0; i < num; i++)
{
float x = Mathf.Sin(i * ang + moveang) * r;
float z = Mathf.Cos(i * ang + moveang) * r;
if (i >= cubes.Count)
{
cubes.Add(Instantiate(cube, transform));
cubes[i].GetComponent<Image>().sprite = Resources.Load<Sprite>(str + "/" + i);
sorts.Add(cubes[i].transform);
}
float p = (z + r) / (2 * r);
float scale = min * (1 - p) + max * p;
cubes[i].transform.localPosition = new Vector3(x, 0, -z); //赋位置
cubes[i].transform.localScale = Vector3.one * scale; //赋大小
}
sorts.Sort((a, b) =>
{
if (a.localScale.x > b.localScale.x)
{
return 1;
}
else if (a.localScale.x == b.localScale.x)
{
return 0;
}
else
{
return -1;
}
});
for (int i = 0; i < sorts.Count; i++)
{
sorts[i].SetSiblingIndex(i);
}
}
//指定对齐
public void Alignment(int n)
{
//当前位置
int now = cubes.IndexOf(sorts[num - 1].gameObject);
//计算距离
int a = n - now;
int b = num - Mathf.Abs(a);
b = a < 0 ? b : -b;
int c = Mathf.Abs(a) < Mathf.Abs(b) ? a : b;
//弧度计算
float moveang = c * ang + Mathf.Asin(sorts[num - 1].localPosition.x / r);
//距离计算
float moveDiStance = moveang * r;
//时间计算
float time = Mathf.Abs(moveDiStance) / cutspeed;
//DOTween.To((float a) =>
//{
// distance = a;
// Move();
//}, distance, distance - moveDiStance, time).OnComplete(() =>
//{
// sorts[num - 1].GetComponent<Select>().OnSelected();
//});
}
float inertiaStartTime = 0;
float inertiaTime = 0;
float startspeed = 0;
bool isInertia = false;
/// <summary>
/// 拖拽中
/// </summary>
/// <param name="eventData"></param>
public void OnDrag(PointerEventData eventData)
{
distance += eventData.delta.x;
Move();
}
/// <summary>
/// 结束拖拽
/// </summary>
/// <param name="eventData"></param>
public void OnEndDrag(PointerEventData eventData)
{
inertiaStartTime = nowtime;
startspeed = eventData.delta.x;
isInertia = true;
inertiaTime = startspeed / cutspeed;
}
float alignmentStartTime = 0;
float alignmentTime = 0;
float nowdistance = 0;
float alignmentDistance = 0;
bool isAlignment = false;
// Update is called once per frame
void Update()
{
nowtime += Time.deltaTime;
if (inertiaStartTime + inertiaTime > nowtime)
{
float t = (nowtime - inertiaStartTime) / inertiaTime;
distance += startspeed * (1 - t);
Move();
}
else if (isInertia)
{
startspeed = 0;
isInertia = false;
alignmentStartTime = nowtime;
isAlignment = true;
nowdistance = distance;
alignmentDistance = Mathf.Asin(sorts[num - 1].localPosition.x / r) * r;
alignmentTime = alignmentDistance / cutspeed;
}
if (alignmentStartTime + alignmentTime > nowtime)
{
float t = (nowtime - alignmentStartTime) / alignmentTime;
distance = nowdistance * (1 - t) + (nowdistance - alignmentDistance) * t;
Move();
}
else if (isAlignment)
{
isAlignment = false;
distance = nowdistance - alignmentDistance;
Move();
if (str == "Tou")
{
change.objects[0] = Resources.Load<GameObject>("Prefabs/" + str + "_" + sorts[num - 1].GetComponent<Image>().sprite.name);
change.Change();
}
else if (str == "Tui")
{
change.objects[1] = Resources.Load<GameObject>("Prefabs/" + str + "_" + sorts[num - 1].GetComponent<Image>().sprite.name);
change.Change();
}
else if (str == "YiFu")
{
change.objects[2] = Resources.Load<GameObject>("Prefabs/" + str + "_" + sorts[num - 1].GetComponent<Image>().sprite.name);
change.Change();
}
}
}
}
在轮转图这里我们要确定脚本在这个物体上代表是哪个部位,是头,衣服还是腿,确定部位我们是通过脚本所挂载物体的名字,然后进行切割得到的。确定是哪个部位之后,后面的思路就好些多了。