角色换装的实现

在游戏中,换装效果主要在人物更换服装或者更换皮肤的时候用到。换装主要有三个要点:材质,网格,模型。我们所说的换装其实就是改变角色各个部位上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();
            }
        }
    } 
}

在轮转图这里我们要确定脚本在这个物体上代表是哪个部位,是头,衣服还是腿,确定部位我们是通过脚本所挂载物体的名字,然后进行切割得到的。确定是哪个部位之后,后面的思路就好些多了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值