【Unity基础练习 构造分形(编程控制)】

今天的教程来源于下方链接(它讲的更详细一点,我更多的只是总结)
构造分形(递归实现的细节)

今天这个练习,只需要自己创建一个空物体,一个材质,一个C#脚本即可运行。全部统一命名为Fractal。
【Unity基础练习 构造分形(编程控制)】

以下是C#脚本,所有需要注意的地方我都用注释标识好了:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Fractal : MonoBehaviour
{
    public Mesh[] meshes;//随机使用mesh
    public Material material;
    public float childScale;//分形子类缩放大小
    public int maxDepth = 0;//设定递归最大深度,不然不断迭代至栈溢出

    public float spawnProbability;//分形创建概率,为了使分形不规则

    public float maxRotationSpeed;//旋转变量

    public float maxTwist;//附加一个扭曲使分形元素的排列更加随机
    private float rotationSpeed;//旋转变量
    private int depth = 0; //当前深度
    
    private static Vector3[] childDirections={//将方向变为静态数组以简洁化代码
        Vector3.up,
        Vector3.right,
        Vector3.left,
        Vector3.forward,
        Vector3.back
    };
    private static Quaternion[] childOrientations = {//将旋转变为静态数组以简化代码
        Quaternion.identity,
        Quaternion.Euler(0f,0f,-90f),
        Quaternion.Euler(0f,0f,90f),
        Quaternion.Euler(90f,0f,0f),
        Quaternion.Euler(-90f,0f,0f)
    };

    private Material[,] materials;//我们将显示制作动态批处理提升性能,使用二级颜色级数

    private void InitializeMaterials(){
        materials = new Material[maxDepth + 1,2];
        for(int i=0;i<=maxDepth;i++){//预先将材质全部制作好,这样就不必在分形复制的时候再创建
            float t = i/(maxDepth-1f);
            t *=t;
            materials[i,0] = new Material(material);
            materials[i,0].color = Color.Lerp(Color.white,Color.yellow,t);//让颜色线性变化
            materials[i,1] = new Material(material);
            materials[i,1].color = Color.Lerp(Color.white,Color.cyan,t);
        }
        materials[maxDepth,0].color = Color.magenta;//将最大深度的分形赋予洋红色
        materials[maxDepth,1].color = Color.red;
    }

    private void Start() {
        rotationSpeed = Random.Range(-maxRotationSpeed,maxRotationSpeed);
        transform.Rotate(Random.Range(-maxTwist,maxTwist),0f,0f);
        if(materials==null){
            InitializeMaterials();//如果没有材质则显式创建
        }
        gameObject.AddComponent<MeshFilter>().mesh= 
            meshes[Random.Range(0,meshes.Length)];//直接分配网格和材料给他们
        gameObject.AddComponent<MeshRenderer>().material = materials[depth,Random.Range(0,2)];//接↑ 目的是运行时可以自动添加
        if(depth<maxDepth){
            StartCoroutine(CreateChildren());//启动协程
        }
    }
    private void Update() {//让我们的分形动起来
        transform.Rotate(0f,rotationSpeed*Time.deltaTime,0f);
    }

    private void Initialize(Fractal parent,int childIndex){
        meshes = parent.meshes;//子分形附上父类的mesh,只传递mesh数组的引用
        materials = parent.materials;//附上父元素的materal,只传递材料数组的引用
        maxDepth = parent.maxDepth;//最大深度继承
        depth = parent.depth + 1;//每递归创建一次就令最大深度加1
        childScale = parent.childScale;//分形子类大小继承
        spawnProbability = parent.spawnProbability;//继承分形生成概率
        maxRotationSpeed = parent.maxRotationSpeed;//传递旋转速度
        maxTwist = parent.maxTwist;//传递扭曲量
        transform.parent = parent.transform;//设置父节点
        transform.localScale = Vector3.one*childScale;//设置分形太小
        transform.localPosition = childDirections[childIndex]*(0.5f+0.5f*childScale);//设置位置
        transform.localRotation = childOrientations[childIndex];//旋转是为了更好的展示视图
    }

    private IEnumerator CreateChildren(){//定义CreateChildren协程
        // yield return new WaitForSeconds(0.5f);
        // new GameObject("Fractal Child").
        //     AddComponent<Fractal>().Initialize(this,Vector3.up);//创建分形子类,包括指定方向
        //     //这里是脚本调用自身
        // yield return new WaitForSeconds(0.5f);
        // new GameObject("Fractal Child").
        //     AddComponent<Fractal>().Initialize(this,Vector3.right);//创建分形子类,包括指定方向
        //     //这里是脚本调用自身
        // yield return new WaitForSeconds(0.5f);
        // new GameObject("Fractal Child").
        //     AddComponent<Fractal>().Initialize(this,Vector3.left);//创建分形子类,包括指定方向
        //     //这里是脚本调用自身
        // 以上是简化之前的代码

        //以下是简化之后的代码
        for(int i=0;i<childDirections.Length;i++){
            if(Random.value<spawnProbability){//控制分形生成概率
            yield return new WaitForSeconds(Random.Range(0.1f,0.5f));//随机时间增长
            new GameObject("Fractal Child").AddComponent<Fractal>().
                Initialize(this,i);
            }
        }
    }
}

代码并不长,甚至可以说是代码本身是十分容易理解的。不过这次的练习让我学习到蛮多东西的。

1.首先是怎么考虑构造出最终分形的效果。是从复制物体->复制材质->添加随机数->添加随机mesh->添加扭曲。通过这样一个过程一步步构造,完成了最终的分形制作。

2.关于协程。上次在制作贪吃蛇的时候也使用到了协程,这里先大概介绍一下协程,之后我会单独写一篇博客来总结和说明其用法。

协同程序,是在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行。
这里的WaitForSeconds是Unity提供的时间延迟类。

//原本写法
  for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){
    yield return 0;//挂起,下一帧再来从这个位置继续执行。
  }
  //使用WaitForSeconds的写法
  yield return new WaitForSeconds(3.0f);

3.动态批处理
由于这个功能貌似已经在我当前Unity版本(2018+)淘汰了,所以这次的练习我们是显式去模仿动态批处理的方法的。关于动态批处理的作用,我个人理解是这样:
在这次的练习中,我们每一次递归创建分形的时候都会创建一次材质。而我们先显式的将所有材质创建好,组成一个静态数组,这样就可以省去每一次创建分形的材质创建,可以提升性能。
(动态批处理显式创建材质的代码已经在上方给出)

4.关于静态数组的使用
这次练习包括上次练习,还学到的一个十分重要的东西就是静态数组的使用。让静态数组存储我们程序中经常使用到的东西,然后使用循环索引去获取到这些存储起来的,资源/代码/属性。其实那天的函数雕刻,函数使用枚举存放,并且使用静态数组+委托调用的方式,也更像是使用静态数组的进阶用法。所以,如果要考虑怎么简化代码,让它更简洁,静态数组是个十分不错的方式。

上一篇:常用光学元件专用术语中英文对照


下一篇:springboot-静态资源处理