又两个星期没写文章了,主要是沉迷 Screeps 这个游戏,真的是太好玩了导致我这两个礼拜 Github 小绿点几乎天天刷。其实想开一个新坑大概把自己写 AI 的心路历程记录下,不过觉得因为要消耗太多时间暂时决定先不开,准备把过程中遇到的有趣的算法问题记录下就好了。言归正传今天来到「构建分形」 这篇文章。比较简单主要介绍递归的思想。我们就迅速一些,因为我还要继续沉迷 Screeps,因为还要继续学习嗯。。。再贴一次「原文链接」吧。。

PART 1 概述

分形」这种东西,随便了解一下大概就能想到作者要用递归的方法来完成。所以这一篇教程性质的文章主要是讲在 Unity 里使用递归完成一些事情。鉴于大家应该上大学的时候随随便便上个课就差不多了解递归这种基本概念,因此我们就进展快一些~大概要完成以下事情:

  • 使用递归生成一大堆立方体和球体
  • 整理一下使其变成分形
  • 美化一下

PART 2 递归生成

首先我们要在 MonoBehaviour 里面生成一个立方体,需要如下代码。

public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material; // Use this for initialization
void Start ()
{
gameObject.AddComponent<MeshFilter>().mesh = Mesh;
gameObject.AddComponent<MeshRenderer>().material = Material;
}
}

非常简单,然后在场景里新建一个 GameObject 再挂上这个脚本,拖一些默认的 mesh 和 material 上去就好了,运行发现 OK 完美成功。那么说好的递归呢?非常简单,我们只需要在Start()里面创建一个新的 GameObject 再给他挂上这个 MonoBehaviour 就好。当然要记得限制递归的次数不然要爆炸~每次递归都记得调整子物体的位置和大小,最后设置一下递归深度这样就 OK 了,代码如下

public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material; public int Depth; // Use this for initialization
void Start ()
{
gameObject.AddComponent<MeshFilter>().mesh = Mesh;
gameObject.AddComponent<MeshRenderer>().material = Material;
if (Depth > 0)
{
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this);
} } public void Initialize(Fractal parent)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
transform.SetParent(parent.transform);
}
}

那么这样就可以生成一大堆叠在一起的立方体了。。接下来的目标就是对这段代码修修补补让这些立方体组成看起来像是分形的样子。

PART 3 分形

首先我们尝试让每个立方体在除了底面的每一面生成一个比他小一半的立方体。首先需要让Initialize()接收位置和方向以及大小的参数。

public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
Size = size;
transform.SetParent(parent.transform);
transform.localPosition = pos;
transform.localEulerAngles = rot;
transform.localScale = Vector3.one * size;
}

非常简单,然后在每个立方体执行Start()的时候初始化 5 个小立方体,之所以我们需要设置小立方体的朝向是为了小立方体朝着其 Z 轴方向 (0, 0, 1) 生长,而不用考虑每次递归的时候的生长方向。代码如下

private void Start ()
{
gameObject.AddComponent<MeshFilter>().mesh = Mesh;
gameObject.AddComponent<MeshRenderer>().material = Material;
if (Depth <= 0) return;
var posOffset = Size + Size / 2f;
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.left * posOffset, new Vector3(0, -90, 0));
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.right * posOffset, new Vector3(0, 90, 0));
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.up * posOffset, new Vector3(-90, 0, 0));
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.down * posOffset, new Vector3(90, 0, 0));
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.forward * posOffset, new Vector3(0, 0, 0));
}

最后在场景里设置下Scale = (0.5, 0.5, 0.5)size = 0.5f Depth = 4,再设置初始物体 Z 向上,即(-90, 0, 0)运行一下效果如图所示还不错~

嗯感觉还不错~再多设置一下变成 6 呢?我的 Macbook Pro 风扇开始呼呼的转。。。

接下来稍微重构下代码~之前的太丑了。我们把五行长得差不多的创建子物体的代码提取一下关键参数,完整版如下:

public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material; public int Depth;
public float Size; private readonly Vector3[] _positions = {Vector3.left, Vector3.right, Vector3.up, Vector3.down, Vector3.forward};
private readonly Vector3[] _rotations = {Vector3.down, Vector3.up, Vector3.left, Vector3.right, Vector3.zero}; // Use this for initialization
private void Start ()
{
gameObject.AddComponent<MeshFilter>().mesh = Mesh;
gameObject.AddComponent<MeshRenderer>().material = Material;
if (Depth <= 0) return;
var posOffset = Size + Size / 2f;
for (int i = 0; i < 5; i++)
{
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, _positions[i] * posOffset, _rotations[i] * 90);
}
} public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
Size = size;
transform.SetParent(parent.transform);
transform.localPosition = pos;
transform.localEulerAngles = rot;
transform.localScale = Vector3.one * size;
}
}

PART 4 美化

感觉作者写的美化一点都不美~不过我们还是按照教程顺手做一些换个 Mesh 啊随机旋转啦,生成机率之类的事情吧也算是有个交代。

随机 Mesh

这个非常简单了我们把 Mesh 这个字段扩充成一个数组。然后在初始化MeshFilter的地方从里面随机一个出来,像下面这样。然后在拖一些 Mesh 进去。

public class Fractal : MonoBehaviour
{
public Mesh[] Mesh;
public Material Material;
...
private void Start ()
{
gameObject.AddComponent<MeshFilter>().mesh = Mesh[Random.Range(0, Mesh.Length)];
gameObject.AddComponent<MeshRenderer>().material = Material;
...
}
...
}

这样就可以了~运行起来每次都不太一样。。图就不截了变化并不大大家应该可以想象出来~

生成机率

也很简单,添加一个机率然后在生成的地方每次随机一下,随到了就生成。。

public class Fractal : MonoBehaviour
{
...
public float Probability;
...
private void Start ()
{
...
for (int i = 0; i < 5; i++)
{
if (Random.Range(0, 1f) <= Probability)
{
new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, _positions[i] * posOffset, _rotations[i] * 90);
}
}
} public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
...
Probability = parent.Probability;
...
}
}

把机率调成 0.75 以后生成效果如下图(跟上一条随机 Mesh 一起展示了)

旋转起来吧

接下来要做的就是让这些东西全部动起来。。。并且以随机的速度。。嗯我已经可以想像出来大概是怎样的鬼畜场景了,尝试实现一下的话首先就是加一个最大旋转速度。然后在Update()里面随机好速度然后做一次旋转就好了~

public class Fractal : MonoBehaviour
{
...
public float MaxRotateSpeed;
... private void Start ()
{
...
} private void Update()
{
var rotationSpeed = Random.Range(-MaxRotateSpeed, MaxRotateSpeed);
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
} ...
}

运行一下发现似乎总是在原地抖动的样子。。。一定是我们速度变换的频率太高了所以最终结果会趋近于原地不动,稍微限制一下加点随机。。

public class Fractal : MonoBehaviour
{
...
public float MaxRotateSpeed;
public float RotateSpeedChangeRate;
private float RotateSpeed;
... private void Update()
{
Random.InitState(Depth * (int)Mathf.Ceil(Time.time * 100));
if (Random.Range(0, 1f) <= RotateSpeedChangeRate)
{
RotateSpeed = Random.Range(-MaxRotateSpeed, MaxRotateSpeed);
}
transform.Rotate(0f, 0f, RotateSpeed * Time.deltaTime);
} public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
...
MaxRotateSpeed = parent.MaxRotateSpeed;
RotateSpeedChangeRate = parent.RotateSpeedChangeRate;
...
}
}

哇画面真的是太诡异了。。。

PART 5 总结

好的这一篇文章就这样成功的 水过去了 完成了~这一篇大概上就是递归的使用方法吧~其实没怎么看原文自己摸索的时候还是要稍微花几分钟的,不过还是非常简单啊大家随便看看应该就可以了解的很透彻了~感兴的同学的欢迎 follow 我的「Github」下载「项目工程」准备继续去玩 Screeps 喽~


原文链接:https://snatix.com/2018/07/07/022-constructing-a-fractal/

本文由 sNatic 发布于『大喵的新窝』 转载请保留本申明

Catlike学习笔记(1.4)-使用Unity构建分形的更多相关文章

  1. Catlike学习笔记(1.3)-使用Unity画更复杂的3D函数图像

    第三篇来了-今天去参加了 Unite 2018 Berlin,感觉就是....非常困...回来以后稍微睡了下清醒了觉得是时候认真学习下了,不过讲的很多东西都是还没有发布或者只有 Preview 的版本 ...

  2. Catlike学习笔记(1.1)-使用Unity实现一个钟表

    最近发现『Catlike系列教程』觉得内容真的很赞,感觉有很多地方涉及到了我的知识盲点,如果真的可以照着做下来一遍的话应该收获颇丰.因为教程很长所以逐字翻译不太可能了(主要是翻译的太差).基本上就是把 ...

  3. Catlike学习笔记(1.2)-使用Unity画函数图像

    『Catlike系列教程』第二篇来了~今天周六,早上(上午11点)醒来去超市买了一周的零食回来以后就玩了一整天游戏非常有负罪感.现在晚上九点天还亮着感觉像下午7点左右的样子好像还不是很晚...所以就写 ...

  4. SpringCloud学习笔记(6):使用Zuul构建服务网关

    简介 Zuul是Netflix提供的一个开源的API网关服务器,SpringCloud对Zuul进行了整合和增强.服务网关Zuul聚合了所有微服务接口,并统一对外暴露,外部客户端只需与服务网关交互即可 ...

  5. Java学习笔记之使用反射+泛型构建通用DAO

    PS:最近简单的学了学后台Servlet+JSP.也就只能学到这里了.没那么多精力去学SSH了,毕竟Android还有很多东西都没学完.. 学习内容: 1.如何使用反射+泛型构建通用DAO. 1.使用 ...

  6. 《Spring实战》学习笔记-第五章:构建Spring web应用

    之前一直在看<Spring实战>第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读<Spring实战>第四版了,章节安排与之前不同了,里面应用的应该是最新的技术. ...

  7. 学习笔记:Vue+Node+Mongodb 构建简单商城系统(二)

    前面几个月工作有点忙,导致构建简单商城系统的计划搁置近三个月.现在终于有时间重新回过头来继续本计划.本篇主要记录自己在阿里云服务器上搭建node运行环境的整个过程,以及对其中遇到的一些问题的思考. 一 ...

  8. 学习笔记:首次进行JUnit+Ant构建自动的单元测试(二)

    关键字:JUnit,Ant,单元测试 终于把JUnit+Ant构建单元测试的大概了解了,其实我实践的过程是对了,只是指导博客(看到这里不懂请看我上一篇博客)本身的错误“成功”把我带入“坑”,有时候网友 ...

  9. 学习笔记:首次进行JUnit+Ant构建自动的单元测试(一)

    指导博客:https://blog.csdn.net/Cceking/article/details/51692010 基于软件测试的需求,使用JUnit+Ant构建自动的单元测试. IDE:ecli ...

随机推荐

  1. SSH批量分发管理

    ssh服务认证类型主要有两个: 基于口令的安全验证: 基于口令的安全验证的方式就是大家一直在用的,只要知道服务器的ssh连接账户.口令.IP及开发的端口,默认22,就可以通过ssh客户端登陆到这台远程 ...

  2. November 14th, 2017 Week 46th Tuesday

    Eternity is said not to be an extension of time but an absence of time. 永恒不是时间的无限延伸,而是没有时间. What is ...

  3. JAVA引用的种类

    最近在进行Java项目开发的时候,由于业务的原因,有时候new的对象会比较多,这个时候我总是有一个疑惑?那就是JVM在何时决定回收一个Java对象所占据的内存?这个问题其实对整个web系统来说是一个比 ...

  4. runloop是iOS系统上的actor模式

    runloop是iOS系统上的actor模式(单线程派发的)

  5. Grafana3.0.1+Zabbix3.0.4监控系统平台搭建

    前言 本文的Zabbix部分知识只介绍它的基础安装,Zabbix的使用以及配置优化并不在本文的介绍范围之内. 本文只介绍在CentOS6系列下的安装和部署,其他发行版与其他版本号暂不涉及 本文默认使用 ...

  6. Git系列七之备份迁移 升级 恢复管理

    0.Gitlab安装 1.安装和配置必要的依赖关系在CentOS7,下面的命令将在系统防火墙打开HTTP和SSH访问. yum install curl openssh-server postfix ...

  7. 【转载】python中利用smtplib发送邮件的3中方式 普通/ssl/tls

    #!/usr/bin/python # coding:utf- import smtplib from email.MIMEText import MIMEText from email.Utils ...

  8. 常用lua代码块

    1.读取请求体中参数 local request_method = ngx.var.request_method local args --获取参数的值 if "GET" == r ...

  9. [转]改善C#程序的建议4:C#中标准Dispose模式的实现

    需要明确一下C#程序(或者说.NET)中的资源.简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类: 托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象: 非托管资源:不 ...

  10. win10家庭版安装DockerToolbox-18.03.0-ce

    下载DockerToolbox-18.03.0-ce.exe https://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/ 点击安 ...