撰写了文章 更新于 2021-07-16 21:17:54
【自学笔记】制作动森岛建规划器(四)
前言
我到这个阶段已经开始明显感觉到「Bolt可视化编程插件」相比直接使用C#代码的不足,我虽然考虑了直接用代码进行编程,但这意味着我肯定要花上一段时间学习C#的基础知识。
但是因为「Bolt可视化编程插件」的巨大局限性,所以我还是决定放弃了使用这个插件,转而学习C#,直接用写代码的方式来完成。
有之前的许多积累作为基础,学习C#比我预想的还要顺利许多,大约一两周左右已经可以写些东西出来了。
加上一些乱七八糟的实践,不太复杂的程序应该都没有什么问题了,只是应该不如专业程序员那么快速。
基于之前的规划,这里开始首先就是用代码把之前实现的功能全部复现一次。
而且因为之前做的过程中踩过一些坑,做了一些修改和调整,这次重做可以绕过这些修改,直接按照最后的效果编写。
相机射线检测
这部分没啥好说的,就是把之前做的节点用代码再写一遍。
原本还应该要有对相机本身的控制,但因为暂时还没做,所以现在就只有射线检测放在这里面。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public RaycastHit CameraRayCast(LayerMask layer)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 1000, layer))
{
Debug.DrawLine(transform.position, hit.point, Color.red);
}
return hit;
}
}
Cliff生成器
总体全局的一些函数和变量我都写在了LevelController里面。
目前的函数就只有生成各类地形块的Generator,这部分和之前的节点也基本差不多。
因为希望通用在不同地形的建造上,所以加了一个输入判断来确定是建造什么类型的地形,而所有Generator都存在一个数组里。
因为目前要先实现建造Cliff,所以数组里就放入了Cliff这一项,更多的在之后再逐步添加。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LevelController : MonoBehaviour
{
public Camera camera;
public GameObject buildButtonGroup;
public GameObject[] tileGenerator;
public void CreatGenerator(string tileType)
{
GameObject generator;
switch (tileType)
{
case "Cliff":
generator = tileGenerator[0];
break;
default:
generator = tileGenerator[0];
break;
}
GameObject clone = Instantiate(generator, new Vector3(0, 0, 0), Quaternion.identity);
clone.GetComponent<TileGenerator>().camera = camera;
}
}
建造Cliff
这个就是挂在Generator上的脚本了,同样是照着之前的节点用代码抄了一遍。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TileGenerator : MonoBehaviour
{
public Camera camera;
public GameObject tileMesh;
public LayerMask layerMask;
void Update()
{
FollowcCursor();
CreatTile();
CancelBuild();
}
void FollowcCursor()
{
Vector3 cursorPoint = camera.GetComponent<CameraController>().CameraRayCast(layerMask).point;
cursorPoint.x = Mathf.Round(cursorPoint.x);
cursorPoint.y = Mathf.Round(cursorPoint.y);
cursorPoint.z = Mathf.Round(cursorPoint.z);
transform.position = cursorPoint;
}
void CreatTile()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
GameObject clone = Instantiate(tileMesh, transform.position, Quaternion.identity);
}
}
void CancelBuild()
{
if(Input.GetKeyDown(KeyCode.Mouse1))
{
Destroy(gameObject);
GameObject.Find("LevelController").GetComponent<LevelController>().buildButtonGroup.SetActive(true);
}
}
}
然后是通过Generator生成的Tile本身的代码,这部分的核心就是自动计算Tile形状,实现方式也跟之前一样。
不过毕竟是目前最复杂的一个部分,写起来多少还是有点麻烦。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AutoTile : MonoBehaviour
{
public Mesh center, cornerLU, cornerRU,cornerLD, cornerRD, innerCornerLU, innerCornerRU, innerCornerLD, innerCornerRD, sideU, sideD, sideL, sideR;
public LayerMask layer;
bool[] tileState = new bool[8];
Collider[] tileCollider = new Collider[8];
void Start()
{
SetTile();
ResetSurroundingTile();
}
ArrayList CheckTile(Vector3 direction)
{
ArrayList checkOutput = new ArrayList(){false, null};
Vector3 origin = gameObject.transform.position + new Vector3(0, 0.5f, 0);
RaycastHit hit;
Physics.Raycast(origin, direction, out hit, 1, layer);
if (hit.collider != null)
{
checkOutput[0] = true;
checkOutput[1] = hit.collider;
}
return checkOutput;
}
void CheckSurroundingTileState()
{
//从左上开始,顺时针计算
tileState[0] = (bool)CheckTile(new Vector3(1, 0, -1))[0];//左上
tileState[1] = (bool)CheckTile(new Vector3(0, 0, -1))[0];//上
tileState[2] = (bool)CheckTile(new Vector3(-1, 0, -1))[0];//右上
tileState[3] = (bool)CheckTile(new Vector3(-1, 0, 0))[0];//右
tileState[4] = (bool)CheckTile(new Vector3(-1, 0, 1))[0];//右下
tileState[5] = (bool)CheckTile(new Vector3(0, 0, 1))[0];//下
tileState[6] = (bool)CheckTile(new Vector3(1, 0, 1))[0];//左下
tileState[7] = (bool)CheckTile(new Vector3(1, 0, 0))[0];//左
tileCollider[0] = (Collider)CheckTile(new Vector3(1, 0, -1))[1];//左上
tileCollider[1] = (Collider)CheckTile(new Vector3(0, 0, -1))[1];//上
tileCollider[2] = (Collider)CheckTile(new Vector3(-1, 0, -1))[1];//右上
tileCollider[3] = (Collider)CheckTile(new Vector3(-1, 0, 0))[1];//右
tileCollider[4] = (Collider)CheckTile(new Vector3(-1, 0, 1))[1];//右下
tileCollider[5] = (Collider)CheckTile(new Vector3(0, 0, 1))[1];//下
tileCollider[6] = (Collider)CheckTile(new Vector3(1, 0, 1))[1];//左下
tileCollider[7] = (Collider)CheckTile(new Vector3(1, 0, 0))[1];//左
}
void SetTileCorner(string name, bool stateL, bool stateR, bool stateCorner, Mesh corner, Mesh sideL, Mesh sideR, Mesh innerCorner, Mesh center)
{
if (!stateL && !stateR)
{
transform.Find(name).GetComponent<MeshFilter>().mesh = corner;
}
else if (stateL && !stateR)
{
transform.Find(name).GetComponent<MeshFilter>().mesh = sideR;
}
else if (!stateL && stateR)
{
transform.Find(name).GetComponent<MeshFilter>().mesh = sideL;
}
else if (stateL && stateR)
{
if (stateCorner)
{
transform.Find(name).GetComponent<MeshFilter>().mesh = center;
}
else
{
transform.Find(name).GetComponent<MeshFilter>().mesh = innerCorner;
}
}
}
void SetTile()
{
CheckSurroundingTileState();
SetTileCorner("LU", tileState[7], tileState[1], tileState[0], cornerLU, sideL, sideU, innerCornerLU, center);
SetTileCorner("RU", tileState[1], tileState[3], tileState[2], cornerRU, sideU, sideR, innerCornerRU, center);
SetTileCorner("LD", tileState[5], tileState[7], tileState[6], cornerLD, sideD, sideL, innerCornerLD, center);
SetTileCorner("RD", tileState[3], tileState[5], tileState[4], cornerRD, sideR, sideD, innerCornerRD, center);
}
void ResetSurroundingTile()
{
if (tileCollider[0]) { tileCollider[0].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[1]) { tileCollider[1].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[2]) { tileCollider[2].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[3]) { tileCollider[3].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[4]) { tileCollider[4].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[5]) { tileCollider[5].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[6]) { tileCollider[6].gameObject.GetComponent<AutoTile>().SetTile(); }
if (tileCollider[7]) { tileCollider[7].gameObject.GetComponent<AutoTile>().SetTile(); }
}
}
拆除Cliff
这部分也没啥内容,照着之前的节点,把Generator的内容稍作修改就完了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TileRemover : MonoBehaviour
{
public Camera camera;
public LayerMask layerMask;
void Update()
{
RemoveTile();
CancelRemove();
}
void RemoveTile()
{
RaycastHit cursorPoint = camera.GetComponent<CameraController>().CameraRayCast(layerMask);
if(Input.GetKeyDown(KeyCode.Mouse0) && cursorPoint.collider)
{
cursorPoint.collider.gameObject.GetComponent<AutoTile>().RemoveSelf();
}
}
void CancelRemove()
{
if (Input.GetKeyDown(KeyCode.Mouse1))
{
Destroy(gameObject);
LevelController LvCtrl = GameObject.Find("LevelController").GetComponent<LevelController>();
LvCtrl.buildButtonGroup.SetActive(true);
LvCtrl.isRemove = false;
}
}
}
然后在LevelConrtoller里加上生成Remover的函数
public void CreatRemover(string tileType)
{
GameObject remover;
switch (tileType)
{
case "Cliff":
remover = tileRemover[0];
break;
default:
remover = tileRemover[0];
break;
}
GameObject clone = Instantiate(remover, new Vector3(0, 0, 0), Quaternion.identity);
clone.GetComponent<TileRemover>().camera = camera;
isRemove = true;
}
因为是让被拆除的Cliff自行销毁自身,所以在AutoTitle里也加上了自我销毁的函数。
public void RemoveSelf()
{
gameObject.layer = 0;
CheckSurroundingTileState();
ResetSurroundingTile();
Destroy(gameObject);
}
函数内前三行的作用在之前的节点版有提过,是为了在避免销毁后周围八个Tile能够重新适应新的形状。
到这为止,基本的功能就重写完了。
接下来就是要把一些细节反馈也做了,包括重叠检测、视觉反馈、音效反馈、UI提示。
细节处理
首先是视觉反馈,包括了要拆除的Cliff以及与Cliff重叠后的Generator。
先是要拆除的Cliff,在isRemove状态下,鼠标悬停后颜色变红,而鼠标离开后恢复白色。
private void OnMouseEnter()
{
if(GameObject.Find("LevelController").GetComponent<LevelController>().isRemove)
{
gameObject.transform.Find("LU").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.red);
gameObject.transform.Find("RU").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.red);
gameObject.transform.Find("LD").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.red);
gameObject.transform.Find("RD").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.red);
}
}
private void OnMouseExit()
{
if (GameObject.Find("LevelController").GetComponent<LevelController>().isRemove)
{
gameObject.transform.Find("LU").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.white);
gameObject.transform.Find("RU").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.white);
gameObject.transform.Find("LD").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.white);
gameObject.transform.Find("RD").GetComponent<MeshRenderer>().material.SetColor("_Color", Color.white);
}
}
然后是重叠状态的Generator,用OverlappingCount来记录重叠的对象数量,重叠对象数量为0的时候可以建造Cliff。
void CancelBuild()
{
if(Input.GetKeyDown(KeyCode.Mouse1))
{
Destroy(gameObject);
GameObject.Find("LevelController").GetComponent<LevelController>().buildButtonGroup.SetActive(true);
}
}
private void OnTriggerEnter(Collider other)
{
OverlappingCount += 1;
}
private void OnTriggerExit(Collider other)
{
OverlappingCount -= 1;
}
剩下的音效和UI的反馈比较琐碎,就不写了.
到这为止,之前用「Bolt可视化编程插件」的节点所实现的功能,除了河流以外都已经用代码实现了一遍。
之前就是在河流的瀑布上感到有些棘手而想对整个建造系统进行优化,接下来就是用代码来继续完成这个优化,并且同时把河流的建造拆除也完成。
目录