HuQJ's Blog
  博客内容

unity3d制作RPG游戏系列(4)——UI界面

发布时间:[2018-12-18 15:21:02]     博客分类:[unity3d]     浏览数量:(968)

游戏中除了3d场景以外,UI界面也是很重要的一部分,例如主角的头像、血条、背包、按钮等。

下面是myrpg中的简单UI界面,使用NGUI插件制作:

ui.png


一、导入NGUI插件

NGUI是一个非官方的GUI插件,但是特别好用,所以一般大家都会选择NGUI来制作界面,导入方式很简单,下载NGUI 的unity包然后拖到项目中即可,因为NGUI包有版本要求,所以这里就不贴资源了,大家可以到网上下载。

倒入之后菜单栏会多一个NGUI的菜单:

图片.png


二、使用NGUI制作血条示例

NGUI有一些预制的组件,例如进度条,这可以方便的实现血条显示功能,首先从 NGUI -> open -> prefeb toolbar 打开预制对象

图片.png

然后选择一个进度条样式拖拽到Hierarchy中,这样就创建了一个进度条:

图片.png

这里的hp bar就是血条,它由多个子物体组成,包括背景色、前景色等,可以在Inspector中设置调整进度条的颜色,大小、位置

图片.png


图片.png

然后注意到进度条中on个有一个UISlider组件,通过这个组件的value值就可以调整进度条的进度显示:

图片.png

所以在代码中人物血量变化的地方(例如收到攻击、喝药水),重新设置进度条的进度从而实现血条的功能。

//血条显示
private static UISlider _hPSlider;

void Start()
{
    _hPSlider = GameObject.FindGameObjectWithTag("hp").GetComponent<UISlider>();
}

/**
* 给主角造成伤害
*/
public static void GotAttack(int hurt)
{
   ......

    tmp = Health - hurt;
    Health = tmp > 0 ? tmp : 0;
    //血条显示值变化
    _hPSlider.value = (float) Health / MaxHealth;
}


三、按钮制作示例

按钮制作的关键在于绑定点击事件,如果使用u3d自带的ui组件还挺麻烦的,但是NGUI就很简单了。

首先创建一个sprite组件,这个组件其实就可以理解为一个图片,然后选择图集里的一张图片绑定在sprite上,然后在父sprite目录下还可以创建子sprite,同样可以绑定一张图片,这样就可以实现按钮的背景和前景图片了。

图片.png

这里我制作的是加血的按钮,所以还有一个数字label用来显示还剩多少血药,效果如下:

图片.png

为了实现按钮的效果还需要给sprite绑定一个按钮点击组件,方法是选中sprite之后,点击NGUI->Attach->Collider,这样添加了一个碰撞检测,然后再点击NGUI->Attach->Button Script这样就绑定了一个点击脚本,会发现这时候多出了一个组件可以绑定一个点击函数:

图片.png

然后写一个点击事件的脚本,创建一个public的非静态方法:

/**
 * 使用血药加血(按钮效果)
 */
public void UseHP()
{
    UseHP_s();
}

//用于快捷键
public void UseHP_s()
{
    if (HPNum > 0)
    {
        _hero.DrinkPotion();
        HPNum--;
        Health += HPRecoverNum;
        if (Health > MaxHealth)
        {
            Health = MaxHealth;
        }

        _hPSlider.value = (float) Health / MaxHealth;
        _hPNumLabel.text = HPNum.ToString();
    }
}

然后将这个脚本绑定到任何一个游戏对象中,这里我绑定的是camera,然后将camera拖拽到上面的button script中即可。


四、背包制作

背包是RPG游戏中不可或缺的一部分,制作背包的过程其实也是使用Sprite的过程,就我这里的背包来说,包含以下几个部分:

背景、关闭按钮、物品格子、物品图标

所以首先创建一个sprite并将背包背景图片绑定在上面,然后创建若干子sprite作为格子,并将格子图片绑定在上面,同理还有关闭按钮。最后如果格子里有物品还需要将物品作为一个sprite并且赋给格子的自物体。结构如下:

图片.png

然后通过创建一个背包按钮来控制背包的显示和关闭即可。背包中的物品图标应当事先做做成预制体,当游戏逻辑中剪刀物品放入背包后则在格子相应的sprite下面添加一个该预制体。

向背包中添加物品的方法如下:

private bool AddItemToBag(string name)
{
    //血药和体力值不往背包放
    if (name.Equals(GoodsManager.HP_POTIONS_NAME))
    {
        HeroStatus.AddHPMedicine();
        return true;
    }

    if (name.Equals(GoodsManager.EP_POTIONS_NAME))
    {
        HeroStatus.AddEPMedicine();
        return true;
    }

    if (goodsNum >= cells.Length)
    {
        return false; //背包已满
    }

    if (GoodsManager.Name2Sprite.ContainsKey(name))
    {
        //之前这些是放在场景中置为不可见的,需要复制一份出来
        _tmpGoods = Instantiate(GoodsManager.Name2Sprite[name]);
        _tmpGoods.SetActive(true);
        _tmpGoods.name = name;
        cells[goodsNum].AddChild(_tmpGoods);
        Destroy(_tmpGoods);
        goodsNum++;
        if (goods.ContainsKey(name))
        {
            _tmpNum = goods[name] + 1;
            goods.Remove(name);
        }
        else
        {
            _tmpNum = 1;
        }

        goods.Add(name, _tmpNum);
    }


    return true;
}

这里因为一些原因我没有将物品图标sprite作为预制体而是直接放在场景中置为不可见,当需要添加的时候复制一份并置为可见即可。而寻找对应


四、背包物品悬浮显示说明以及拖拽丢弃

背包中的物品需要在鼠标悬浮的时候显示物品说明,以及当背包满了的时候可以拖拽物品丢到地面。实现起来也比较简单,首先要给物品sprite添加一个UIEventListener组件,添加方法就直接点击Inspector中的Add Compenet即可。

图片.png

这个组件可以通过委托的方式实现各种事件。添加组件之后给物品绑定一个c#脚本,脚本中指定悬浮、拖拽等方法。

public class GoodsUsage : MonoBehaviour
{
    private UIEventListener _listener;

    public Texture2D UsageBackground;

    private string _desc;

    private Vector2 _size;

    private Vector2 _mousePosition;

    private bool _shouldDraw;

    private Hero _hero;

    //拖拽时记录物品原来的位置
    private Vector2 _originPos;

    // Use this for initialization
    void Start()
    {
        _listener = UIEventListener.Get(gameObject);
        _listener.onHover += OnHoverGoods;
        _listener.onDrag += OnDrag;
        _listener.onDragEnd += OnDragEnd;
        _listener.onDragStart += OnDragStart;
        _desc = GoodsManager.Name2Desc[gameObject.name];
        _hero = GameObject.FindGameObjectWithTag("hero").GetComponent<Hero>();
    }

    // Update is called once per frame
    void Update()
    {
    }

    //悬浮显示物品用途
    private void OnHoverGoods(GameObject obj, bool isOver)
    {
        if (isOver) //进入悬浮
        {
            StartCoroutine(ShowUsageWait());
            _shouldDraw = true;
        }
        else //退出悬浮
        {
            _shouldDraw = false;
        }
    }

    //拖拽
    private void OnDragStart(GameObject obj)
    {
        if (!obj.transform.parent.gameObject.tag.Equals("cell"))
        {
            return;
        }

        _originPos = transform.localPosition;
    }

    private void OnDrag(GameObject obj, Vector2 delta)
    {
        if (!obj.transform.parent.gameObject.tag.Equals("cell"))
        {
            return;
        }

        _shouldDraw = false;
        transform.localPosition = new Vector3(transform.localPosition.x + delta.x,
            transform.localPosition.y + delta.y, transform.localPosition.z);
    }

    private void OnDragEnd(GameObject obj)
    {
        if (!obj.transform.parent.gameObject.tag.Equals("cell"))
        {
            return;
        }

        //拖拽到地面
        if (Input.mousePosition.x < Screen.width - 430 || Input.mousePosition.y < Screen.height - 500)
        {
            Destroy(obj);

            _hero.ThrowGoods(gameObject.name, obj.transform.parent.gameObject);
        }
        else //回到格子
        {
            transform.localPosition = new Vector3(_originPos.x, _originPos.y, transform.localPosition.z);
        }
    }

    IEnumerator ShowUsageWait()
    {
        yield return new WaitForSeconds(1f);
    }

    private void OnGUI()
    {
        if (_shouldDraw)
        {
            _size = GUI.skin.label.CalcSize(new GUIContent(_desc));
            _mousePosition = Input.mousePosition;
            GUI.DrawTexture(new Rect(_mousePosition.x - _size.x - 10, Screen.height - _mousePosition.y,
                _size.x + 10, _size.y + 10), UsageBackground);
            GUI.color = Color.white;
            GUI.Label(new Rect(_mousePosition.x - 5 - _size.x, Screen.height - _mousePosition.y + 5,
                _size.x, _size.y), _desc);
        }
    }
}

这里,对于悬浮显示物品使用说明是通过OnGUI方法绘制的,在悬浮事件中设置一个是否绘制GUI的标识,然后在OnGUI方法中判断是否需要绘制说明,并且还需呀实现说明位置跟随鼠标移动,这里通过Input.mousePosition获得鼠标的位置信息。

对于拖拽事件,实际上有三个委托:OnDragStart, OnDrag, 和OnDragEnd。分别对应点击开始拖拽事件、拖拽中事件、和鼠标释放结束拖拽事件,分别需要实现:记录图片初始位置(以便为拖拽出去的时候复位)、让图标跟随鼠标移动实现拖拽效果、和判断是否将物品拖拽出去了的功能。

当然拖拽丢弃物品之后还需要在地图上生成一个3d物品,这个就不属于GUI应该做的逻辑了。


鼠标悬浮显示说明:

1.gif


拖拽丢弃:

1.gif


标签:

  所有评论

您尚未登录!选择登录方式登录后方可评论~    

      按博客类别
      按博客日期