unity3d 摇杆源码_unity3d摇杆 - CSDN
精华内容
参与话题
  • 最近的项目中需要开发虚拟摇杆功能,由于历史原因对于FingerGesture较为熟悉,故没有采用EasyTouch。 NGUI中的UIDragObject NGUI中有一个UIDragObject.cs组件,添加到GameObject,即可实现简单的拖拽动作。 其中的...

    引言

    最近的项目中需要开发虚拟摇杆功能,由于历史原因对于FingerGesture较为熟悉,故没有采用EasyTouch。

    NGUI中的UIDragObject

    NGUI中有一个UIDragObject.cs组件,添加到GameObject,即可实现简单的拖拽动作。


    其中的Target需要设置被拖拽的对象,代码注释如下:

    	/// <summary>
    	/// Target object that will be dragged.
    	/// </summary>
    
    	public Transform target;

    仅仅添加该组件是不够的,想要捕获鼠标事件还需要在对象上添加Box Collider组件。

    运行项目,就可以拖拽对象了。

    分析虚拟摇杆需求

    首先要虚拟摇杆分为两部分,中间一小部分为中间的滑块,周围一圈是背景层。(资源是EasyTouch里的)


    操作同时也分为两种情况:

    1.拖拽中间的滑块;

    2.直接点击周围的背景部分,滑块直接跳转到对应位置。

    返回什么?

    操作虚拟摇杆只需返回一个Vector3向量即可(从原点指向滑块被拖拽位置,其实是一个二维向量,z值没有用到。)

    代码实现

    对于滑块部分的操作可以借鉴NGUI中的UIDragObject的实现,只需保留最基本的功能,只用到了NGUI中的OnPress和OnDrag事件。

    滑块部分代码如下:

    using UnityEngine;
    using System.Collections;
    
    public class JoyStickDragObject : MonoBehaviour
    {
        public Transform target;
    
        Vector3 mTargetPos;                                         // 目标当前位置
        Vector3 mLastPos;
    
        int mTouchID = 0;
    
        bool mStarted = false;
        bool mPressed = false;
    
        [SerializeField]
        protected Vector3 scale = new Vector3(1f, 1f, 0f);
        protected Vector3 originPos = Vector3.zero;                 
        protected Vector3 offsetFromOrigin = Vector3.zero;          // 原点到拖拽位置的向量
    
        public Vector3 OffsetFromOrigin
        {
            set { offsetFromOrigin = value; }
            get { return offsetFromOrigin; }
        }
    
    	// Use this for initialization
    	void Start () {
    	}
    	
    	// Update is called once per frame
    	void Update () {
    	    
    	}
    
        void OnPress(bool pressed)
        {
            if (enabled && NGUITools.GetActive(gameObject) && target != null)
            {
                if (pressed)
                {
                    if (!mPressed)
                    {
                        mTouchID = UICamera.currentTouchID;
                        mPressed = true;
                        mStarted = false;
                        CancelMovement();
                    }
                }
                else if (mPressed && mTouchID == UICamera.currentTouchID)
                {
                    mPressed = false;
                    target.position = Vector3.zero;
                }
            }
    
        }
        void OnDrag(Vector2 delta)
        {
            Ray ray = UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos);
            float dist = 0f;
    
            Vector3 currentPos = ray.GetPoint(dist);
            ///< 更新当前坐标到上一时刻坐标的向量
            Vector3 offset = currentPos - mLastPos;
            ///< 更新当前坐标到原点的向量
            UpdateVector3FromOrigin(currentPos);
            mLastPos = currentPos;
    
            if (!mStarted)
            {
                mStarted = true;
                offset = Vector3.zero;
            }
    
            Debug.LogWarning("offset: " + offset + " OffsetFromOrigin: " + OffsetFromOrigin);
            Move(offset);
        }
    
        void Move(Vector3 moveDelta)
        {
            mTargetPos += moveDelta;
            target.position = mTargetPos;
        }
    
        void CancelMovement()
        {
            if (target != null)
            {      
                Vector3 pos = target.localPosition;
                target.localPosition = pos;
            }
            mTargetPos = (target != null) ? target.position : Vector3.zero;
        }
    
        ///< 更新当前坐标到原点的向量
        void UpdateVector3FromOrigin(Vector3 pos)
        {
            OffsetFromOrigin = pos - originPos;
        }
    }
    
    背景层的处理与滑块部分略有不同,主要在于点击背景层,滑块需要直接移动到点击位置

    背景层代码如下:

    using UnityEngine;
    using System.Collections;
    
    public class JoyStickBackground : MonoBehaviour
    {
    
        public Transform thumb;
    
        private bool isReset = true;
        [SerializeField]
        protected Vector3 originPos = Vector3.zero;                 // 原点
        protected Vector3 offsetFromOrigin = Vector3.zero;          // 原点到拖拽位置的向量
        public Vector3 OffsetFromOrigin
        {
            set { offsetFromOrigin = value; }
            get { return offsetFromOrigin; }
        }
        // Use this for initialization
        void Start()
        {
    
        }
    
        // Update is called once per frame
        void Update()
        {
        }
    
        void OnPress(bool pressed)
        {
            if (pressed)
            {
                Ray ray = UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos);
                float dist = 0f;
                Vector3 currentPos = ray.GetPoint(dist);
    
                thumb.position = currentPos;
                isReset = false;
                ///< 更新当前坐标到原点的向量
                UpdateVector3FromOrigin(currentPos);
            }
            else
            {
                thumb.position = Vector3.zero;
                isReset = true;
            }
        }
    
        void OnDrag(Vector2 delta)
        {
            Ray ray = UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos);
            float dist = 0f;
    
            Vector3 currentPos = ray.GetPoint(dist);
            thumb.position = currentPos;
    
        }
    
        ///< 更新当前坐标到原点的向量
        void UpdateVector3FromOrigin(Vector3 pos)
        {
            OffsetFromOrigin = pos - originPos;
        }
    }
    
    效果图:

    拖拽滑块:

    点击背景:


    展开全文
  • 如今手机游戏玩法多种多样,尤其使用虚拟摇杆进行格斗类游戏开发或者是MMORPG成为了主流的开发方式,可能不少人都会为了制作一个完善的虚拟摇杆感到烦恼,一次又不少人选择使用插件来制作虚拟摇杆。  Momo大神在...

      如今手机游戏玩法多种多样,尤其使用虚拟摇杆进行格斗类游戏开发或者是MMORPG成为了主流的开发方式,可能不少人都会为了制作一个完善的虚拟摇杆感到烦恼,一次又不少人选择使用插件来制作虚拟摇杆。

      Momo大神在不久前才写了一篇用UGUI制作虚拟摇杆的,有兴趣的同学可以到这里看看原文,原文地址:http://www.xuanyusong.com/archives/3924

      今天刚好公司向里面需要用到这个功能,因此我也研究了一下Momo这篇文章,发现里面有一个算是bug的问题因此我就在Momo大神的代码上进行修改,修复了这个问题而且也添加一下功能方便大家去拓展和使用。

      先来看看我和我的小伙伴发现的bug吧。

      下图两张图片是Momo大神的写的UGUI虚拟摇杆的小Demo,我们来看看其效果吧:

       图片一:

       

       图片二:

       

    比较一下两张图片你会发现有一个致命问题小球的偏移量出问题了,这种情况只出现在虚拟摇杆距离屏幕边距较近的时候会出现这个问题,远离边距的时候就不会出现这个问题了,来看看远离边距时候的效果吧。如下图所示:


    这样的限制对于摇杆功能来说并不会有太大的影响,但是UI的设计上还是会出现一些问题和麻烦的。因此我今天就是为了修复这个问题而对UGUI虚拟摇杆进行了研究,花了一点时间进行了bug的修复,然后拓写了它的功能,方便我们来获取摇杆的偏移量来控制人物移动。

      先来看看我实现的效果吧,废话不多说上图!

      

      上面的图片可以看出来,虚拟摇杆已经贴着屏幕边框了,但是偏移量并没有减少。

      我也不说多少神马了因为实在是太简单了,我就直接上代码吧

    using UnityEngine;
    using System.Collections;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;
     
    public class ScrollCircle :ScrollRect 
    {
        public float recoveryTime = 0.1f;
    	protected float mRadius;
        protected bool isOnEndDrag = false;
        protected Vector3 offsetVector3 = Vector3.zero;
    
    	void Start()
    	{
            inertia = false;
            movementType = MovementType.Unrestricted;
            //计算摇杆块的半径
            mRadius = (transform as RectTransform).sizeDelta.x * 0.5f;
    	}
    
        public override void OnScroll(PointerEventData data)
        {
    
        }
    	public override void OnDrag (PointerEventData eventData)
    	{
    		base.OnDrag (eventData);
            isOnEndDrag = false;
    		var contentPostion = this.content.anchoredPosition;
    		if (contentPostion.magnitude > mRadius){
    			contentPostion = contentPostion.normalized * mRadius ;
    		    SetContentAnchoredPosition(contentPostion);
    		}
    	}
    
        public override void OnEndDrag(PointerEventData eventData)
        {
            base.OnEndDrag(eventData);
            if (!isOnEndDrag)
                isOnEndDrag = true;
        }
    
        void Update()
        {
            UpdateContent();
        }
    
        /// <summary>
        /// 摇杆小球复位
        /// </summary>
        public void UpdateContent()
        {
            if (isOnEndDrag)
            {
                if (content.localPosition == Vector3.zero)
                    isOnEndDrag = false;
                float x = Mathf.Lerp(content.localPosition.x, 0.0f, recoveryTime);
                float y = Mathf.Lerp(content.localPosition.y, 0.0f, recoveryTime);
                content.localPosition = new Vector3(x, y, content.localPosition.z);
            }
            CalculateOffset();
        }
        /// <summary>
        /// 计算偏移量
        /// </summary>
        private void CalculateOffset()
        {
            offsetVector3 = content.localPosition / mRadius;
        }
        /// <summary>
        /// 获取偏移量大小
        /// 偏移量范围是[-1,1]
        /// </summary>
        /// <returns></returns>
        public Vector3 GetOffsetVector3()
        {
            return offsetVector3;
        }
    }

    这里要注意一点,我在start函数里面修改了movementType,如果没有修改movementType参数的同学是没办法实现这个功能的~切记切记!!

    最后上一张图片,红框的位置是要特别注意的地方!


    今天写了两篇文章好累啊,我会尽量把自己学到研究到的东西都写出来,可能会很乱很杂大家别介意哈....哈哈哈哈~~

                                                                                                                                                                                                                                                                                               -----Begonia

    展开全文
  • 欢迎来到unity学习、unity培训、unity企业培训教育专区,这里有很多U3D资源、U3D培训视频、U3D教程、U3D常见问题、U3D项目源码,【狗刨学习网】unity极致学院,致力于打造业内unity3d培训、学习第一品牌。...


       官方摇杆脚本只能完成一个摇杆,要实现双摇杆比较费劲
      现在我吧我废了一通宵的结果共享给大家
      1.双摇杆是用来控制第一人称控制器的,但第一人称控制器右js和c#有的地方不能通用,所以首先要将所有将本变为c#脚本
     
      2.建立Joystick.cs脚本,我也在代码中加了注释方便大家理解
      3.将GUI摇杆纹理放入脚本,并将脚本关联与第一人称控制器
      4.修改第一人称控制器脚本

     /**
      * *JoyStick.cs
      */
      using UnityEngine;
      using System.Collections;
      //创建枚举类型,可在加载脚本的实体上选择是左摇杆还是右摇杆
      public enum JoyStickType
      {
      leftJoyStick,
      rightJoyStick
      }
      //脚本JoyStick类
      public class JoyStick : MonoBehaviour
      {
      public JoyStickType joyStickType; //摇杆类型,左摇杆还是右摇杆
      private Vector2 centerPos; //摇杆的中心点位置,屏幕坐标
      public GUITexture centerBall; //球型摇杆
      public float joyStickRadius; //摇杆移动范围的半径
      private Vector2 position; //摇杆要传递出去的参数,就靠他控制移动旋转
      private int lastFingerID = -1; //最后一次触摸的手指id
      private bool centerBallMoving = false; //球型摇杆移动开关
      //加载时运行方法
      void Start ()
      {
      //为了让摇杆适配不同分辨率屏幕,设置大小和坐标
      centerBall.transform.localScale = new Vector3( centerBall.pixelInset.size.x / Screen.width, centerBall.pixelInset.size.y / Screen.height,0);
      //因为GUI纹理锚点都是左下角0,0 所以为了让两边摇杆对称摇杆坐标向-x方向移动半个纹理宽的屏幕距离(屏幕距离通过 纹理宽:屏幕分辨率宽 获得)
      centerBall.transform.position = new Vector3 (centerBall.transform.position.x - centerBall.pixelInset.size.x/2/Screen.width,
      centerBall.transform.position.y,0);
      //将摇杆坐标赋给作为摇杆的中心,以后用来复位摇杆用
      centerPos = centerBall.transform.position;
      }
      //每帧运行方法
      void Update ()
      {
      //调用摇杆方法
      JoyStickController();
      }
      //摇杆方法
      void JoyStickController()
      {
      int count = Input.touchCount; //获取触摸点的数量
      for (int i = 0; i count; i++) //逐个分析触摸点的操作
      {
      Touch touch = Input.GetTouch(i); //获取当前处理的触摸点
      //将当前的触摸坐标转换为屏幕坐标
      Vector2 currentTouchPos = new Vector2(touch.position.x/Screen.width - centerBall.pixelInset.size.x/2/Screen.width, touch.position.y/Screen.height);
      Vector2 temp = currentTouchPos - centerPos; //得到方向向量temp(触摸的位置和摇杆的坐标差)
      if (centerBall.HitTest(touch.position)) //判断是否触摸点在要干范围之内
      {
      if (temp.magnitude joyStickRadius) //如果方向向量temp的长度没有超出摇杆的半径,temp.magnitude为求坐标差的距离,及两点间的距离
      {
      lastFingerID = touch.fingerId; //记录该触摸的id
      centerBallMoving = true; //摇杆移动开关打开
      }
      }
      //若中心球移动开关打开,摇杆中心球就会跟随手指移动。但需要加以限制,当手指触摸没有超出摇杆的圆形区域时,中心球完全跟随手指触摸;
      //当手指触摸超出圆形区域时,中心球处于触摸位置和摇杆中心点所形成的方向上并且不能超出半径
      if (touch.fingerId == lastFingerID centerBallMoving)
      {
      if (temp.magnitude joyStickRadius) //如果手指触摸没有超出摇杆的圆形区域,即摇杆半径,摇杆中心球的位置一直跟随手指
      {
      centerBall.transform.position = new Vector3 (currentTouchPos.x,currentTouchPos.y,0); //设置摇杆的坐标等于触点的坐标
      }
      else //超出半径
      {
      Vector2 temp2 = temp; //定义临时变量temp2
      temp2.Normalize(); //将temp2标准化
      //设置摇杆坐标位置不超过半径
      centerBall.transform.position = new Vector3((joyStickRadius * temp2 + centerPos).x, (joyStickRadius * temp2 + centerPos).y, 0);
      }
      if (temp.x = 0)
      {
      //一下为示例代码:控制旋转方向,主要利用Vector2.Angle(temp, new Vector2(0, 5))得到角度并利用
      //initialization_script.current_player_tank_script.BodyRotation(Vector2.Angle(temp, new Vector2(0, 5)));
      }
      if (temp.x 0)
      {
      //一下为示例代码:控制旋转方向,主要利用Vector2.Angle(temp, new Vector2(0, 5))得到角度并利用
      //initialization_script.current_player_tank_script.BodyRotation(-1 * Vector2.Angle(temp1, new Vector2(0, 5)));
      }
      //控制移动的函数或者控制开火的函数,假设左摇杆控制移动,右摇旋转
      switch(joyStickType)
      {
      case JoyStickType.leftJoyStick:
      position = temp*100; //移动需坐标
      break;
      case JoyStickType.rightJoyStick:
      position = temp*10; //旋转需坐标
      break;
      }
      //当释放触摸的时候中心球位置重置
      if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
      {
      centerBall.transform.position = new Vector3(centerPos.x, centerPos.y, 0); //摇杆复位
      temp = new Vector2(0,0); //距离差为0
      position = temp; //赋值0给需要坐标已停止动作
      centerBallMoving = false; //设置不能移动摇杆
      lastFingerID = -1; //清楚本次手指触摸id
      }
      }
      }
      }
      //获取传递坐标方法
      public Vector2 getPositions()
      {
      return position;
      }
      }

     



    展开全文
  • Unity 虚拟摇杆Unity内置插件实现

    千次阅读 2017-10-14 16:50:38
    今天我们来学习如何利用Unity3d自带的摇杆组件,实现利用摇杆来控制角色的移动。 今天所用到的例子参考了网上的一个教程(http://www.unitymanual.com/thread-5189-1-1.html),原文用javascript实现了。我这里用...

    手机游戏,尤其是mmo类的有,都少不了虚拟游戏摇杆。今天我们来学习如何利用Unity3d自带的摇杆组件,实现利用摇杆来控制角色的移动。

    今天所用到的例子参考了网上的一个教程(http://www.unitymanual.com/thread-5189-1-1.html),原文用javascript实现了。我这里用c#来实现

    • 具体步骤
    1.导入Character Controller组件,主要是用到其中的角色模型
    2.导入Standard Assets(Mobile)组件,里面包含了Joystick组件
    3.创建一个Gui Texture,命名为Joystick。设置它的材料Texture 为 Joystick Thumb
      设置它的Pixel Inset .x,y Width,Hight
    4.为Joystick添加脚本Joystick.cs
    5.创建一个地形。不会的同学,可以直接引用到源码中的
    6.将角色控制器中的Bip001加到Player上,初始化状态为idle
    7.为Main Camera 添加 Smooth Follow脚本(角色控制器脚本中包含),使得摄像机可以跟随人物移动
    8.为Main Camera添加自定义脚本 PlayerController.cs脚本
    [csharp] view plain copy
    1. using UnityEngine;  
    2. using System.Collections;  
    3.   
    4.   
    5. public class PlayerController : MonoBehaviour  
    6. {  
    7.     //角色  
    8.     public GameObject player;  
    9.     //摇杆  
    10.     public Joystick joystick;  
    11.   
    12.   
    13.     void Update()  
    14.     {  
    15.         //摇杆没有被触发  
    16.         if (joystick.tapCount <= 0)  
    17.         {  
    18.             return;  
    19.         }  
    20.         //获取摇杆偏移  
    21.         var joyPositionX = joystick.position.x;  
    22.         var joyPositionY = joystick.position.y;  
    23.   
    24.   
    25.         if (joyPositionY != 0 || joyPositionX != 0)  
    26.         {  
    27.             //设置角色的朝向(朝向当前坐标+摇杆偏移量)  
    28.             player.transform.LookAt(new Vector3(player.transform.position.x + joyPositionX, player.transform.position.y, player.transform.position.z + joyPositionY));  
    29.             //移动玩家的位置(按朝向位置移动)  
    30.             player.transform.Translate(Vector3.forward * Time.deltaTime * 5);  
    31.             //播放奔跑动画  
    32.             player.animation.Play("run");  
    33.         }  
    34.         else  
    35.         {  
    36.             //播放待机动画  
    37.             player.animation.Play("idle");  
    38.         }  
    39.     }  
    40. }  
    • 最终的效果图:


    Joystick.cs
    代码比较多,就不详细列出。

    主要思路:记录触摸的位置,记录相关的偏移量。与此相关联的是GUI Texture组件。

    • 总结
    官方自带的摇杆组件比较简单,可以基本实现简单的功能。但是,也存在很多的局限,比如更换外观比较繁琐,无法自适应屏幕的大小确定相对位置,基于GUI layer。为此,下节我们将介绍一个功能更加强大的摇杆组件Easy Touch 3,敬请期待!有任何问题欢迎一起探讨ken@iamcoding.com

    • 源码地址

    http://pan.baidu.com/s/1GMnyQ




       (本文可以随意分享,请注明出处http://blog.csdn.net/janeky/article/details/17326711,谢谢!)

                            (本文可以随意分享,请注明出处http://blog.csdn.net/janeky/article/details/17326711,谢谢!)
    展开全文
  • 在新版本的 Unity中 有一个叫做 CrossPlatformInputManager 的东西,用过自带摇杆和按钮的朋友们应该都知道哈 应为 源码中的 CrossPlatformInputManager 用起来不是很顺手,他好像是方形的,但是他能很好的和 ...
  • unity实现3d摇杆 结合leapmotion控制实现VR场景移动Created by miccall (转载请注明出处 miccall.tech )3D UI设想 解决VR场景移动 开发vr很恼火的就是场景的移动 真实地方本来就小,如果vr里面的场景也小,那还玩...
  • UGUI实现Unity虚拟摇杆

    2017-10-18 13:37:10
    基于UGUI实现虚拟遥杆
  • Unity3D 制作按钮开关,旋钮开关,摇杆手柄 插件包。
  • Unity3D RPG《海战》手机游戏完整源码 C#脚本 《海战》改编自海洋老师的海战游戏。PureMvc框架做的UI,里面有自动过剧情、自动攻击,支持手指触摸、虚拟摇杆、鼠标点击、按键移动。里面用了AI模拟群体行为、关卡...
  • Unity3D NGUI屏幕摇杆

    千次阅读 2015-09-04 19:59:46
    NGUI下增加一个Empty对象,重命名为Joystick。在它本身上加载UIAnchor脚本,设置好它的大小。Joystick的子集中增加俩个新的Empty,命名为Background,Thumb。所有设置如下所示。 ...Thumb上增加box collier,为...
  • unity3d之3D贪吃蛇

    千次阅读 2017-11-28 10:20:09
    3D贪吃蛇  大二在校生,无兴趣于安卓,爱好游戏开发,自己做了个u3d贪吃蛇,分享下源码。  首先是蛇,我用的是链表结构,蛇头带动蛇身,蛇身是一个链表,头部碰撞到食物,食物消失,蛇身体加长一节。  食物...
  • 下面附上源码,因为有注释我就不多说什么了 源码: public&amp;amp;nbsp;class&amp;amp;nbsp;CoronaScripts&amp;amp;nbsp;:&amp;amp;nbsp;MonoBehaviour&amp;amp;nbsp;{ &amp;amp;...
  • Unity3D+VR的实现

    千次阅读 2019-04-26 12:15:26
    针对CardBoard一类的眼镜用Unity3D开发VR内容。 1、创建VRCamera:使用Dive插件,从DiveUnityDemo提取摄像机和有关的脚本并export出来。 2、对象选择:视选模式:从眼睛发射一条射线,选择看到的对象。手柄模式:...
  • 今天我们来学习如何利用Unity3d自带的摇杆组件,实现利用摇杆来控制角色的移动。 今天所用到的例子参考了网上的一个教程(http://www.unitymanual.com/thread-5189-1-1.html),原文用JavaScript实现了。我这里用...
  • 学习Unity脚本推荐:Unity3D官网索引 Joystick在手游开发中非常常见,也就是在手机屏幕上的虚拟操纵杆,但是Unity3D自带的Joystick贴图比较原始,所以经常有使用自定义贴图的需求。 下面就来演示一下如何...
  • Unity3D 工程机械以及常见机构铰链,关节绑定 插件包。
  • Unity3d+moba+UGUI摇杆

    千次阅读 2018-04-13 10:49:59
    1 功能描述使用UGUI制作摇杆摇杆的初始位置是可变的,当按下同时改变Bg位置和thumb位置,当松手时Bg,thumb返回初始原位。主要函数:ScreenPointToLocalPointInRectangle,Vector3.Distance。2 详细设计Bg为摇杆的...
  • Unity 5.X 3D游戏开发技术详解与典型案例对Unity 3D集成开发环境界面、脚本的编写和众多高级特效的实现进行了详细介绍,内容深入浅出,是一本适合不同需求、不同开发水平读者的技术宝典。 全书共分16章。第1章主要...
  • 本书对Unity 3D集成开发环境界面、脚本的编写和众多高级特效的实现进行了详细介绍,内容深入浅出,是一本适合不同需求、不同开发水平读者的技术宝典。 全书共分16章。第1章主要介绍了Unity 3D的诞生、特点、开发环境...
  • 都是网上找的连七八糟的资料了,整理好分享的,有学习资料,视频,源码,插件……等等 东西比较多,不是所有的都是你需要的,可以按ctrl+F来搜索你要的东西,如果有广告,不用理会,关掉就...*文件名称:CG_Unity3D...
1 2 3 4 5 6
收藏数 107
精华内容 42
关键字:

unity3d 摇杆源码