yande.re 324063 sample animal_ears bathing cleavage feet garter lingerie nekomimi nopan see_through tail wasabi_(artist) wet_clothes.jpg
上一篇:

第七节 建筑部署控制器和界面操作实现


创建BuildingHandler脚本
实际创造建筑时,我们一般通过点击鼠标左键部署
这通常采用发射一条射线,将鼠标所在screen空间坐标点投射到world世界坐标系,这就是我们第三节通过图示讲解的ScreenToWorldPoint方法,不太熟悉的话,我们再看一下示意图
检测与世界地图ground碰撞后,获得碰撞点的x.z坐标,从而与上一节的board面板控制器交互,判断此刻是否持有一个建筑,判断资产是否足够创建新的建筑,之后再通过board面板控制发送出来部署的具体坐标位置,通过咱们的buildingHandler脚本进行部署。
好了,我们提取出所需要产生联系的组件:
1.City,我们需要获得City类里面的Cash属性,建造的时候进行判断,造完扣钱也要修改Cash值
同时,我们新建building后,还需要传递buildingcount计数器让buildingcount++。
2.改变cash后,UIController也要通信修改面板cash数值。
3.Board,我们需要传递out出来的hit碰撞点坐标给board控制器,接收board的执行命令是否创建新建筑到ground以及存储部署后的建筑信息到board类里面的building array数组。
于是我们先从City开始吧:

[SerializeField]
private City city;

同样的,我们需要通过观察面板显示city信息方便调试,于是做一个序列化。

[SerializeField]

再来一个UIController进行后续交互

private UIController uiController;

同样的,序列化后方便调试

[serialiazedField]

同UI里面那些button交互后,我们需要创建一个building array得到对应的建筑类型0,1,2,3这些信息,并在观察inspector面板将各个建筑模型预制体和这些建筑按钮进行绑定,因此也来个序列化方便操作吧:

[SerializeField]
private Building[] buildings;

再创建一个面板方便在放置建筑的时候进行交互判断:

[SerializeField]
private Board board;

我们还需要一个选中状态的建筑,等待鼠标左键放置,这个选择中的建筑,将起到和board沟通的作用,为放置建筑的最终步骤。

private Building selectedBuilding;

main函数里写一个方法

Public void EnableBuilder(int building)
{
    selectedBuilding = buildings[building];
}

回到 hierarchy面板,将buildinghandler挂载到ground物体上,将4个建筑按钮挂载ground物体上的enableBuilder点击On click事件,并且设置好对应数字,使得我们点击这4个按钮的时候,比如说道路就是0,让按钮上的值int0参数传入enableBuilder方法,将selctedBuilding赋值为buildings[0],这样就相当于获得了对应类型的建筑了.
为了稍后测试效果,我们将EnableBuilder方法写一个debug:

Debug.Log("选择的建筑是:" + selectedBuilding.buildingName);

接下来是一个核心方法,这节的重点,也是整个游戏系统内交互的重点。与board交互用,在update方法中,我们检测输入信息,比方说我们检测到鼠标左键右键之类的,我们就可以调用这个核心交互方法,效果就是,如果我们选择了一个建筑准备部署,此刻发射一条射线,如果射线碰撞到我们的ground,我们将hit点坐标信息提交给board,通过board组件里面的CalculateGridPosition方法将hit位置的x,z坐标记录进我们的grid网格数组里,通过x,z坐标取整后获得一个最近的网格位置,通过AddBuilding方法申明将此选中的建筑放置到这个网格值对应的坐标位,同时扣除对应资产金额。
这就是这个交互方法的核心功能和流程,我们开始逐一实现。

void InteractWithBoard()
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;
    if(Physics.Raycast(ray, out hit))

这里讲解一下out引用的原因,了解过的,可以跳过这段,
我们的raycasthit方法,如果命中三维物体,会记录第一个碰到的物体信息,保存在hit里,hit里会储存很多信息,包括碰撞点,碰到的表面的法线,射线从原点到碰撞点的距离,碰到的uv纹理坐标,碰到的collider碰撞器还有rigdbody刚体组件等待信息,是一份信息表格,不是简单的数值value,所以我们这里涉及到数值传递问题,值传递和引用,
一般我们操作对象,调用参数都是pass by value居多,传递过来一个值,我们新建的变量会拷贝这个值,形成一个独立的备份数值,修改这个新变量,不会影响原变量或者对象,在内存层面上,也是新开内存地址储存新值。
而引用调用pass by reference则是直接操作原来的对象,不新建一份拷贝,修改新变量直接是通过引用地址直接修改原来的对象,所以这里的hit不能简单传值过来,需要out调取出来引用原hit信息,这就是使用out关键字的原因。
好了,我们继续

Vector3 gridPosition = board.CalculateGridPosition(hit.point);
if(!board.IsBuildingHere(gridPosition))
{
    if (city.Cash >= selectedBuilding.cost)
    {
        city.Cash -= selectedBuilding.cost;
        uiController.UpdateCityData();
        city.buildingCounts[selectedBuilding.id]++;
        board.AddBuilding(selectedBuilding, gridPosition);
    }
}

我们的交互功能主体差不多都实现了,现在只需要在update方法中加入Input信息检测,就可以调用了

if(Input.GetMouseButtonDown(0)&& selectedBuilding != null)
        {
            InteractWithBoard();
        }

我们可以去hierarchy面板绑定信息进行测试了
之前咱们的Handler是挂载到ground上面的,我们找到这个脚本,buildings属性下size字段填个4吧
我们之前在第二节已经将4个预制体挂载building脚本,如果你之前还未挂载的,记得依次绑定4种建筑预制体一个building脚本,先录入每种建筑的id,cost,buildingName信息,搞定之后,现在就可以将4个预制体模型按照屏幕左下角顺序依次挂载到Element0-4个元素上了。
别忘了将ground上面的board脚本挂载到咱们这个handler脚本里的board上。
City物体上的city脚本挂载到handler脚本的city上,
还有Uicontroller也记得从city物体上直接拖过来挂载到handler脚本上,将handler所有字段属性都匹配完毕
运行游戏,基本的创建建筑放置功能就实现了,下一节我们将讲解如何移除建筑,还有一些细节的修改优化
下一篇:

Last modification:August 12, 2020
If you think my article is useful to you, please feel free to appreciate