撰写了文章 更新于 2020-12-27 23:28:30
【自学笔记】制作动森岛建规划器(一)
学习目的
最近想着下班在家没事边学习边做点东西玩玩,一来作为业余的娱乐(是的学编程就是比玩游戏好玩!)满足一下自己,二来顺便拓宽一下技能树范围。
起初想着做个小游戏,但是后来感觉这个目标略大了一些,比较容易半途而废。于是想着做些实用的功能玩,首先想到的是做个「换装模拟器」,但是要实现我所想的所有功能可能比做个游戏还复杂,于是也放弃了这个目标。
因为我玩了一段时间动森,我媳妇也一直在玩动森,我最大的苦恼就是动森不能简单便捷的规划自己的岛,而且也没有足够的家具来实现自己的规划。于是我觉得直接做个「动森岛建规划器」好像也不错。
这个目标涉及的功能都比较基础和简单,需要做的也不算多,算是比较容易完成的目标,并且成品也有一定的使用价值。
于是最终第一个小目标确定为:「动森岛建规划器」!
一些废话
目前来看,可视化编程是一个挺奇怪的发展方向,因为用节点的方式进行编程存在「可读性差」这么一个严重缺陷,越是复杂的程序这个缺陷就越严重。
在简单程序中,通过节点间的连线来清晰了解程序运行的逻辑顺序算是个小优点吧,但是一旦程序稍微复杂点,这个小优点也不复存在了。
不知道这个各方都在推的这个东西究竟能发展成什么样,能不能实现理想中的作用(如果有的话)。
开始
我是四川音乐学院的美术专业毕业,在2014~2015年在校期间接触并自学了一些虚幻4引擎的内容,掌握了一些Blueprint的基础,当时还做了一个平台跳跃和一个FPS的游戏DEMO。加上近两年来工作中的接触,略微了解了一点编程的原理。
我有一些可视化编程的经验基础,这两年的项目中也用可视化编辑器编写过不少shader,而我完全不会写代码。
基于以上条件,我直接使用可视化编程的方式进行编程会更加顺手,更容易得到成果。
因此在经过筛选之后,我选择使用已经被Unity收编的「Bolt」插件进行编程制作。
要做这个「规划器」,基本要实现的目标有这些:
①悬崖的建造/拆除;
②河流的建造/拆除;
③树木种植/拔除;
④桥梁的建造/拆除;
⑤各类建筑的建造/拆除/移动;
⑥家具的建造/拆除/移动/旋转;
⑦镜头的移动/缩放;
⑧类似游戏的镜头视角;
还有就是如果有时间有精力还能做的更进一步的进阶效果:
①视觉效果优化;
②昼夜变化;
③天气变化;
④季节变化;
基础射线检测
我需要实现的建造方式,就是在鼠标点击的位置生成模型,这是一种俯视角下比较常用的建造方式。要实现这个效果,我想到的就是用射线检测的方式来得到一个坐标,然后在这个坐标生成出需要的模型。
那么首先我需要先了解如何实现「射线检测」,以及如何获取到射线击中位置的坐标。
经过搜索,我找到了一段实现射线碰撞检测的代码:
(代码来源:https://blog.csdn.net/weixin_39549161/article/details/93380797)
using UnityEngine;
using System.Collections;
public class lasermaster : MonoBehaviour {
public Vector3 originPos;//射线起始位置
public Vector3 direction;//射线方向
public float maxDistance;//射线最大检测距离
// Use this for initialization
void Start () {
maxDistance = 10.0f;
}
// Update is called once per frame
void Update () {
originPos = transform.position + transform.up * 2.2f;//射线起始位置
direction = transform.forward;//射线方向
Vector3 targetPos = originPos + transform.forward * maxDistance;//射线最大检测距离处的点位置,找到起始点与终点,方便画线
Ray ray = new Ray(originPos, direction);//创建名为ray的射线
RaycastHit hit;//碰撞检测信息存储
if (Physics.Raycast(ray, out hit, maxDistance)){//碰撞检测
Debug.DrawLine(originPos,hit.point);//画线显示
Debug.Log (hit.collider.name);//打印检测到的碰撞体名称
}
else {
Debug.DrawLine(originPos, targetPos);//没检测到碰撞体,则以最大检测距离画线
}
}
}
基于我对编程的一点浅薄理解,加上过去的经验,我把这段代码的原理读懂之后在Bolt中进行了复现。
将这段代码挂在相机上,射线起点以及方向都可以直接通过相机自身获取。
这里成功的从相机的位置向相机朝向发**一条射线,并且进行了碰撞检测。后续一切功能的基础就有了。
生成模型
实现了射线检测之后,就可以尝试在射线击中的位置生成一个模型,这一步完成后,基本的「检测+创建」功能就具有雏形了。
但我不知道如何才能让程序「生成」一个模型,于是在YouTube搜索了「unity create object」,找到了这个视频:https://www.youtube.com/watch?v=XO-E6QaTniQ
public class spawner : MonoBehaviour {
public Transform spawnPos;
public GameObject spawnee;
void Update () {
if(Input.GetMouseButton(0)) {
Instantiate(spawnee, spawnPos.position, spawnPos.rotation);
}
}
}
他用来「生成」模型的代码段也非常简单,就是在点击鼠标左键后,在指定的位置「实例化」指定的模型,这里面的关键词就是「实例化(Instantiate)」。
于是我在上一阶段的射线检测部分之后,接上「Game Object Instantiate」节点以及鼠标点击的条件判断,就可以在点击鼠标左键后,在射线击中的位置生成模型了。
因为只是用来测试模型能否正常生成,所以直接用了一个内置的圆形作为生成对象,同时额外加了一个圆柱用来检查模型的方向。
另外,因为之前使用的「Raycast」节点并不包含「Layer Mask」,所以对所有碰撞体都会进行判定,但是我不希望在任何碰撞体上都能生成同一类模型,比如地块不应该能生成在家具上。所以我顺便将「Raycast」节点替换成了带有「Layer Mask」的类型。
向鼠标位置射线检测
现在可以在射线击中的位置生成我需要的模型了,接下来就需要让射线打在我需要的位置,也就是鼠标在画面上所指的位置。
同样经过简单的搜索,我找到了一个鼠标点击射线检测的代码:
(代码来源:https://www.cnblogs.com/guxin/p/unity-mouse-click-raycast-detect-collision.html)
void Update (){
if(Input.GetMouseButton(0)){
//从摄像机发出到点击坐标的射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if(Physics.Raycast(ray,out hitInfo)){
//划出射线,只有在scene视图中才能看到
Debug.DrawLine(ray.origin,hitInfo.point);
GameObject gameObj = hitInfo.collider.gameObject;
Debug.Log("click object name is " + gameObj.name);
//当射线碰撞目标为boot类型的物品,执行拾取操作
if(gameObj.tag == "boot"){
Debug.Log("pickup!");
}
}
}
}
同样总体上和之前的差别不大,关键在于第4行,这一行告诉了我如何获取鼠标位置以及向鼠标点击的方向发射射线。
照着范例代码以节点形式原样实现之后,就顺利实现了鼠标射线检测的功能了。
另外,因为动森的建造是基于格子的,所以创建坐标应当是整数,所以我还对实例化模型的坐标进行了四舍五入取整。
以下就是现阶段所实现的效果:
目录