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虚拟摇杆控制人物移动

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

      三 摇杆表现强化

      占坑

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

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

  • 相关阅读:
    k8s的基本概念与基本功能
    STM32F030看门狗
    STM32F030低功耗
    STM32开发脱坑记
    ubuntu下安装wine并运行source insight
    Linux下使用Eclipse搭建ARM开发环境
    linux下的find文件查找命令与grep文件内容查找命令(转)
    STM32F030 BootLoader与应用程序的跳转设置
    MCU开发之MDK-ARM总结
    IIC协议总结
  • 原文地址:https://www.cnblogs.com/pyqLoner/p/9911392.html
Copyright © 2011-2022 走看看