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

来源:互联网 发布:重庆双成网络 编辑:程序博客网 时间:2024/04/29 19:36
本文永久地址: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 文件,代码如下:

01using UnityEngine;
02using System.Collections.Generic;
03 
04public 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,代码如下:

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

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

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

001using UnityEngine;
002using System.Collections;
003 
004public 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,代码如下:

01using UnityEngine;
02using System.Collections;
03 
04public 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
原创粉丝点击