【Unity基础】catlike coding base篇之二(制作函数库,3D动画图形制作)


前言

学习catlike coding教程的base篇第二章个人笔记。


一、函数库的制作

1.已有脚本展示

Graph类,其实就是一个创建出游戏对象的脚本,把这些游戏对象缓存下来,并且使用该脚本控制游戏物体空间坐标和缩放。

使用步骤就是在游戏创建中创建一个新的空物体,并且把Graph脚本挂载到空物体上面。
在这里插入图片描述
代码演示:

public class Graph : MonoBehaviour
{
    
    
    [SerializeField] public Transform pointPrefabs;
    [SerializeField] public int createPointNum = 100;  //创建点的数量
    [SerializeField] public float pointDist = 0.1f;   //点和点之间的距离 
    [SerializeField] public FunctionLibrary.FuncName f = FunctionLibrary.FuncName.Wave;  
    private Transform[] _transforms;
    private Transform _trans; 
    
    void Start()
    {
    
    
        _trans = transform; 
       //统一修改预制体大小
       pointPrefabs.localScale = new Vector3(0.05f,0.05f,0.05f);
       _transforms = new Transform[createPointNum];
       for (int i = 0;i<createPointNum;i++)
       {
    
    
           Transform newTrans = Instantiate(pointPrefabs);
           newTrans.gameObject.name = $"point{i}";
           newTrans.SetParent(_trans); 
           _transforms[i] = newTrans;
       }
    } 
   
    //让图像动起来
    void Update()
    {
    
    
        Tick();
    }

    
    [HideInInspector] private Vector3 _cache = Vector3.zero; 
    void Tick()
    {
    
     
        float time = Time.time;
        for (int i = 0;i < _transforms.Length;i++)
        {
    
    
            float x = i * pointDist;
            _cache.x = x; 
            _cache.y = Mathf.Sin(x*2 + time);
            _transforms[i].localPosition = _cache;
        }
    } 
}

这里的效果就是简单的绘制一个挥动的Sin振幅图像,下面是gif演示:
在这里插入图片描述
由于子节点使用了局部坐标,所以可以自由的修改父节点的空间坐标,旋转,缩放。
在这里插入图片描述

2.使用枚举和委托制作函数库

上面的这段代码

 void Tick()
    {
    
     
        float time = Time.time;
        for (int i = 0;i < _transforms.Length;i++)
        {
    
    
            float x = i * pointDist;
            _cache.x = x; 
            _cache.y = Mathf.Sin(x*2 + time);
            _transforms[i].localPosition = _cache;
        }
    } 

这一句 _cache.y = Mathf.Sin(x2 + time);。这是一句根据物体的x坐标来获得物体的y坐标的代码。我们可以稍微封装一下这个函数,申明一个新的类型FunctionLibrary,其中把Mathf.Sin(x2+time)加进去,这里使用静态修饰而无需创建FunctionLibrary对象,就有以下代码:

public static class FunctionLibrary
{
    
    
    public static float DrawSin(float x,float t)
    {
    
    
        return Mathf.Sin(x * 2 + t);
    }
}

并且这里改成使用一个委托去代替Math.Sin(x*2 + time),而这个委托只需要坐标的x属性和时间变化值这两个变量。
FunctionLibarary改成

public static class FunctionLibrary
{
    
    
    public delegate float Function(float x,float t);
    public static float DrawSin(float x,float t)
    {
    
    
        return Mathf.Sin(x * 2 + t);
    }
}

这样Graph的Tick函数就可以改成:

 void Tick()
    {
    
    
        FunctionLibrary.Function f = FunctionLibrary.DrawSin;
        float time = Time.time;
        for (int i = 0;i < _transforms.Length;i++)
        {
    
    
            float x = i * pointDist;
            _cache.x = x; 
            _cache.y = f(x,time);
            _transforms[i].localPosition = _cache;
        }
    } 

再写入一个委托的数组和一个枚举的声明,最好就改成这样:

public static class FunctionLibrary
{
    
    
    public delegate float Function(float x,float t);
    public static Function[] functions = {
    
    DrawSin};
    public enum FuncName {
    
     DrawSin }
    public static float DrawSin(float x,float t)
    {
    
    
        return Mathf.Sin(x * 2 + t);
    }
    public static Function GetFunc(int index)
    {
    
    
        return functions[index];
    }
}

这里主要是使用funtions数组,FuncName枚举,GetFunction函数来索引到对应的函数。再对FunctionLibrary引入多个函数就有:

public static class FunctionLibrary
{
    
    
    public delegate float Function(float x,float t);
    public static Function[] functions = {
    
    DrawSin,Wave,MultiWave,Ripple,RippleAnim};
    public enum FuncName {
    
    DrawSin,Wave,MultiWave,Ripple,RippleAnim } 
    public static float DrawSin(float x,float t)
    {
    
    
        return Mathf.Sin(x * 2 + t);
    }
    public static Function GetFunc(int index)
    {
    
    
        return functions[index];
    } 
   
    public static float Wave(float x, float t)
    {
    
    
        float y = Sin(PI * (x + t));
        y += 0.5f * Sin(2f * PI * (x + t));
        return y / 1.5f;
    }

    public static float MultiWave(float x, float t)
    {
    
    
        float y = Sin(PI * (x + 0.5f * t));
        y += 0.5f * Sin(2f * PI * (x + t));
        return y / 1.5f;
    }

    public static float Ripple(float x, float t)
    {
    
    
        float d = Abs(x);
        return Sin(PI * 4.0f * d);
    }

    public static float RippleAnim(float x, float t)
    {
    
    
        float d = Abs(x);
        float y = Sin(PI * (4.0f*d - t));
        return y / (1.0f + 10.0f * d);
    } 
}

最后Graph应该做出的修改:

    [SerializeField] public FunctionLibrary.FuncName funcIndex = FunctionLibrary.FuncName.DrawSin;
    [HideInInspector] private Vector3 _cache = Vector3.zero; 
    void Tick()
    {
    
    
        FunctionLibrary.Function f = FunctionLibrary.GetFunc((int)funcIndex);
        float time = Time.time;
        for (int i = 0;i < _transforms.Length;i++)
        {
    
    
            float x = i * pointDist;
            _cache.x = x; 
            _cache.y = f(x,time);
            _transforms[i].localPosition = _cache;
        }
    } 

这样就简单的完成了一个函数库,并且可以在Inspector面板上自由选择函数。
在这里插入图片描述

3.这里稍微放一下,下面会使用到的一个简单的shader

shader "Custom/PointShader"
{
    
    
    
    SubShader{
    
    
        Tags {
    
     "RenderType" = "Opaque"}
        LOD 200
        
        Pass{
    
    
            CGPROGRAM
            #pragma target 3.0 
            #pragma vertex vert        
            #pragma fragment frag     
            
            struct adp_data
            {
    
    
                float4 vert:POSITION;
            };

            struct v2f
            {
    
    
                float4 vert:SV_POSITION;
                fixed4 color:TEXCOORD0;
            };

            v2f vert(adp_data i)
            {
    
    
                v2f o;
                o.vert = UnityObjectToClipPos(i.vert);
                o.color = mul(unity_ObjectToWorld,i.vert);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
    
    
                fixed4 outColor = i.color;
                outColor = saturate(outColor + (0.5,0.5,0.5));
                return outColor;
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

二、曲面图像绘制

1.简单实现一个带有对角波动的图像

上面的图像是仅仅使用了x轴和y轴。下面把z轴也用上来。
首先定义一个值,用于每次计算完多少个数量的代表点的物体时,把用于计算物体x坐标的变量进行一次重置,并且迭代一次计算z变量的数值。而这里我每次计算x和z开始的时候都是选择使用对称的坐标,所以就有一下代码更新出来:

public class Graph : MonoBehaviour
{
    
    
    [SerializeField] public Transform pointPrefabs;
    [SerializeField] public int createPointNum = 100;  //创建点的数量
    [SerializeField] public float pointDist = 0.1f;    //点和点之间x轴的距离 
    [SerializeField] public float zPointDist = 0.05f;  //点和点之间z轴的距离
    [SerializeField] public int max = 100;   //新增变量
    [SerializeField] public float scale = 1.0f;  //主要控制代表点的缩放
    private Transform[] _transforms;
    private Transform _trans; 
    
    void Start()
    {
    
    
        _trans = transform; 
       //统一修改预制体大小
       pointPrefabs.localScale = new Vector3(scale,scale,scale);
       _transforms = new Transform[createPointNum];
       for (int i = 0;i<createPointNum;i++)
       {
    
    
           Transform newTrans = Instantiate(pointPrefabs);
           newTrans.gameObject.name = $"point{i}";
           newTrans.SetParent(_trans); 
           _transforms[i] = newTrans;
       }
    } 
   
    //让图像动起来
    void Update()
    {
    
    
        Tick();
    }

    [SerializeField] public FunctionLibrary.FuncName funcIndex = FunctionLibrary.FuncName.DrawSin;
    [HideInInspector] private Vector3 _cache = Vector3.zero; 
    void Tick()
    {
    
    
        FunctionLibrary.Function f = FunctionLibrary.GetFunc((int)funcIndex);
        float time = Time.time;
        int index = GetStartX();                      
        int zIndex = - _transforms.Length / max / 4;   //计算对称的z开始的值
        for (int i = 0;i < _transforms.Length;i++)
        {
    
    
            float x = index * pointDist;
            float z = zIndex * zPointDist;
            _cache.x = x;
            _cache.y = f(x,time);
            _cache.z = z;
            _transforms[i].localPosition = _cache;
            index++;
            if (index >= max){
    
    
                index = GetStartX();
                zIndex++;
            }
        }
    }
    
    int GetStartX()
    {
    
    
        //计算对称x开始的值
        return -_transforms.Length / 2 <= -max ? -max : -_transforms.Length / 2;
    }
}

修改好上面的代码之后就有一下的效果:
在这里插入图片描述
而这里,我们知道根据上面那张gif图,这个图像里面的坐标轴箭头为z轴,指向为正的方向,所以该图像由右边往左边z逐渐加大,当点足够多的时候,我们可以认为z的值也是线性的在修改。当我们往普通的振幅函数DrawSin里面引用z变量时就会有以下效果:
在这里插入图片描述
可想象出来吧,z值带来的x值的变化刚刚好可以引起这种对角线的变化。修改代码如下:

public static float DrawSin(float x,float z,float t)
    {
    
    
        return Mathf.Sin( PI * (x + z + t))/2;
    }

这样就简单实现了一个带有对角线波动的3D图像。

2.实现一个混合对角波动的图像

当然也可以给z的方向也做一个波动,就有以下,修改以下MultiWave函数:

public static float MultiWave(float x,float z, float t)
    {
    
    
        float y = Sin(PI * (x + 0.5f * t));
        y += 0.5f * Sin(2f * PI * (z + t));
        y += Sin(PI * (x + z + 0.25f * t));
        return y*(1f/2.5f);
    }

上面的代码相当于是给z的方向做了两个振幅:
振幅1, y += 0.5f * Sin(2f * PI * (z + t));。里面的2f乘以PI是加快振幅的频率,外面乘以0.5是把这个振幅的最小峰值和最大赋值降低一下。
振幅2,y += Sin(PI * (x + z + 0.25f * t));。这里是添加对角线振幅,切把时间调慢至0.25倍。
最后的 y / 2.5f,主要是因为上面的振幅加起来的赋值会变成原来的2.5倍。为了控制在1倍到-1倍。
最终效果如下:
在这里插入图片描述

3.实现一个按圆波动的图像

上面实现了一个比较复杂的混合波动的图像,当然我们也可以带入圆的计算公式。按照xx + zz来添加波动,并且越接近x = 0,z = 0的情况下,y的返回值就越大。这样可以实现一下水滴的效果,代码如下:

public static float Ripple(float x,float z, float t)
    {
    
    
        float d = Sqrt(x*x + z*z);
        float y = Sin(PI * (4f * d - t));
        return y / (1 + 10 * d);
    }

下面为最终效果:
在这里插入图片描述

三.3D封闭图像的实现

上面的基本上都是实现了曲面所以表达式f(x,z) = y基本山都可以实现出来,但是我们知道有一个局限性,那就是在3D空间里面拥有相同的y坐标的点是不能同时引用相同的x,z坐标的,所以得把表达式修改成一下:
f(u,v) = (x,y,z)。有一个uv的坐标系去得到一个坐标点。所以函数库得稍微修改一下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.Mathf;

public static class FunctionLibrary
{
    
    
    public delegate Vector3 Function(float x,float z,float t);
    public static Function[] functions = {
    
    DrawSin,Wave,MultiWave,Ripple};
    public enum FuncName {
    
    DrawSin,Wave,MultiWave,Ripple} 
    public static Vector3 DrawSin(float u,float v,float t)
    {
    
    
        Vector3 p;
        p.x = u;
        p.z = v;
        p.y = Mathf.Sin( PI * ( u + v + t))/2;
        return p;
    }
    public static Function GetFunc(int index)
    {
    
    
        return functions[index];
    } 
    
    public static Vector3 Wave(float u,float v, float t)
    {
    
    
        Vector3 p;
        p.x = u;
        p.z = v;
        float y = Sin(PI * (u + t));
        y += 0.5f * Sin(2f * PI * (v + t));
        p.y = y / 1.5f;
        return p;
    }

    public static Vector3 MultiWave(float u,float v, float t)
    {
    
    
        Vector3 p;
        p.x = u;
        p.z = v;
        float y = Sin(PI * (u + 0.5f * t));
        y += 0.5f * Sin(2f * PI * (v + t));
        y += Sin(PI * (u + v + 0.25f * t));
        p.y = y * (1f / 2.5f);
        return p;
    }

    public static Vector3 Ripple(float u,float v, float t)
    {
    
    
        Vector3 p;
        p.x = u;
        p.z = v;
        float d = Sqrt(u*u + v*v);
        float y = Sin(PI * (4f * d - t));
        p.y = y / (1 + 10 * d);
        return p;
    } 
}

上面的函数的返回值都修改成了Vector3。

1.创建3D复杂对象

我们可以使用这段代码来创建一个球出来

public static Vector3 Sphere(float u,float v, float t)
    {
    
    
        Vector3 p;
        float r = Cos(0.5f * PI * v);
        p.x = r * Sin(u  * PI);
        p.y = Sin(v * 0.5f * PI);
        p.z = r * Cos(u  * PI);
        return p;
    }

这段代码实际上就是靠p.x = Sin(u * PI);和p.z = Cos(u * PI);来给x - z平面创建一个圆。后面根据 v的值来给这个圆创建不同的半径所以就有了 float r = cos(0.5f * PI * v);这句代码。而y是根据Sin(v * 0.5f * PI);决定的。
其实这个只是看起来像球的胶囊。
最终效果如下:
在这里插入图片描述
这里使用创建更为复杂的公式来创建一个图形,代码如下:

 public static Vector3 Touch(float u,float v, float t)
    {
    
    
        float r1 = 0.7f + 0.1f * Sin(PI * (6f * u + 0.5f * t));
        float r2 = 0.15f + 0.05f * Sin(PI * (8f * u + 4f * v + 2f * t));
        float s = r1 + r2 * Cos(PI * v);
        Vector3 p;
        p.x = s * Sin(PI * u);
        p.y = r2 * Sin(PI * v);
        p.z = s * Cos(PI * u);
        return p;
    }

最终效果为:
在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/qq_41094072/article/details/131282403
今日推荐