2013-10-14 19:49:57 u011838628 阅读数 9180
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

      最近在看Unity3D的人物模型和动画。所以今天先说下人物的换装吧。相信大家都玩过网游吧,没有玩过的也相信见过,就是网游或者单机游戏里的人物会有更换服装,更换武器的功能。如果外形(mesh)是一样的,那么把贴图换下就好,但是如果外形不一样甚至骨骼都变了的话,就需要我们今天讨论的技术了。

  一.Unity3D里的3D模型

Unity3D里的基础模型是从3D工具中导出来的fbx文件。把fbx放在Assets目录下会自动生成一个Materials文件夹,里面会生成一个材质球。在Unity3D项目里的显示是这样

注意:Textrue需要另外添加进去。

         模型的主要文件就是那个蓝色的方块,前面两个(WGKS_wugang,WGKS_yaodai)这两个文件是模型的Mesh信息,一个是人物本身的,一个是腰带的,因为比较简单的模型,所以只有两个。下面的一坨是动作信息,动作是可以自己设置的,这个以后文章会讲。而紧接着的

“Bip001”这个是模型的骨骼信息,你拖到Scene里就能层层打开看到了。而WGKS_yaodai则是蒙皮信息的一些玩意。选择到它会在Inspector属性栏里看到SkinnedMeshRenderer组件,这里会看到它牵扯到的mesh,material,bone信息。

  

  二.换装方案1组合模型的显示和隐藏

  这个题目可能说的不合适,但是想不到更合适的了。我们的第一种换装的方法很简单,就是让模型或者Mesh进行显示和隐藏。就比如让人物穿两套衣服,显示第一套的时候隐藏第二套,显示第二套的时候显示第一套。

         例如这个模型:

         

这里有TBJ_SR_01和TBJ_SR_03两个SkinnedMeshRenderer.那么我们可以分别设置Active来设置是否可见。代码如下:

public GameObject clothA = null;
public GameObject clothB = null;

if (GUI.Button(new Rect(5, 5, 100, 50), "ClothA"))
{
    clothA.SetActive(true);
    clothB.SetActive(false);
}

if (GUI.Button(new Rect(5, 60, 100, 50), "ClothB"))
{
    clothA.SetActive(false);
    clothB.SetActive(true);
}


运行起来的效果如下:

ClothA

ClothB

      如果角色是换武器而不是换装备的话,原理是一样的,只不过我们要找到附属的武器GameObject然后设置让它显示和隐藏就可以了。

  三.换装方案2 替换Skin信息

      方案1的方法用起来比较简单,比较适合简单的模型替换,比如一个模型来来回回就两把武器的话,就可以用。但是如果一个模型有十来套衣服或者十几件装备的话,这么做会比较悲剧的,因为太占资源了,创建一个模型,牵扯的不用的东西也整进内存了,暴殄天物。

      所以我想能不能游戏运行的时候替换模型的Skin信息达到我们的目的。一开始我觉得,其实我们只需要把sharedmesh,material替换掉就行了。实验证明不行的。大家可以自己尝试下,Unity3D会报错,即使不报错模型也会变形的。模型中还有一块是骨骼没有动,所以我想尝试下如果把骨骼去掉能不能拿到mesh信息等,然后再替换。结果是从3DMAX里去掉骨骼导出的话,蒙皮信息也没有了。所以我确定了一点是,被换装的模型必须有骨架,而源Mesh信息中必须包含骨架信息,按照我的理解就是Mesh中保存着这个点依附的是哪个骨头,权重是多少等。

 

  这里我准备了两个模型:

            

带有完整的信息(骨头,动作,蒙皮信息等,只不过模型只有一个mesh而已)。

放在Scene的样子:

            

  我的目的是想让左边的那个模型读取右边模型的SkinnedMeshRenderer信息,然后赋值给自己。首先我把右边的模型除了骨架的所以资源赋值给一个prefab,然后加载prefab,读取信息给左边的模型。

  代码如下:

// 声明两个SkinnedMeshRenderer
public SkinnedMeshRenderer MyskinMeshRender = null;
public SkinnedMeshRenderer SrcSkinMeshRender = null;

//加载资源
Object Src = Resources.Load("Res/Materials2");
objSrc = Instantiate(Src) as GameObject;

//找到目标模型的SkinnedMeshRenderer
MyskinMeshRender = GameObject.Find("TBJ_SR_01").GetComponent<SkinnedMeshRenderer>();
//获取加载的SkinnedMeshRenderer
SrcSkinMeshRender = objSrc.GetComponent<SkinnedMeshRenderer>();
//获取目标模型的骨骼
Transform[] bonesMy = gameObject.GetComponentsInChildren<Transform>();
Debug.Log(bonesMy.Length);
//Transform[] bonesSrc = GameObject.Find("TBJ_SR_03").GetComponentsInChildren<Transform>();
//获得源SkinMesh中依附的骨骼信息
Transform[] bonesSrc = SrcSkinMeshRender.bones;
Debug.Log(bonesSrc.Length);

//根据源SkinMesh依附的骨骼信息重新排列目标骨骼
List<Transform> bones = new List<Transform>();
foreach (Transform boneS in bonesSrc)
{
    foreach (Transform boneM in bonesMy)
    {
        if (boneS != null && boneM != null)
        {
            if (boneS.name != boneM.name)
            {
                continue;
            }
            bones.Add(boneM);
        }
    }
}
MyskinMeshRender.bones = bones.ToArray();
MyskinMeshRender.sharedMesh = SrcSkinMeshRender.sharedMesh;
MyskinMeshRender.sharedMaterials =SrcSkinMeshRender.sharedMaterials;     


运行之后发现皮肤换了,而且动画信息没有丢失掉,也就是说还能动,但是有个奇葩的现象,人物是倒着的,如图:

  这个我分析后觉得是因为骨骼的问题。两个模型的骨骼有点不一样。因为我是读取了右边的模型的蒙皮信息对应的骨骼信息而重组了左边的骨骼,但是左边的动画信息还是按照原来的。虽然不是很清楚动画数据里面数据结构,但是我觉得就是记录的骨骼或者mesh或者当前模型的世界坐标的某个点对应的Mesh或者骨骼移动到哪个位置。

 

  接着我换了一个思路,用一个人物模型,一套骨骼,一套动作,不过蒙皮信息有两个,导出fbx。

如图:

 

  然后在3DMax里面导出一个没有蒙皮信息只有骨骼和动作的fbx,如图:

  然后添加脚本,代码如下: 

//声明两个SkinMeshRender 声明为public是为了方便观察 真正需要private
public SkinnedMeshRenderer MyskinMeshRender = null;
public SkinnedMeshRenderer SrcSkinMeshRender = null;

//添加SkinnedMeshRenderer组件
MyskinMeshRender = gameObject.AddComponent<SkinnedMeshRenderer>();
	
if (Input.GetKeyDown(KeyCode.Z))
{
    GameObject obj = GameObject.Find("TBJ_SR_01") as GameObject;
    SrcSkinMeshRender = GameObject.Find("TBJ_SR_01").GetComponent<SkinnedMeshRenderer>();

    Transform[] bonesMy = gameObject.GetComponentsInChildren<Transform>();
    Transform[] bonesSrc = SrcSkinMeshRender.bones;

    List<Transform> bones = new List<Transform>();
    foreach (Transform boneM in bonesSrc)
    {
        foreach (Transform boneS in bonesMy)
        {
            if (boneM != null && boneS != null)
            {
                if (boneM.name != boneS.name)
                {
                    continue;
                }
                bones.Add(boneS);
            }
        }
    }
    MyskinMeshRender.bones = bones.ToArray();
    MyskinMeshRender.sharedMesh = SrcSkinMeshRender.sharedMesh;
    MyskinMeshRender.sharedMaterials = SrcSkinMeshRender.sharedMaterials;
}


 

  运行,结果没有问题了。如图:

  

  按下Z之后:

  而且动作保留。

 

  总结:

  虽然这个例子不是很经典,也存在些许问题,希望大家指出问题,共同进步。在添加skinnedMeshRenderer组件的时候有个问题,如果模型选择Generic的时候,Unity3D会挂掉。

这个例子可以运用到骨骼和动作基本一样,但是外形不同的模型中,减少游戏中的资源重复占用。网上还有个更经典的例子,运用的是Character Customization 包,运用的原理是一样的,这里给大家看下运行结果:

  

      

  这个模型是由各个部分的mesh组成,而我的例子是用的一个mesh。在复杂的模型中,我们可以查找子组件的SkinnedMeshRenderer,然后进行操作就可以了。想换哪里就替换哪里的SkinnedMeshRenderer的信息,更新对应的骨骼列表和组合一下网格就行了。

List<CombineInstance> combineInstances = new List<CombineInstance>();
CombineInstance ci = new CombineInstance();
ci.mesh = SrcSkinMeshRender.sharedMesh;
combineInstances.Add(ci);


         还有一个想法是这样的,源资源中是否需要骨头。我发现重组骨头结构的时候其实只是读取了骨头的列表信息,所以我觉得可以写成一个配置信息,直接读取配置信息来重组。这个想法还没有得到验证,等有时间的吧。

 

 

2019-10-27 00:44:50 weixin_39820793 阅读数 109
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

游戏设计要求:

  • 创建一个地图和若干巡逻兵(使用动画);
  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
  • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
  • 失去玩家目标后,继续巡逻;
  • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
  • 程序设计要求:
    • 必须使用订阅与发布模式传消息
    • 工厂模式生产巡逻兵

实现的代码地址为:Github

视频的地址为:腾讯视频

首先是找到巡逻兵的资源,我是在Asset Store上找到的资源,大家自己找自己喜欢的就好了。

然后进行具体的代码实现,首先在实现好对各个物体的具体控制类之后,然后就是实现GameEventManager类,也是来进行订阅者模式的代码。

public class GameEventManager : MonoBehaviour
{
    public delegate void EscapeEvent(GameObject patrol);
    public static event EscapeEvent OnGoalLost;
    public delegate void FollowEvent(GameObject patrol);
    public static event FollowEvent OnFollowing;
    public delegate void GameOverEvent();
    public static event GameOverEvent GameOver;
    public delegate void WinEvent();
    public static event WinEvent Win;
    public void PlayerEscape(GameObject patrol) {
        if (OnGoalLost != null) {
            OnGoalLost(patrol);
        }
    }
    public void FollowPlayer(GameObject patrol) {
        if (OnFollowing != null) {
            OnFollowing(patrol);
        }
    }
    public void OnPlayerCatched() {
        if (GameOver != null) {
            GameOver();
        }
    }
    public void TimeIsUP() {
        if (Win != null) {
            Win();
        } 
    }
}


然后这次也有一些比较需要实现的内容,镜头跟随的内容,没有这个的话,游戏进行的体验不是非常的好。

public class CameraFollowAction : MonoBehaviour
{
    public GameObject player;            
    public float smothing = 5f;         
    Vector3 offset;                     

    void Start() {
        offset = new Vector3(0, 5, -5);
    }

    void FixedUpdate() {
        Vector3 target = player.transform.position + offset;
        transform.position = Vector3.Lerp(transform.position, target, smothing * Time.deltaTime);
    }
}
2019-02-18 18:29:50 qq_43667944 阅读数 131
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

               

在手游中换装成了越来越不可缺的一个功能,毫无疑问各式各样的时装为游戏增添了不同的色彩。

对于2D手游,或许是更换对应的序列帧,也或许是如同3D手游一般,更换模型动画。


对于游戏中的人物,一般分为头、上身、下身、武器四大部分就可以了。所以在游戏中,我们把这四部分单独做四个模型动画,然后动态拼接为一个整体,达到换装的效果。


然后根据动画制作人员的喜好 又有两种方式来拼接:

1、每个身体部位自带动画数据

2、每个身体部位不带动画数据,动画数据单独提取出来


我们这里介绍第二种。

对于我们这种每一个动作的单独一个动作数据的方式,要怎么拼合到一个动画数据。Unity官方文档有介绍 - 分割动画

http://game.ceeger.com/Manual/Splittinganimations.html




来看我们实际项目中的Project视图:


把每一个动作的动画数据都单独提取出来,然后汇总到warrior这一个文件中。


让我们来看看如何加载这些动画数据和各个部位到游戏中。


把四个部位的模型拖到Scene中,可以看到拼成了一个完整的人物


但是没有动作效果,因为这些只是模型骨头,但是没有动作数据,从右边的红框就看出来。

所以我们需要把动作数据加入到模型中。


点击右边的小圆点,在弹出的动画列表中选择动画,比如我选择的 run ,为四个部位的模型都选择run。再运行,这是就有了动作。我们的主角在跑动。


下面让我们通过代码来把动作和模型数据加载进来。


首先加载进来模型

using UnityEngine;using System.Collections;using System.Collections.Generic;public class LoadTest : MonoBehaviour { // Use this for initialization void Start () {        GameObject head = Instantiate(Resources.Load("anim/character/warrior/fashion/head_1")) as GameObject;        GameObject jack = Instantiate(Resources.Load("anim/character/warrior/fashion/jacket_1")) as GameObject;        GameObject pant = Instantiate(Resources.Load("anim/character/warrior/fashion/pants_1")) as GameObject;        GameObject weapon = Instantiate(Resources.Load("anim/character/warrior/fashion/warrior_10l")) as GameObject; }  // Update is called once per frame void Update () {  }}

然后加载进来动画数据:

using UnityEngine;using System.Collections;using System.Collections.Generic;public class LoadTest : MonoBehaviour { // Use this for initialization void Start () {        //加载模型;        GameObject head = Instantiate(Resources.Load("anim/character/warrior/fashion/head_1")) as GameObject;        GameObject jack = Instantiate(Resources.Load("anim/character/warrior/fashion/jacket_1")) as GameObject;        GameObject pant = Instantiate(Resources.Load("anim/character/warrior/fashion/pants_1")) as GameObject;        GameObject weapon = Instantiate(Resources.Load("anim/character/warrior/fashion/warrior_10l")) as GameObject;        //加载动作数据;        Animation mation = Resources.Load("anim/character/warrior/warrior", typeof(Animation)) as Animation;        //获取所有的动作;        List<string> animList = new List<string>();        foreach (AnimationState state in mation)        {            Debug.Log(state.name);            animList.Add(state.name);            //添加到四个部位;            head.animation.AddClip(mation.GetClip(state.name),state.name);            jack.animation.AddClip(mation.GetClip(state.name), state.name);            pant.animation.AddClip(mation.GetClip(state.name), state.name);            weapon.animation.AddClip(mation.GetClip(state.name), state.name);        }        head.animation.Play("run");        jack.animation.Play("run");        pant.animation.Play("run");        weapon.animation.Play("run"); }  // Update is called once per frame void Update () {  }}

并且播放run 这个动作。

来看看效果


我们的主角就这样跑起来了。


           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

没有更多推荐了,返回首页