zoukankan      html  css  js  c++  java
  • Unity3d项目入门之虚拟摇杆

      Unity本身不提供摇杆的组件,开发者可以使用牛逼的EasyTouch插件或者应用NGUI实现相关的需求,下面本文通过Unity自身的UGUI属性,实现虚拟摇杆的功能。 主参考 《Unity:使用 UGUI 的 ScrollRect 製作虛擬搖桿》和松神的《UGUI研究院之游戏摇杆》,分“摇杆UI的构建”和“摇杆事件连接”以及“摇杆表现强化”三方面总结制作过程中的思路笔记。

      一 摇杆UI的搭建

      核心是使用UGUI 的 ScrollRect Component( 经常用于 Scroll View 的制作),因能响应拖动回弹,同时又能计算对象的X轴和Y轴的偏移,所以选择此组件开发。 创建两Image组件,分别显示摇杆背景和摇杆圆点,

      (overlay保证是场景摄像机看到的最上层)

      (摇杆圆点以底座节点作parent,重置位置)

      给stickBg_添加ScrollRect组件(实质是一脚本),并在 Inspector 视窗將 Content 栏位拖动设置stick_为其内容,

      

      注意,Movement Type 项,将此栏位设置为 Elastic,英文意思是回弹,则能自动将设置在 Content 的物件拉回置中,拖动原件,會有一定程度的缓冲力使物体不被拉远,並在放开时自动弹回置中。

      运行游戏,发现虽然能拖能回弹,但是只能矩形拖动,而且矩形区域贼大,不符合实际项目的摇杆需求。 参考松神博文,直接继承重写ScrollRect组件,代码如下:

    using UnityEngine;
    using System.Collections;
    using UnityEngine.UI; //ScrollRect组件定义在UI的命名空间
    using UnityEngine.EventSystems;
    
    public class ScrollCircle : ScrollRect
    {
        protected float mRadius;
    
        protected override void Start()
        {
    
            this.mRadius = (transform as RectTransform).sizeDelta.x * 0.5f;
        }
    
        public override void OnDrag(PointerEventData eventData)
        {
    
            base.OnDrag(eventData);
    
            Vector2 contentPostion = base.content.anchoredPosition;
    
            if (contentPostion.magnitude > this.mRadius)
            {
    
                contentPostion = contentPostion.normalized * this.mRadius;
            }
    
            base.content.anchoredPosition = contentPostion;
        }
    }

      运行游戏,发现拖动半径生效。

      【补料:

         (直接上传给大家凑合着用,亲身找过资源,都要钱不好找哈哈)】

      还有一隐藏bug,就是当摇杆位置布置得比较靠近屏幕边缘时,拖动会出现拖不尽的问题,可参考 《Unity3D学习日记(一)使用UGUI制作虚拟摇杆

      二 摇杆事件连接

      在摇杆UI制作完毕后,接下来监听和派发摇杆的触摸事件,即监听接收到stickBg_的移动,且将移动量传递出去给目标对象。这就运用到unity的事件系统的API,如下:

    using UnityEngine;
    //using System.Collections;       //可去,使用 Coroutine 才需要此声明空间
    using UnityEngine.Events;         //事件派发Interface
    using UnityEngine.EventSystems;   //触摸begin drag end事件监听

      在操作摇杆时,主要监听“开始控制( OnBeginDrag )” 和 “控制中( OnDrag )” 以及 “控制结束( OnEndDrag )”三个事件。 接口定义在Event空间的  IDragHandler , IEndDragHandler , IBeginDragHandler类声明中,所以摇杆脚本类要继承这三者类,详细类分析参考官网的 IDragHandler ,

    public class FixedJoystickHandler : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler
    {
        public void OnBeginDrag(PointerEventData eventData) {}
        public void OnDrag(PointerEventData eventData) {}
        public void OnEndDrag(PointerEventData eventData) {}
    }

      然后,定义 UnityEvent 事件执行处理对象,负责接收目前操作摇杆的结果,简单说是当监听到上述事件时要对哪个对象执行什么功能,有点类似于UGUI的 Button ,在 Inspector 窗口有Click 事件项,用以设置当发生Click 时会调用执行哪个对象的哪个接口,

      

      官方UnityEvent使用例程如下:

    public class ExampleClass : MonoBehaviour
    {
        UnityEvent m_MyEvent;
    
        void Start()
        {
            if (m_MyEvent == null)
                m_MyEvent = new UnityEvent();
    
            m_MyEvent.AddListener(Ping);
        }
    
        void Update()
        {
            if (Input.anyKeyDown && m_MyEvent != null)
            {
                m_MyEvent.Invoke();
            }
        }
    
        void Ping()
        {
            Debug.Log("Ping");
        }
    }
        public Transform content;        //摇杆位移量“主人”
        public UnityEvent beginControl;  //开始控制器
        public UnityEvent endControl;    //结束控制器

      注意,stickBg_实时位移时需要将真实的位移量传递给UnityEvent类对象以计算移动距离和方向等,即需要传递参数,这就要运用到 UnityEvent扩展的模板类 UnityEvent<T0> ,官方例程如下:

    using UnityEngine;
    using UnityEngine.Events;
    
    [System.Serializable]
    public class MyIntEvent : UnityEvent<int>
    {
    }
    
    public class ExampleClass : MonoBehaviour
    {
        public MyIntEvent m_MyEvent;
    
        void Start()
        {
            if (m_MyEvent == null)
                m_MyEvent = new MyIntEvent();
    
            m_MyEvent.AddListener(Ping);
        }
    
        void Update()
        {
            if (Input.anyKeyDown && m_MyEvent != null)
            {
                m_MyEvent.Invoke(5);
            }
        }
    
        void Ping(int i)
        {
            Debug.Log("Ping" + i);
        }
    }

      可见T0表示传递的参数类型,且支持多个形参传递,如本节中传递的位移量是Vector3,

      

        [System.Serializable]  //这里加Serializable目的是使其正确显示在Inspector的事件属性窗口,如下图
        public class VirtualJoystickEvent : UnityEvent<Vector3> { }
       public VirtualJoystickEvent controlling;

      

      最后,就是实例化每个事件监听行为,如刚开始被拖动时执行 beginControl 事件等,

        public void OnBeginDrag(PointerEventData eventData)
        {
    
            this.beginControl.Invoke();
        }
        public void OnEndDrag(PointerEventData eventData)
        {
    
            this.endControl.Invoke();
        }

      还有一点要注意的,本节 controlling 传递的结果是摇杆的偏移量和方向,stick_ 是 stickBg_的子控件,初始本地坐标localPosition为(0,0,0),移动时 x > 0 表示向右,< 0 是向左,而 y > 0 向上。但是随着摇杆资源的尺寸大小不同,若使用实际的偏移量会因不同大小差异而变化,可能会造成影响。 故进行 归一化 运算,使其x和y值总是介于-11之间,从而实现不管各种摇杆资源尺寸改变,被控制的物体接收到的值都是一样的。

        public void OnDrag(PointerEventData eventData)
        {
    
            if (this.content)
            {
                this.controlling.Invoke(this.content.localPosition.normalized);
            }
        }

      到此事件监听和派发实现完毕,附上完整脚本的代码:

    //FixedJoystickHandler.cs
    using UnityEngine;
    using UnityEngine.Events;
    using UnityEngine.EventSystems;
    
    public class FixedJoystickHandler : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler
    {
    
        [System.Serializable]
        public class VirtualJoystickEvent : UnityEvent<Vector3> { }
    
        public Transform content;
        public UnityEvent beginControl;
        public VirtualJoystickEvent controlling;
        public UnityEvent endControl;
    
        public void OnBeginDrag(PointerEventData eventData)
        {
    
            this.beginControl.Invoke();
        }
    
        public void OnDrag(PointerEventData eventData)
        {
    
            if (this.content)
            {
                this.controlling.Invoke(this.content.localPosition.normalized);
            }
        }
    
        public void OnEndDrag(PointerEventData eventData)
        {
    
            this.endControl.Invoke();
        }
    }

      运行游戏前,先添加事件处理器controller,对事件接收对象进行赋值,如下图:

      

      

      本节将3d模型预制体对象p4赋值给各个control,响应的是Tank.cs中的对应接口方法。 当然控制目标也可以为人物角色,平时站在原地不移动的动画播放状态为闲置中Idle,当操作摇杆时通知该角色 Animator 开始移动,让 Animator 的动作状态切换为走路动画。然后controling控制中的事件不断通知角色更新当前最新的方向和位移;同理在结束控制后通知角色的 Animator 停止移动,重新切换回原先Idle的动画播放。

      //Tank.cs
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
    
      public class Tank : MonoBehaviour {
        public List<AxleInfo> axleInfos;
        private float motor_ = 0;
        public float maxMotor;
        private float steer_ = 0;
        public float maxSteer;
        private float brake_ = 0;
        public float maxBrake = 100;
        private Transform wheelList;
    
        private void joyStickControl(float x, float y)
        {
            steer_ = x * maxSteer;
            motor_ = y * maxMotor;
        }
    
        private void playerCtrl()
        {
            //steer_ = Input.GetAxis("Horizontal") * maxSteer;
            //motor_ = Input.GetAxis("Vertical") * maxMotor;
            brake_ = 0;
            foreach (AxleInfo axleInfo in axleInfos)
            {
                if (axleInfo.leftWheel.rpm > 5 && motor_ < 0)  //正在前进时“down”
                    brake_ = maxBrake;
                else if (axleInfo.leftWheel.rpm < -5 && motor_ > 0) //正在后退时“up”
                    brake_ = maxBrake;
                continue;
            }
        }
    
        void Update () {
            playerCtrl();
            foreach(AxleInfo axleInfo in axleInfos)
            {
                if (axleInfo.motor)
                {
                    axleInfo.leftWheel.motorTorque = motor_;
                    axleInfo.rightWheel.motorTorque = motor_;
                }
                if(axleInfo.steering)
                {
                    axleInfo.leftWheel.steerAngle = steer_;
                    axleInfo.rightWheel.steerAngle = steer_;
                }
                axleInfo.leftWheel.brakeTorque = brake_;
                axleInfo.rightWheel.brakeTorque = brake_;
                if (axleInfos[1] != null && axleInfo == axleInfos[1])
                {
                    WheelRotation(axleInfos[1].leftWheel);
                }
            }
        }
    
        private void WheelRotation(WheelCollider collider)
        {
            if (wheelList == null)
                return;
            Vector3 pos;
            Quaternion rot;
            collider.GetWorldPose(out pos, out rot);
            foreach(Transform w in wheelList)
            {
                w.rotation = rot;
            }
        }
    
        private void Start()
        {
            wheelList = transform.Find("WheelList");
        }
    
        public void beginMove()
        {
            Debug.Log("start begin move!!");
        }
    
        public void doMove(Vector3 drag)
        {
            joyStickControl(drag.x, drag.y);
        }
    
        public void endMove()
        {
            joyStickControl(0,0);
        }
    }

      运行游戏,可看到坦克能通过摇杆和按键盘一样的操纵了~

      资料链接:

      《Unity3d使用UGUI开发原生虚拟摇杆

      《Unity3D游戏开发之使用EasyTouch虚拟摇杆控制人物移动

      《教你一步步实现一个虚拟摇杆

      三 摇杆表现强化

      占坑

      (摇杆半透明化,点击拖动的时候全体化)

      (摇杆一开始隐藏,触摸时才实际点位置显示,即浮动式虚拟摇杆)

  • 相关阅读:
    【贪心】【堆】Gym
    【并查集】Gym
    【拓扑排序】【bitset】Gym
    【递归】【线段树】【堆】AtCoder Regular Contest 080 E
    【二分图】【并查集】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem L. Canonical duel
    【动态规划】【滚动数组】【bitset】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem J. Terminal
    【二分】【字符串哈希】【二分图最大匹配】【最大流】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem I. Minimum Prefix
    【枚举】【最小表示法】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem F. Matrix Game
    【推导】【构造】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem E. Space Tourists
    【推导】【贪心】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem D. Clones and Treasures
  • 原文地址:https://www.cnblogs.com/pyqLoner/p/9911392.html
Copyright © 2011-2022 走看看