4.10 自定义按钮 在塔防游戏中,防守单位都是动态创建的,通常游戏中将提供若干个按钮,当选中其中一个按钮后,这个按钮将保持激活状态,这时在场景中选择位置按下鼠标,即可创建一个防守单位,同时也会扣除一些用于创建防守单位的资金或点数。 接下来我们将创建一个自定义的按钮,虽然使用OnGUI创建按钮更容易,但OnGUI的效率较低,也不利于制作布局复杂或有特殊需求的UI,更难为它制作动画。 创建脚本GUIButton.cs: using UnityEngine; using System.Collections; public class GUIButton : MonoBehaviour { // 按钮状态 protected enum StateID { NORMAL=0, // 正常 FOCUS, // 高亮 ACTIV, // 选中 } protected StateID m_state = StateID. NORMAL; // 按钮的贴图 public Texture[] m_ButtonSkin; // 按钮的ID public int m_ID = 0; // 按钮是否处于激活状态 protected bool m_isOnActiv = false; // 按钮的缩放 public float m_scale = 1.0f; // 按钮的屏幕位置 Vector2 m_screenPosition; // 按钮的当前贴图 public GUITexture m_texture; // 初始化按钮 void Awake() { // 获得贴图 m_texture = this.guiTexture; // 获得位置 m_screenPosition = new Vector3(m_texture.pixelInset.x, m_texture.pixelInset.y, 0); // 设置默认状态 SetState(StateID.NORMAL); } //更新按钮状态,选中按钮,返回它的ID public int UpdateState(bool mouse,Vector3 mousepos) { int result = -1; if (m_texture.HitTest(mousepos)) { if (mouse) { SetState(StateID.ACTIV); return m_ID; } else SetState(StateID.FOCUS); } else { if (m_isOnActiv) SetState(StateID.ACTIV); else SetState(StateID.NORMAL); } return result; } // 设置按钮状态 protected virtual void SetState(StateID state) { if (m_state == state) return; m_state = state; m_texture.texture = m_ButtonSkin[(int)m_state]; float w = m_ButtonSkin[(int)m_state].width * m_scale; float h = m_ButtonSkin[(int)m_state].height * m_scale; m_texture.pixelInset = new Rect(this.m_screenPosition.x, m_screenPosition.y, w, h); } // 设置按钮缩放 public virtual void SetScale(float scale) { m_scale = scale; float w = m_ButtonSkin[0].width * scale; float h = m_ButtonSkin[0].height * scale; m_screenPosition.x *= scale; m_screenPosition.y *= scale; m_texture.pixelInset = new Rect(m_screenPosition.x, m_screenPosition.y, w, h); } // 设置激活状态 public virtual void SetOnActiv(bool isactiv) { if (isactiv) SetState(StateID.ACTIV); else if (m_isOnActiv) SetState(StateID.NORMAL); m_isOnActiv = isactiv; } } 在这个脚本中,Awake函数初始化了按钮的状态,按钮一共有三种状态,包括正常、高亮和激活。 在UpdateState函数中,判断是否选中按钮,如果选中,则返回按钮的ID。 SetState函数用来设置按钮的状态,实际上是在更新按钮的贴图。 SetScale函数用来设置按钮的缩放,在手机平台,因为机器设备的分辨率不统一,所以在不同平台对按钮进行相应缩放是必要的。 SetOnActiv函数会将按钮设为激活状态,当按钮处于这种状态,即使鼠标从按钮上移开,按钮的状态也不会改变。 在Project窗口的GUI文件夹中找到ui_turret_n.png,确定它处于选择状态,在菜单栏选择【GameObject】→【Create Other】→【GUI Texture】创建一个显示有UI贴图的游戏体,然后为它指定脚本GUIButton.cs,在Button Skin中设置对应按钮三种状态的贴图,将按钮的ID设为1,如图4-27所示。 我们将不使用3D坐标改变按钮的位置,将按钮的Position设为0,然后将Pixel Inset的X和Y设为5,如图4-27所示,按钮将出现在屏幕坐标(5,5)的位置,如图4-28所示。 图4-27 设置贴图 图4-28 设置按钮位置 将按钮命名为button_0,并设为GameManager的子物体,如图4-29所示。 图4-29 设置按钮为GameManager的子物体 打开GameManager.cs,加入按钮等相关属性如下,然后将前面创建的防守单位与m_guardPrefab关联。新建一个名为ground的Layer,将m_groundlayer与其关联。 // 按钮 GUIButton m_button; // 当前选中的按钮ID int m_ID; // 防守单位prefab public Transform m_guardPrefab; // 地面的碰撞层 public LayerMask m_groundlayer; 在GameManager.cs的Start函数中获得按钮: m_button = this.transform.FindChild("button_0").GetComponent<GUIButton>(); 更新脚本GameManager.cs的Update函数如下: void Update () { //如果生命为0 if (m_life <= 0) return; // 按下鼠标操作 bool press=Input.GetMouseButton(0); // 松开鼠标操作 bool mouseup = Input.GetMouseButtonUp(0); // 获得鼠标位置 Vector3 mousepos = Input.mousePosition; // 获得鼠标移动距离 float mx = Input.GetAxis("Mouse X"); float my = Input.GetAxis("Mouse Y"); // 如果当前按钮ID大于0,并且处于松开鼠标操作 if (m_ID > 0 && mouseup ) { //如果小于5个点数,返回,什么也不做 if (m_point < 5) { m_ID = 0; m_button.SetOnActiv(false); return; } //创建一条从摄像机射出的射线 Ray ray = Camera.main.ScreenPointToRay(mousepos); //计算射线与地面的碰撞 RaycastHit hit; if ( Physics.Raycast(ray, out hit, 100, m_groundlayer) ) { //获得碰撞点的位置 int ix = (int)hit.point.x; int iz = (int)hit.point.z; if (ix >= GridMap.Instance.MapSizeX || iz >= GridMap.Instance.MapSizeZ || ix<0 || iz<0 ) return; // 如果当前单元格可以摆放防守单位 if (GridMap.Instance.m_map[ix, iz].fieldtype == MapData.FieldTypeID. GuardPosition) { Vector3 pos = new Vector3((int)hit.point.x + 0.5f, 0, (int)hit.point.z + 0.5f); // 创建防守单位 Instantiate(m_guardPrefab, pos, Quaternion.identity); m_ID = 0; // 按钮重新恢复到正常状态 m_button.SetOnActiv(false); // 减少5个点数 SetPoint(-5); } } } // 获得按钮的ID int id = m_button.UpdateState(mouseup, Input.mousePosition); if (id > 0) { m_ID = id; // 激活按钮,这时可以创建防守单位 m_button.SetOnActiv(true); return; } // 移动摄像机 GameCamera.Inst.Control(press, mx, my); } 在Update中,首先获得了鼠标操作的各种状态,如果当前按钮的ID大于0,说明激活了按钮,这时在场景中点击鼠标左键,即会在单元格中创建一个防守单位,每创建一个防守单位还会减少5个点数。 我们的游戏现在只有一个按钮,当我们有多个按钮时,可以将这些按钮都放入到ArrayList中,然后在循环中对每个按钮进行测试,是否按中了其中某一个按钮。 将地面的Layer设为ground,使射线可以与地面碰撞。 运行游戏,按一下屏幕上的按钮,然后在地面上点一下,即可创建一个防守单位。 这个塔防游戏到这里就结束了,它还非常简陋,但具备了塔防游戏的基本要素,如果添加更多的细节和更好的画面,相信它可以变成一款不错的游戏。 本章的最终示例工程保存在光盘目录chapter04_TD,包括Excel文件。在光盘目录buildstd中保存有已经编译好的文件,可以直接运行游戏。