Unity3D 基地实现(摄像机移动、拖动建筑等)

本文永久地址:http://www.omuying.com/article/34.aspx, 【文章转载请注明出处!】

在做一个策略类的游戏时,需要实现一个基地的功能,功能并不是太复杂,默认只能显示场景(45度视角)的一部分,然后通过移动场景(地形)查看场景中的其他部分,当点击建筑时可以拖动场景中的建筑到一定地方!

最终效果如下:

第一步:先布局好场景界面,如图:

下面我们先把地表的网格显示出来,这儿用的是 Unity3D 自带的透明顶点 Shader,暂时没有想到好的解决办法,如下图:

接着我们设置主摄像机的旋转视角为45度,我们现在可以看到场景里面有两个摄像机,另一个摄像机的目的是为了当我们拖动对象时可以始终保持被拖动的对象被优先渲染,如下图:

另外我们需要保证另一个摄像机(BuildingCamera)的旋转、位置、缩放要与主摄像机相同,并且保证 Depth 要大于主摄像机的 Depth:

下面我们新建立一个层,主要用于显示被拖动的对象,如图:

我们再添加一个 Tag 为 Drag 的标记,主要用来检测拖动的对象,如图:

到现在,另外我们还需要确保 Grids 与 Buildings 的位置、旋转、缩放保持一致,主要目的是为了计算单位统一:

下面我们设置 Plane 与 Small、Large、Middle 对象的 Tag 为 Drag,因为 Plane 是放置,Small、Large、Middle 对象是可拖动对象,如图:

到这儿场景基本上布置完毕,现在我们需要编写代码来实现了,首先我们给建立一个 C# 类,取名 SceneGrid.cs 文件,代码如下:

01 using UnityEngine;
02 using System.Collections.Generic;
03  
04 public class SceneGrid : MonoBehaviour
05 {
06     public int gridRows;
07     public int gridCols;
08  
09     public Dictionary<Vector3, int> gridList;
10  
11     void Awake()
12     {
13         this.gridList = new Dictionary<Vector3, int> ();
14  
15         float beginRow = -this.gridRows * 0.5f + 0.5f;
16         float beginCol = -this.gridCols * 0.5f + 0.5f;
17          
18         Vector3 position = Vector3.zero;
19          
20         for (int rowIndex = 0; rowIndex < this.gridRows; rowIndex ++)
21         {
22             for (int colIndex = 0; colIndex < this.gridCols; colIndex ++)
23             {
24                 position = new Vector3(beginRow + rowIndex, 0f, beginCol + colIndex);
25                 this.gridList.Add(position, 0);
26             }
27         }
28     }
29  
30     /// <summary>
31     /// 更新格子状态
32     /// </summary>
33     /// <param name="positionList">Position list.</param>
34     /// <param name="status">If set to <c>true</c> status.</param>
35     public void SetGrid(Vector3[] positionList, bool status)
36     {
37         foreach (Vector3 position in positionList)
38         {
39             if(this.gridList.ContainsKey(position))
40             {
41                 this.gridList[position] = (status == true ? 1 : 0);
42             }
43         }
44     }
45  
46     /// <summary>
47     /// 能否可以放置
48     /// </summary>
49     /// <returns><c>true</c> if this instance can drop the specified positionList; otherwise, <c>false</c>.</returns>
50     /// <param name="positionList">Position list.</param>
51     public bool CanDrop(Vector3[] positionList)
52     {
53         foreach (Vector3 position in positionList)
54         {
55             if(!this.gridList.ContainsKey(position)) return false;
56             if(this.gridList[position] != 0) return false;
57         }
58         return true;
59     }
60 }

然后我们把 SceneGrid.cs 挂载到 Plane 对象上,如图:

然后我们继续创建一个 C# 类,取名:SceneBuilding.cs,代码如下:

1 using UnityEngine;
2 using System.Collections;
3  
4 public class SceneBuilding : MonoBehaviour
5 {
6     public int buildingWidth = 1;
7     public int buildingHeight = 1;
8 }

接着我们把 SceneBuilding.cs 依次添加到 Small、Large、Middle 对象上,如图:

我们再添加一个 C# 类,取名:SceneController.cs,这个类比较重要,场景的主要逻辑都在这个类当中,代码如下:

001 using UnityEngine;
002 using System.Collections;
003  
004 public class SceneController : MonoBehaviour
005 {
006     /// 鼠标枚举
007     enum MouseTypeEnum
008     {
009         LEFT = 0
010     }
011  
012     // 拖动建筑枚举
013     enum BuildingLayerEnum
014     {
015         BUILDING = 8
016     }
017  
018     /// <summary>
019     /// 水平移动速度
020     /// </summary>
021     public float horizontalSpeed = 10f;
022  
023     /// <summary>
024     /// 垂直移动速度
025     /// </summary>
026     public float verticalSpeed = 10f;
027  
028     /// <summary>
029     /// 滚轮速度
030     /// </summary>
031     public float mouseScrollSpeed = 10f;
032  
033     /// <summary>
034     /// 拖动状态判断 X 坐标
035     /// </summary>
036     public float moveOffsetX = 1f;
037  
038     /// <summary>
039     /// 拖动状态判断 Y 坐标
040     /// </summary>
041     public float moveOffsetY = 1f;
042  
043     /// <summary>
044     /// 主摄像机
045     /// </summary>
046     public Camera mainCamera;
047  
048     /// <summary>
049     /// 拖动建筑显示层
050     /// </summary>
051     public Camera buildingCamera;
052      
053     /// <summary>
054     /// 建筑容器对象,要跟 表格容器对象在相同位置,缩放、旋转都要相同
055     /// </summary>
056     public Transform buildingsObject;
057      
058     /// <summary>
059     /// 场景格子
060     /// </summary>
061     public SceneGrid sceneGrid;
062  
063     /// <summary>
064     /// 鼠标状态
065     /// </summary>
066     private bool mousePressStatus = false;
067  
068     /// <summary>
069     /// 鼠标 X 坐标
070     /// </summary>
071     private float mouseX;
072  
073     /// <summary>
074     /// 鼠标 Y 坐标
075     /// </summary>
076     private float mouseY;
077  
078     /// <summary>
079     /// 滚轮数据
080     /// </summary>
081     private float mouseScroll;
082  
083     /// <summary>
084     /// 建筑信息
085     /// </summary>
086     private SceneBuilding sceneBuilding;
087  
088     /// <summary>
089     /// 拖动的建筑对象
090     /// </summary>
091     private GameObject moveObject;
092  
093     /// <summary>
094     /// 移动对象的位置信息
095     /// </summary>
096     private Vector3 movePosition;
097  
098     /// <summary>
099     /// 移动偏移数据
100     /// </summary>
101     private Vector3 moveOffset;
102  
103     /// <summary>
104     /// 最后一次对象位置列表
105     /// </summary>
106     private Vector3[] prevPositionList;
107  
108     /// <summary>
109     /// 射线碰撞位置
110     /// </summary>
111     private Vector3 hitPosition;
112  
113     void Update()
114     {
115         // 按下鼠标、轴
116         if (Input.GetMouseButtonDown((int)MouseTypeEnum.LEFT))
117         {
118             this.mousePressStatus = true;
119  
120             // 如果有选中的建筑信息
121             if(this.sceneBuilding != null)
122             {
123                 // 重置建筑信息对象
124                 this.sceneBuilding = null;
125             }
126  
127             // 检测鼠标点击区域是否是建筑对象
128             this.sceneBuilding = PhysisUtils.GetTByMousePoint<SceneBuilding> (this.mainCamera);
129             // 如果是建筑对象
130             if (this.sceneBuilding != null)
131             {
132                 this.prevPositionList = GridUtils.GetPostionList(this.sceneBuilding.transform.localPosition,this.sceneBuilding.buildingWidth, this.sceneBuilding.buildingHeight);
133                 this.sceneGrid.SetGrid(this.prevPositionList, false);
134             }
135         }
136         // 松开鼠标、轴
137         if (Input.GetMouseButtonUp ((int)MouseTypeEnum.LEFT))
138         {
139             bool dropStatus = false;
140             this.mousePressStatus = false;
141             // 销毁拖动对象
142             if(this.moveObject != null && this.sceneBuilding != null)
143             {
144                 Vector3 targetPosition =this.moveObject.transform.localPosition;
145  
146                 Destroy(this.moveObject);
147                 this.moveObject = null;
148  
149                 if(this.CanDrop(ref this.hitPosition))
150                 {
151                     Vector3[] positionList = GridUtils.GetPostionList(targetPosition, this.sceneBuilding.buildingWidth,this.sceneBuilding.buildingHeight);
152                     if(this.sceneGrid.CanDrop(positionList))
153                     {
154                         dropStatus = true;
155                         this.sceneGrid.SetGrid(positionList, true);
156                         this.sceneBuilding.transform.localPosition = targetPosition;
157                     }else{
158                         Debug.Log("不能放置");
159                     }
160                 }
161             }
162             if(!dropStatus) if(this.prevPositionList != null)this.sceneGrid.SetGrid(prevPositionList, true);
163  
164             this.prevPositionList = null;
165         }
166         // 如果鼠标在按住状态
167         if (this.mousePressStatus)
168         {
169             this.mouseY = this.horizontalSpeed * Input.GetAxis ("Mouse X");
170             this.mouseX = this.verticalSpeed * Input.GetAxis ("Mouse Y");
171             // 当超过一定的偏移坐标,才视为拖动建筑
172             if((Mathf.Abs(this.mouseX) >= this.moveOffsetX || Mathf.Abs(this.mouseY) >= this.moveOffsetY) && this.sceneBuilding != null)
173             {
174                 // 创建一个新的建筑对象
175                 if(this.moveObject == null)
176                 {
177                     // 设置建筑信息的屏幕坐标
178                     this.movePosition =this.mainCamera.WorldToScreenPoint(this.sceneBuilding.transform.position);
179                     // 设置建筑信息的坐标偏移值
180                     this.moveOffset = this.sceneBuilding.transform.position - this.mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, this.movePosition.z));
181  
182                     this.moveObject = (GameObject)Instantiate(this.sceneBuilding.gameObject);
183  
184                     this.moveObject.name =this.sceneBuilding.gameObject.name;
185                     this.moveObject.tag = null;
186                     this.moveObject.layer = (int)BuildingLayerEnum.BUILDING;
187                     this.moveObject.transform.parent =this.buildingsObject;
188  
189                     Destroy(this.moveObject.GetComponent<SceneBuilding>());
190                     Destroy(this.moveObject.GetComponent<BoxCollider>());
191  
192                     this.moveObject.transform.localPosition =this.sceneBuilding.gameObject.transform.localPosition;
193                 }
194             }
195             // 如果移动摄像机
196             if(this.sceneBuilding == null)
197             {
198                 Vector3 rotationVector = Quaternion.Euler(this.mainCamera.transform.eulerAngles) * newVector3(this.mouseY, 0f, this.mouseX);
199             rotationVector.y = 0f;
200             this.mainCamera.transform.localPosition -= rotationVector * Time.deltaTime;
201             }else
202             {
203                 // 如果移动的是建筑
204                 if(this.moveObject != null)
205                 {
206                     if(this.CanDrop(ref this.hitPosition))
207                     {
208                         this.hitPosition -= this.moveOffset; 
209                          
210                         Vector3 currentLocalPosition =this.buildingsObject.transform.InverseTransformPoint(this.hitPosition);
211  
212                         currentLocalPosition.x = (int)currentLocalPosition.x - 0.5f;
213                         currentLocalPosition.z = (int)currentLocalPosition.z - 0.5f;
214  
215                         if(this.sceneBuilding.buildingWidth % 2 == 0)
216                         {
217                             currentLocalPosition.x += 0.5f;
218                         }
219                         if(this.sceneBuilding.buildingHeight % 2 == 0)
220                         {
221                             currentLocalPosition.z += 0.5f;
222                         }
223  
224                         currentLocalPosition.y = 0f;
225  
226                         // 设置对象跟随鼠标
227                         this.moveObject.transform.localPosition = currentLocalPosition;
228                     
229                 }
230             }
231         }
232         // 鼠标滚轮拉近拉远
233         this.mouseScroll = this.mouseScrollSpeed * Input.GetAxis ("Mouse ScrollWheel");
234         if (this.mouseScroll != 0f)
235         {
236             this.mainCamera.transform.localPosition -= new Vector3(0f, mouseScroll, 0f) * Time.deltaTime;
237         }
238          
239         this.buildingCamera.transform.localPosition =this.mainCamera.transform.localPosition;
240     }
241  
242     /// <summary>
243     /// 能否放置
244     /// </summary>
245     /// <returns><c>true</c> if this instance can drop the specified position; otherwise, <c>false</c>.</returns>
246     /// <param name="position">Position.</param>
247     private bool CanDrop(ref Vector3 position)
248     {
249         Ray ray = this.mainCamera.ScreenPointToRay(Input.mousePosition);
250         RaycastHit raycastHit = new RaycastHit(); 
251          
252         if(Physics.Raycast(ray, out raycastHit))
253         
254             if(raycastHit.collider.tag == "Drag")
255             {
256                 position = raycastHit.point;
257                 return true;
258             }
259         }
260         return false;
261     }
262 }

然后把 SceneController.cs 挂载到 SceneController 对象上,并且设置好相关属性,如图:

代码中,提取出了一个计算表格所占格子的类,取名 :GridUtils.cs,代码如下:

01 using UnityEngine;
02 using System.Collections;
03  
04 public class GridUtils
05 {
06     /// <summary>
07     /// 获取格子所在的位置列表
08     /// </summary>
09     /// <returns>The postion list.</returns>
10     /// <param name="transformPosition">Transform position.</param>
11     /// <param name="buildingWidth">Building width.</param>
12     /// <param name="buildingHeight">Building height.</param>
13     public static Vector3[] GetPostionList(Vector3 transformPosition, intbuildingWidth, int buildingHeight, int gridRows = 10, int gridWidth = 10)
14     {
15         Vector3 localPosition = new Vector3 (transformPosition.x, 0f, transformPosition.z);
16         localPosition.x -= buildingWidth * 0.5f;
17         localPosition.z += buildingHeight * 0.5f;
18          
19         Vector3[] positionList = new Vector3[buildingWidth * buildingHeight];
20          
21         for (int rowIndex = 0; rowIndex < buildingWidth; rowIndex ++)
22         {
23             for (int colIndex = 0; colIndex < buildingHeight; colIndex ++)
24             {
25                 positionList[rowIndex * buildingHeight + colIndex] = newVector3(localPosition.x + rowIndex + 1f - 0.5f, 0f, localPosition.z - colIndex - 1f + 0.5f);
26             }
27         }
28          
29         return positionList;
30     }
31 }

到这儿一切都完成了,现在我们运行一下看看效果吧,用到的 PhysisUtils.cs(物理检测相关的类在另外的文章有提到)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值