• 各位朋友,大家晚上好, 我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。今天我们来做一个高端大气上档次的...那么,我们今天呢,就以《愤怒的小鸟》游戏中的小鸟发射器——弹弓为例,

            各位朋友,大家晚上好, 我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。今天我们来做一个高端大气上档次的东西。我相信大家都玩过一款叫做《愤怒的小鸟》的游戏,这款游戏之所以能够受到玩家的喜爱,是因为这是一款物理游戏,在游戏中处处充满了物理。我们熟悉的抛物线、引力等物理元素都在这款游戏中得到了体现。那么《愤怒的小鸟》这款游戏使用的正是Unity引擎,所以我们在游戏中看到了各种各种有趣的物理现象。那么,我们今天呢,就以《愤怒的小鸟》游戏中的小鸟发射器——弹弓为例,来讲述在Unity中如何实现弹性效果。

            首先,我们来引入一个新的概念——LineRenderer。在Unity3D中LineRenderer被称为线性渲染器。通过这个组件,我们可以做一些比较有创意的东西,比如在游戏中绘制线段、制作激光特效、武器拖尾效果等。那么今天呢,我们是用LineRenderer来构造弹弓两边的绳子,这个绳子是具有弹性的,在受力结束后可以恢复到原来的状态。我们首先创建一个如下图的简单场景:


           在上面的场景中,两边的柱子作为固定绳子的物体,小球在两根绳子中间。我们希望实现的是:

    当用户按下鼠标左键并移动鼠标的时候,小球和绳子都将跟着移动,当松开鼠标左键的时候,小球以一定的角度和一定的力发射出去。我们今天着重来讲绳子的实现。首先我们创建一个空的GameObject将坐标值设为原点,命名为RopeL。接下来我们通过Component->Effects->Line Renderer为添加一个线性渲染器组件。如图:


          我们设定好上述参数后,就可以开始编写脚本了,这里两根绳子是对称的:

    using UnityEngine;
    using System.Collections;
    
    public class Ball : MonoBehaviour {
    
    	//鼠标位置
    	private Vector3 MousePos;
    
    	//左侧LineRenderer
    	private LineRenderer LineL;
    	//右侧LineRenderer
    	private LineRenderer LineR;
    	
    	void Start ()
    	{
    		//获取LineRenderer
    		LineL=GameObject.Find("Shootor").transform.FindChild("RopeL").
    			transform.GetComponent<LineRenderer>();
    		LineR=GameObject.Find("Shootor").transform.FindChild("RopeR").
    			transform.GetComponent<LineRenderer>();
    	}
    
    	void Update () 
    	{
    		if(Input.GetMouseButton(0))
    		{
    		   //获取鼠标位置
    			MousePos=Camera.main.ScreenToViewportPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y,-2F));
    		   //设置小球的位置
    		   transform.position=MousePos;
    		   //重新设置LineRenderer的位置
    		   LineL.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));
    		   LineR.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));
    	    }
    		if(Input.GetMouseButtonUp(0))
    		{
    			//获取鼠标位置
    			MousePos=Camera.main.ScreenToViewportPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y,-2F));
    			//设置小球的位置
    			transform.position=MousePos;
    			//重新设置LineRenderer的位置
    			LineL.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));
    			LineR.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));
    
    			//计算小球合力方向
    			Vector3 Vec3L=new Vector3(-2F-MousePos.x,1.8F-MousePos.y,0F-MousePos.z);
    			Vector3 Vec3R=new Vector3(2F-MousePos.x,1.8F-MousePos.y,0F-MousePos.z);
    			Vector3 Dir=(Vec3L+Vec3R).normalized;
    			//获取刚体结构
    			transform.GetComponent<Rigidbody>().useGravity=true;
    			transform.GetComponent<Rigidbody>().AddForce(Dir*10F,ForceMode.Impulse);
    			//恢复LineRenderer
    			LineL.SetPosition(0,new Vector3(0F,1.8F,0F));
    			LineR.SetPosition(0,new Vector3(0F,1.8F,0F));
    		}
    	} 
    }
            我们给小球加上刚体后,绑定这段脚本就可以了。我们需要注意的问题有:

            1、这里的力是给定的大小,我们可以根据绳子被拉长的距离使用一个弹性系数来计算力的大小。

            2、计算合力的方向时,我们首先计算两边绳子的向量值,然后将这两个向量想加即可得到我们的合力方向。

            好了,我们一起来看今天的效果演示吧!


            今天被宿舍里玩游戏的这些人搞得心情很不好,或许注定我们不是同一个世界的人吧,大学三年,渐渐地对专业、班级没有了归属感,我宁愿在我的小圈子里努力做好自己的事情。二十几岁的人,每天就指望着游戏来满足你的成就感,这样的生活我实在不喜欢。人还是应该努力一点的,不为什么,就为了我们父母、亲人、爱人。大学里知道自己喜欢什么,可以做什么比上课学到了什么更重要。你不努力,永远有别人比你更努力,生活总得有点向往吧。唉,算了,发什么牢骚啊,做好自己就行了。该睡觉了,大家晚安吧!

       每日箴言:成长,便是接受一个不完美的自己和一个不理想的自己,也接受这个世界的不完美和不理想。——张小娴



        喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei。
        转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/24941939


    展开全文
  • 我相信大家都玩过一款叫做《愤怒的小鸟》的游戏,这款游戏之所以能够受到玩家的喜爱,是因为这是一款物理游戏,在...那么《愤怒的小鸟》这款游戏使用的正是Unity引擎,所以我们在游戏中看到了各种各种有趣的物理现象。


    下面我们开始今天的游戏开发技能。 我们学习目标:让初学者可以更快速的掌握技术,自行制作修改素材,可以独立完成2D、3D小规模游戏及网页游戏开发。

           今天我们来做一个高端大气上档次的东西。我相信大家都玩过一款叫做《愤怒的小鸟》的游戏,这款游戏之所以能够受到玩家的喜爱,是因为这是一款物理游戏,在游戏中处处充满了物理。我们熟悉的抛物线、引力等物理元素都在这款游戏中得到了体现。那么《愤怒的小鸟》这款游戏使用的正是Unity引擎,所以我们在游戏中看到了各种各种有趣的物理现象。那么,我们今天呢,就以《愤怒的小鸟》游戏中的小鸟发射器——弹弓为例,来讲述在Unity中如何实现弹性效果。

            首先,我们来引入一个新的概念——LineRenderer。在Unity3D中LineRenderer被称为线性渲染器。通过这个组件,我们可以做一些比较有创意的东西,比如在游戏中绘制线段、制作激光特效、武器拖尾效果等。那么今天呢,我们是用LineRenderer来构造弹弓两边的绳子,这个绳子是具有弹性的,在受力结束后可以恢复到原来的状态。我们首先创建一个如下图的简单场景:

           在上面的场景中,两边的柱子作为固定绳子的物体,小球在两根绳子中间。我们希望实现的是:

    当用户按下鼠标左键并移动鼠标的时候,小球和绳子都将跟着移动,当松开鼠标左键的时候,小球以一定的角度和一定的力发射出去。我们今天着重来讲绳子的实现。首先我们创建一个空的GameObject将坐标值设为原点,命名为RopeL。接下来我们通过Component->Effects->Line Renderer为添加一个线性渲染器组件。如图:

          我们设定好上述参数后,就可以开始编写脚本了,这里两根绳子是对称的:

    1. using UnityEngine;  
    2. using System.Collections;  
    3.   
    4. public class Ball : MonoBehaviour {  
    5.   
    6.     //鼠标位置  
    7.     private Vector3 MousePos;  
    8.   
    9.     //左侧LineRenderer  
    10.     private LineRenderer LineL;  
    11.     //右侧LineRenderer  
    12.     private LineRenderer LineR;  
    13.       
    14.     void Start ()  
    15.     {  
    16.         //获取LineRenderer  
    17.         LineL=GameObject.Find("Shootor").transform.FindChild("RopeL").  
    18.             transform.GetComponent<LineRenderer>();  
    19.         LineR=GameObject.Find("Shootor").transform.FindChild("RopeR").  
    20.             transform.GetComponent<LineRenderer>();  
    21.     }  
    22.   
    23.     void Update ()   
    24.     {  
    25.         if(Input.GetMouseButton(0))  
    26.         {  
    27.            //获取鼠标位置  
    28.             MousePos=Camera.main.ScreenToViewportPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y,-2F));  
    29.            //设置小球的位置  
    30.            transform.position=MousePos;  
    31.            //重新设置LineRenderer的位置  
    32.            LineL.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));  
    33.            LineR.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));  
    34.         }  
    35.         if(Input.GetMouseButtonUp(0))  
    36.         {  
    37.             //获取鼠标位置  
    38.             MousePos=Camera.main.ScreenToViewportPoint(new Vector3(Input.mousePosition.x,Input.mousePosition.y,-2F));  
    39.             //设置小球的位置  
    40.             transform.position=MousePos;  
    41.             //重新设置LineRenderer的位置  
    42.             LineL.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));  
    43.             LineR.SetPosition(0,new Vector3(MousePos.x,MousePos.y,MousePos.z-0.5F));  
    44.   
    45.             //计算小球合力方向  
    46.             Vector3 Vec3L=new Vector3(-2F-MousePos.x,1.8F-MousePos.y,0F-MousePos.z);  
    47.             Vector3 Vec3R=new Vector3(2F-MousePos.x,1.8F-MousePos.y,0F-MousePos.z);  
    48.             Vector3 Dir=(Vec3L+Vec3R).normalized;  
    49.             //获取刚体结构  
    50.             transform.GetComponent<Rigidbody>().useGravity=true;  
    51.             transform.GetComponent<Rigidbody>().AddForce(Dir*10F,ForceMode.Impulse);  
    52.             //恢复LineRenderer  
    53.             LineL.SetPosition(0,new Vector3(0F,1.8F,0F));  
    54.             LineR.SetPosition(0,new Vector3(0F,1.8F,0F));  
    55.         }  
    56.     }   
    57. }  
            我们给小球加上刚体后,绑定这段脚本就可以了。我们需要注意的问题有:

           1、这里的力是给定的大小,我们可以根据绳子被拉长的距离使用一个弹性系数来计算力的大小。



           2、计算合力的方向时,我们首先计算两边绳子的向量值,然后将这两个向量想加即可得到我们的合力方向。

            好了,我们一起来看今天的效果演示吧!

    展开全文
  • Unity3D游戏-愤怒的小鸟游戏源码和教程(一) Unity愤怒的小鸟游戏教程 本文提供全流程,中文翻译。Chinar坚持将简单的生活方式,带给世人! Spring Joint 2D : 是Unity提供的一个弹簧关节组件,可通过AddComponent...

    Unity愤怒的小鸟游戏教程


    本文提供全流程,中文翻译。

    Chinar坚持将简单的生活方式,带给世人!

    (拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例)



    AngryEva游戏效果:

    这里写图片描述



    1

    Spring Joint 2D —— 弹簧关节



    Spring Joint 2D : 是Unity提供的一个弹簧关节组件,可通过AddComponent添加

    Unity会自动模拟弹簧的物理效果,来执行函数,使物体具备同样的弹簧效果


    注意:Spring Joint 2D 组件,需要指定链接一个的Rigidbody组件:

    这个物体是所需固定位置的物体(且物体上必须有Rididbody组件)

    在Spring Joint 2D组件下的 Connected Rigid Body 属性中添加

    举个栗子黑白88

    这里写图片描述
    这里写图片描述


    2

    CameraFollow —— 相机跟随,插值


    Mathf.Clamp(posX, 0, 18)

    数学函数.范围(限定目标,0,到 18之间)

    举个栗子黑白88

    /// <summary>
    /// 相机在指定范围跟随
    /// </summary>
    private void CameraFollow()
    {
        //记录Eva的横坐标
        float posX = transform.position.x;
    
        //相机当前位置 = 插值(当前相机位置,目标位置(Mathf.Clamp-限定范围:(限定posX,018之间))
        Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, new Vector3(Mathf.Clamp(posX, 0, 18), Camera.main.transform.position.y, Camera.main.transform.position.z), SmoothFlo * Time.deltaTime);
    
    }

    这里写图片描述


    3

    RelativeVelocity —— 相对速度(- - 检测受伤的方式)


    collision.relativeVelocity.magnitude > MaxSpeed

    碰撞物体的.相对速度.大小 > 最大速度

    举个栗子黑白88

    /// <summary>
    /// 触发检测,检测是否达到受伤条件
    /// </summary>
    /// <param name="collision"></param>
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Eva") //需在外部设置标签,给Eva物体设置Tag为Eva
        {
            AudioPlay(EvaHurtClip);                         //播放受伤音效
            collision.transform.GetComponent<Eva>().Hurt(); //受伤
        }
    
        if (collision.relativeVelocity.magnitude > MaxSpeed) //如果相对速度.大小>最大速度
        {
            Dead(); //调用死亡消除方法
        }
        else if (collision.relativeVelocity.magnitude > MinSpeed && collision.relativeVelocity.magnitude < MaxSpeed) //相对速度在4-8之间
        {
            Render.sprite = HurtSprite; //更换图片,受伤
            AudioPlay(HurtClip);
        }
    }

    4

    Eva —— 脚本


    举个栗子黑白88

    using UnityEngine;
    using System.Collections;
    using UnityEngine.EventSystems;
    
    
    /// <summary>
    /// Eva类脚本
    /// </summary>
    public class Eva : MonoBehaviour
    {
        public                   float          MaxDis    = 1.8f;  //Eva可拖动最远距离
        public                   float          SmoothFlo = 3;     //平滑度
        [HideInInspector] public SpringJoint2D  EvaSP;             //弹簧链接组件
        protected                Rigidbody2D    EvaRg;             //刚体组件
        public                   LineRenderer   LeftLineRenderer;  //左线组件
        public                   Transform      LeftPos;           //弹弓左定点
        public                   LineRenderer   RightLineRenderer; //右线组件
        public                   Transform      RightPos;          //弹弓右定点
        protected                GameObject     EvaBoom;           //Eva爆炸特效
        protected                MyTrail        myTrail;           //定义拖尾脚本对象
        public                   AudioClip      SelectEvaClip;     //选中Eva音效
        public                   AudioClip      FlyEvaClip;        //Eva飞出音效
        protected                SpriteRenderer EvaRender;         //Eva渲染组件
        public                   Sprite         HurtSprite;        //受伤图
        [HideInInspector] public bool           isCanTrail;        //是否能拖拽
        [HideInInspector] public bool           isRelease;         //是否释放Eva
        private                  bool           isClick;           //是否点击
        private                  bool           isFly;             //是否正在飞
    
    
        private void Awake()
        {
            EvaSP     = GetComponent<SpringJoint2D>(); //获取组件
            EvaRg     = GetComponent<Rigidbody2D>();
            myTrail   = GetComponent<MyTrail>();
            EvaRender = GetComponent<SpriteRenderer>();
        }
    
    
        void Start()
        {
            EvaBoom = Resources.Load<GameObject>("Prefabs/EvaMumBoom");
        }
    
    
        // Update is called once per frame
        void Update()
        {
            if (EventSystem.current.IsPointerOverGameObject()) return; //如果点击了UI界面上的按钮,图片。就不向下执行
    
            if (isClick)
            {
                transform.position =  Camera.main.ScreenToWorldPoint(Input.mousePosition);  //屏幕坐标转世界
                transform.position += new Vector3(0, 0, -Camera.main.transform.position.z); //第二种方法:同理,加上摄像机的Z轴偏移量 --得正哦
                //transform.position += new Vector3(0,0,10);//第一种方法:既然摄像机在-10方向上,那么Eva就+10
    
                if (Vector3.Distance(transform.position, RightPos.position) > MaxDis) //如果大于设定距离MaxDis
                {
                    Vector3 pos        = (transform.position - RightPos.position).normalized; //单位化向量,求得方向
                    pos                *= MaxDis;                                             //赋值最大长度 给向量Pos
                    transform.position =  pos + RightPos.position;                            //Eva当前位置赋值:最大距离+起点坐标点的位置
                }
    
                SlingShort();
            }
    
    
            CameraFollow(); //相机跟随
    
            if (isFly) //如果在飞出的过程中
            {
                if (Input.GetMouseButtonDown(0)) //按下鼠标左键
                {
                    EvaYellowExpedite(); //启用黄Eva加速函数
                }
            }
        }
    
    
        /// <summary>
        /// 鼠标按下
        /// </summary>
        private void OnMouseDown()
        {
            if (isCanTrail)
            {
                AudioPlay(SelectEvaClip);
                isClick           = true; //点击了
                EvaRg.isKinematic = true; //启动力学
            }
        }
    
    
        /// <summary>
        /// 鼠标抬起
        /// </summary>
        private void OnMouseUp()
        {
            if (isCanTrail)
            {
                isClick                   = false; //没点击
                RightLineRenderer.enabled = false; //关闭右划线
                LeftLineRenderer.enabled  = false; //关闭左划线
                EvaRg.isKinematic         = false; //关闭力学
                Invoke("Fly", 0.1f);               //调用函数,(“函数名”,延迟时间)
                isCanTrail = false;
            }
        }
    
    
        /// <summary>
        /// 飞出后的处理
        /// </summary>
        private void Fly()
        {
            isRelease = true;      //鼠标抬起
            isFly     = true;      //正在飞,开始
            AudioPlay(FlyEvaClip); //播放音效
            EvaSP.enabled = false; //禁用弹簧链接
            Invoke("NextEva", 4);  //2秒后调用 下一个Eva函数
            myTrail.StartTrail();  //开启拖尾
        }
    
    
        /// <summary>
        /// 弹弓
        /// </summary>
        private void SlingShort()
        {
            //给弹弓划线
            RightLineRenderer.enabled = true;
            LeftLineRenderer.enabled  = true;
            RightLineRenderer.SetPosition(0, RightPos.position);
            RightLineRenderer.SetPosition(1, transform.position);
            LeftLineRenderer.SetPosition(0, LeftPos.position);
            LeftLineRenderer.SetPosition(1, transform.position);
        }
    
    
        /// <summary>
        /// 下一只Eva
        /// </summary>
        protected virtual void NextEva()
        {
            GameManager.Instance.EvaList.Remove(this); //从Eva数组中移除当前Eva
            Destroy(gameObject);
            Instantiate(EvaBoom, transform.position, Quaternion.identity); //实例化特效
            GameManager.Instance.NextEva();                                //调用总控里的下一个判断
        }
    
    
        /// <summary>
        /// 碰撞检测
        /// </summary>
        /// <param name="collision"></param>
        private void OnCollisionEnter2D(Collision2D collision)
        {
            myTrail.ClearTrail(); //清除拖尾
            isFly          = false;
            Time.timeScale = 1;
        }
    
    
        /// <summary>
        /// 相机在指定范围跟随
        /// </summary>
        private void CameraFollow()
        {
            //记录Eva的横坐标
            float posX = transform.position.x;
            //相机当前位置 = 插值(当前相机位置,目标位置(Mathf.Clamp-限定范围:(限定posX,0,18之间))
            Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, new Vector3(Mathf.Clamp(posX, 0, 18), Camera.main.transform.position.y, Camera.main.transform.position.z), SmoothFlo * Time.deltaTime);
        }
    
    
        /// <summary>
        /// 播放音效
        /// </summary>
        /// <param name="clip"></param>
        public void AudioPlay(AudioClip clip)
        {
            AudioSource.PlayClipAtPoint(clip, transform.position); //静态方法:播放音效
        }
    
    
        /// <summary>
        /// 黄色Eva加速方法
        /// </summary>
        public virtual void EvaYellowExpedite()
        {
            isFly = false;
            AudioPlay(FlyEvaClip);
        }
    
    
        /// <summary>
        /// 受伤函数
        /// </summary>
        public void Hurt()
        {
            EvaRender.sprite = HurtSprite;
        }
    }

    这里写图片描述


    5

    EvaMum —— Eva妈妈脚本(- -敌人 )


    提示:由于该游戏逻辑稍易,可被击打对象为 EvaMum 与 场景中的可被拆除的建筑物

    所以此脚本可通用于:被击打物体

    至于是否容易被打死,打碎。取决于碰撞物的相对速度 MinSpeed 与 MaxSpeed 可自己设置

    举个栗子黑白88

    using UnityEngine;
    
    
    /// <summary>
    /// EVA妈妈脚本
    /// </summary>
    public class EvaMum : MonoBehaviour
    {
        public    float          MaxSpeed = 8; //默认最大速度
        public    float          MinSpeed = 3; //默认最小速度
        private   SpriteRenderer Render;       //图片
        public    Sprite         HurtSprite;   //受伤图片
        protected GameObject     Boom;         //爆炸特效
        public    GameObject     EvaMumScore;  //分数图片
        public    bool           isEvaMum;     //是不是Eva妈妈
        public    AudioClip      EvaHurtClip;  //Eva受伤音效
        public    AudioClip      DeadClip;     //销毁音效
        public    AudioClip      HurtClip;     //受伤音效
    
    
        private void Awake()
        {
            Render = GetComponent<SpriteRenderer>(); //获取图片渲染组件
        }
    
    
        // Use this for initialization
        void Start()
        {
            Boom = Resources.Load<GameObject>("Prefabs/EvaMumBoom");
        }
    
    
        /// <summary>
        /// 触发检测,检测是否达到受伤条件
        /// </summary>
        /// <param name="collision"></param>
        private void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.gameObject.tag == "Eva") //需在外部设置标签,给Eva物体设置Tag为Eva
            {
                AudioPlay(EvaHurtClip);                         //播放受伤音效
                collision.transform.GetComponent<Eva>().Hurt(); //受伤
            }
    
            if (collision.relativeVelocity.magnitude > MaxSpeed) //如果相对速度.大小>最大速度
            {
                Dead(); //调用死亡消除方法
            }
            else if (collision.relativeVelocity.magnitude > MinSpeed && collision.relativeVelocity.magnitude < MaxSpeed) //相对速度在4-8之间
            {
                Render.sprite = HurtSprite; //更换图片,受伤
                AudioPlay(HurtClip);
            }
        }
    
    
        /// <summary>
        /// 死亡消除
        /// </summary>
        public void Dead()
        {
            if (isEvaMum)
            {
                GameManager.Instance.EvaMumList.Remove(this); //移除一个EvaMum
            }
            Destroy(gameObject);                                                                                               //删除EvaMum物体
            Instantiate(Boom, transform.position, Quaternion.identity);                                                        //实例化特效
            GameObject scoreobj = Instantiate(EvaMumScore, transform.position + new Vector3(0, 0.5f, 0), Quaternion.identity); //实例化分数
            Destroy(scoreobj, 1.5f);                                                                                           //删除分数
            AudioPlay(DeadClip);                                                                                               //播放死亡音效
        }
    
    
        /// <summary>
        /// 播放音效
        /// </summary>
        /// <param name="clip"></param>
        public void AudioPlay(AudioClip clip)
        {
            AudioSource.PlayClipAtPoint(clip, transform.position); //静态方法:播放音效
        }
    }

    1

    EvaYellow —— 黄色Eva脚本


    黄色小鸟为:加速小鸟,速度乘以2

    注意:由于其他特技类Eva,都属于Eva。

    所以只需继承自Eva,重写Eva脚本中的特技方法 EvaYellowExpedite()

    这里另建一个 EvaYellow 脚本,来重写 Eva 中的 EvaYellowExpedite() 方法即可

    举个栗子黑白88

    /// <summary>
    /// 黄色Eva
    /// </summary>
    public class EvaYellow : Eva//继承自父类Eva
    {
        public override void EvaYellowExpedite()//重写特技方法
        {
            base.EvaYellowExpedite();
            EvaRg.velocity *= 2; //速度2倍
        }
    }

    这里写图片描述


    2

    EvaBlack —— 黑色Eva脚本


    黑色小鸟为:爆炸小鸟 —— 变大,且炸掉周边敌人/物体

    注意:由于其他特技类Eva,都属于Eva。

    所以只需继承自Eva,重写Eva脚本中的特技方法 EvaYellowExpedite()

    这里另建一个 EvaBlack 脚本,来重写 Eva 中的 EvaYellowExpedite() 方法即可

    举个栗子黑白88

    using System.Collections.Generic;
    using UnityEngine;
    
    
    /// <summary>
    /// 黑Eva类脚本
    /// </summary>
    public class EvaBlack : Eva//继承自父类Eva
    {
        public List<EvaMum> EvaMumList = new List<EvaMum>(); //声明一个敌人数组,用来存放EvaMum
    
    
        public override void EvaYellowExpedite() //重写特技方法
        {
            base.EvaYellowExpedite();
            if (EvaMumList.Count > 0 && EvaMumList != null) //判空且有EvaMum存在
            {
                for (int i = 0; i < EvaMumList.Count; i++) //遍历
                {
                    EvaMumList[i].Dead(); //调用EvaMumList数组中的EvaMum物体的死亡方法
                }
            }
            ClearAction(); //调用爆炸动作函数
        }
    
    
        /// <summary>
        /// 碰撞检测
        /// </summary>
        /// <param name="col"></param>
        private void OnTriggerEnter2D(Collider2D col)
        {
            if (col.tag == "Enemy")
            {
                EvaMumList.Add(col.GetComponent<EvaMum>()); //触发器检测到范围内:有敌人,就加入敌人数组EvaMumList
            }
        }
    
    
        /// <summary>
        /// 退出检测
        /// </summary>
        /// <param name="col"></param>
        private void OnTriggerExit2D(Collider2D col)
        {
            if (col.tag == "Enemy")
            {
                EvaMumList.Remove(col.GetComponent<EvaMum>()); //触发器检测到范围内:无敌人,就移除敌人数组EvaMumList
            }
        }
    
    
        /// <summary>
        /// 处理爆炸动作
        /// </summary>
        private void ClearAction()
        {
            transform.localScale = new Vector3(5, 5, 0); //设置自身比例
            EvaRg.velocity       = Vector3.zero;         //速度归零
            myTrail.ClearTrail();                        //清除轨迹
        }
    }

    这里写图片描述


    6

    GameManager ——游戏控制脚本


    用来管理关卡场景中的游戏控制相关操作,挂载到空物体之上

    注意:由于其他特技类Eva,都属于Eva。

    所以只需继承自Eva,重写Eva脚本中的特技方法 EvaYellowExpedite()

    这里另建一个 EvaBlack 脚本,来重写 Eva 中的 EvaYellowExpedite() 方法即可

    举个栗子黑白88

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    
    /// <summary>
    /// 游戏控制脚本
    /// </summary>
    public class GameManager : MonoBehaviour
    {
        public static GameManager Instance
        {
            get { return instance; }
    
            set { instance = value; }
        }
    
        private static GameManager  instance;         //单例
        public         List<Eva>    EvaList;          //Eva数组
        public         List<EvaMum> EvaMumList;       //EvaMum数组
        private        Vector3      OriginPos;        //Eva初始化位置
        public         GameObject   WinPanel;         //胜利游戏面板
        public         GameObject   LosePanel;        //输了游戏面板
        public         GameObject   PausePanel;       //输了游戏面板
        public         GameObject[] Stars;            //星星数组
        private        Button       ListenButton;     //按钮
        private        Animator     PauseAnimator;    //暂停动画
        private        int          StarNum;          //星星数量
        private        bool         isPause;          //是否暂停
        public         int          StarLevelNum = 0; //开始关卡数
        public         int          EndLevelNum  = 3; //结束关卡数
        public         string       LevelCount;       //关卡标识符
        private        int          IndexCount = 0;   //记录每关星星数量
    
    
        void Awake()
        {
            instance = this;
            if (EvaList.Count > 0)
            {
                OriginPos = EvaList[0].transform.position;
            } //如果存在Eva,记录初始化位置
        }
    
    
        void Start()
        {
            Initialize(); //调用初始化函数
            StarNum = 0;
        }
    
    
        /// <summary>
        /// 初始化函数
        /// </summary>
        private void Initialize()
        {
            for (int i = 0; i < EvaList.Count; i++)
            {
                if (i == 0)
                {
                    EvaList[i].transform.position = OriginPos; //给第一个小鸟初始化位置
                    EvaList[i].enabled            = true;      //激活第一个Eva脚本
                    EvaList[i].EvaSP.enabled      = true;      //激活第一个弹簧链接组件
                    EvaList[i].isCanTrail         = true;
                }
                else
                {
                    EvaList[i].enabled       = false; //关闭所有Eva脚本
                    EvaList[i].EvaSP.enabled = false; //关闭所有弹簧链接组件
                }
            }
        }
    
    
        /// <summary>
        /// 判断是否启用下一个Eva
        /// </summary>
        public void NextEva()
        {
            if (EvaMumList.Count <= 0) //如果敌人依旧存在
            {
                WinPanel.SetActive(true); //胜利游戏面板
                AddButtonListen("WinRePlay");
                ListenButton.onClick.AddListener(RePlay);
                AddButtonListen("WinMainMenu");
                ListenButton.onClick.AddListener(Home);
            }
            else
            {
                if (EvaList.Count > 0) //如果Eva存在
                {
                    Initialize(); //初始化Eva
                }
                else
                {
                    LosePanel.SetActive(true); //结束游戏面板
                    AddButtonListen("LoseRePlay");
                    ListenButton.onClick.AddListener(RePlay);
                    AddButtonListen("LoseMainMenu");
                    ListenButton.onClick.AddListener(Home);
                }
            }
        }
    
    
        /// <summary>
        /// 赢了显示星星
        /// </summary>
        public void WinAndShowStar()
        {
            StartCoroutine(ShowStars()); //开启协成,一个个显示
        }
    
    
        /// <summary>
        /// 一个个显示星星协成
        /// </summary>
        /// <returns></returns>
        private IEnumerator ShowStars()
        {
            for (; StarNum < EvaList.Count + 1; StarNum++)
            {
                if (StarNum >= Stars.Length) break; //如果小鸟数量大于星星数量,就跳出:防止越界
    
                yield return new WaitForSeconds(0.5f);
    
                Stars[StarNum].SetActive(true); //开启星星
            }
        }
    
    
        /// <summary>
        /// 添加按钮事件
        /// </summary>
        private void AddButtonListen(string str)
        {
            ListenButton = GameObject.Find(str).GetComponent<Button>();
        }
    
    
        /// <summary>
        /// 重新开始
        /// </summary>
        public void RePlay()
        {
            if (isPause)
            {
                SceneManager.LoadScene(2);
                Time.timeScale = 1;
            }
            else
            {
                SceneManager.LoadScene(2);
                SaveData(); //储存数据
            }
        }
    
    
        /// <summary>
        /// 回到主页
        /// </summary>
        public void Home()
        {
            if (isPause)
            {
                SceneManager.LoadScene(1);
            }
            else
            {
                SceneManager.LoadScene(1);
                SaveData(); //储存数据
                Time.timeScale = 1;
            }
        }
    
    
        /// <summary>
        /// 暂停游戏
        /// </summary>
        public void PauseGame()
        {
            PausePanel.SetActive(true);
            PauseAnimator = PausePanel.GetComponent<Animator>(); //获取暂停动画机
            PauseAnimator.SetBool("isPause", true);
            AddButtonListen("RePlayButton");
            ListenButton.onClick.AddListener(RePlay);
            AddButtonListen("HomeButton");
            ListenButton.onClick.AddListener(Home);
            AddButtonListen("ContinueButton");
            ListenButton.onClick.AddListener(PauseResume);
            isPause = true; //暂停游戏了
    
            if (GameManager.Instance.EvaList.Count > 0) //如果场景里还有Eva
            {
                if (GameManager.Instance.EvaList[0].isRelease == false) //如果没有飞出
                {
                    GameManager.Instance.EvaList[0].isCanTrail = false;
                }
            }
        }
    
    
        /// <summary>
        /// 继续游戏
        /// </summary>
        public void PauseResume()
        {
            Time.timeScale = 1;
            PauseAnimator.SetBool("isPause", false);
            isPause = false; //关闭暂停
    
            if (GameManager.Instance.EvaList.Count > 0)
            {
                if (GameManager.Instance.EvaList[0].isRelease == false)
                {
                    GameManager.Instance.EvaList[0].isCanTrail = true;
                }
            }
        }
    
    
        private int num          = 0;
        private int IndexCount10 = 0;
        private int IndexCount20 = 0;
    
    
        /// <summary>
        /// 存储数据
        /// </summary>
        private void SaveData()
        {
            if (StarNum > PlayerPrefs.GetInt(PlayerPrefs.GetString("NowLevel"))) //判断
            {
                PlayerPrefs.SetInt(PlayerPrefs.GetString("NowLevel"), StarNum); //分别设置每个关卡的星星个数
            }
            //所有星星数量相加
    
    
            if (LevelCount == "0")//通过标识符判断是哪一大系列关卡,并对数据进行保存
            {
                for (int i = StarLevelNum; i <= EndLevelNum; i++)
                {
                    num += PlayerPrefs.GetInt("Level (" + i + ")");
                }
    
                PlayerPrefs.SetInt("Level_1", num);
            }
            else if (LevelCount == "10")
            {
                for (int i = StarLevelNum; i <= EndLevelNum; i++)
                {
                    num += PlayerPrefs.GetInt("Level (" + i + ")");
                }
    
                PlayerPrefs.SetInt("Level_2", num);
            }
            else if (LevelCount == "20")
            {
                for (int i = StarLevelNum; i <= EndLevelNum; i++)
                {
                    num += PlayerPrefs.GetInt("Level (" + i + ")");
                }
    
                PlayerPrefs.SetInt("Level_3", num);
            }
    
            PlayerPrefs.SetInt("AllStarNum",
                PlayerPrefs.GetInt("Level_1") + PlayerPrefs.GetInt("Level_2") +
                PlayerPrefs.GetInt("Level_3")); //在“AllStarNum”中存储总星星数量//将所有产生数据的关卡星星数量总和
        }
    }

    这里写图片描述


    7

    Next Tutorial —— 下一个教程



    至此游戏场景相关结束,需要结合关卡场景

    请跳转至另一个教程 —— Unity3D游戏愤怒的小鸟游戏源码和教程(二)


    支持

    May Be —— 搞开发,总有一天要做的事!


    拥有自己的服务器,无需再找攻略!

    Chinar 提供一站式教程,闭眼式创建!

    为新手节省宝贵时间,避免采坑!


    先点击领取 —— 阿里全产品优惠卷 (享受最低优惠)


    1 —— 云服务器超全购买流程 (新手必备!)

    2 —— 阿里ECS云服务器自定义配置 - 购买教程(新手必备!)

    3—— Windows 服务器配置、运行、建站一条龙 !

    4 —— Linux 服务器配置、运行、建站一条龙 !





    技术交流群:806091680 ! Chinar 欢迎你的加入


    END

    本博客为非营利性个人原创,除部分有明确署名的作品外,所刊登的所有作品的著作权均为本人所拥有,本人保留所有法定权利。违者必究

    对于需要复制、转载、链接和传播博客文章或内容的,请及时和本博主进行联系,留言,Email: ichinar@icloud.com

    对于经本博主明确授权和许可使用文章及内容的,使用时请注明文章或内容出处并注明网址

    展开全文
  • Unity3D开发小游戏】《愤怒的小鸟》开发教程 2019年09月11日 10:34:36恬静的小魔龙阅读数 1698更多 分类专栏:Unity3D日常Unity3d手游开发Unity3D开发小游戏 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA...

    【Unity3D开发小游戏】《愤怒的小鸟》开发教程

    2019年09月11日 10:34:36 恬静的小魔龙 阅读数 1698更多

    分类专栏: Unity3D日常 Unity3d手游开发 Unity3D开发小游戏

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    本文链接:https://blog.csdn.net/q764424567/article/details/100726495

     

    《愤怒的小鸟》开发教程

     

    一、前言

    “愤怒的小鸟”在2009年12月发布,由于它的高度上瘾的游戏,它很快成为有史以来最成功的移动游戏。

    在本教程中,我们将在“Unity”中实现“愤怒的小鸟”翻版。游戏中最复杂的部分是物理系统,但是多亏了Unity,我们就不用担心太多了。使用Unity将使它如此容易,我们将只需要大约100行的代码!

    像往常一样,一切都会尽可能简单地解释,这样每个人都能理解它。

    以下是项目的预览:
    在这里插入图片描述

    二、项目版本

    Unity5.0.0f4

    三、正文

    1.设置相机

    点击Main Cameras,在Hierarchy面板设置背景色以友好的蓝色色调(红色=187, 绿色=238, 蓝色=255)并调整大小而位置如下图所示:
    在这里插入图片描述

    2.地面设置

    地面贴图设置
    为了防止版权问题,我们不能在本教程中使用原“愤怒的小鸟”图形。相反,我们将画我们自己的Sprite,使他们看起来像原来的游戏。

    让我们从用我们选择的绘图工具开始:
    在这里插入图片描述
    将其保存到我们的项目中后,我们可以在项目区:
    在这里插入图片描述
    然后修改导入设置在Inspector:
    在这里插入图片描述
    注:Pixels Per Unit像素转到单位价值16这意味着16x16像素将适合在游戏世界的一个单位。我们将使用这个值作为我们所有的纹理。我们选择16,因为鸟的大小将有一个16x16像素后,我们希望它有一个单位的大小在游戏世界。

    好了,现在我们可以从项目区进入场景:
    在这里插入图片描述
    让我们看看Inspector把地面定位在(0, -2),所以作为不为y=0的都不是地面的一部分:
    在这里插入图片描述
    地面物体设置
    现在地面只是一幅图像,仅此而已。它不是物理世界的一部分,事物不会与它相撞,也不会站在它上面。我们需要添加一个Collider让它成为物理世界的一部分,这意味着事物将能够站在它的顶端,而不是掉进它的正中。

    添加BoxCollider2D组件:
    在这里插入图片描述

    3.边界设置

    创建空对象,命名为borders
    在这里插入图片描述
    位置归零:
    在这里插入图片描述
    现在,我们将在我们的水平的左边、右边和顶部添加某种不可见的区域。每当有东西进入那个区域,它就应该被摧毁。此类行为可以通过Trigger,这几乎只是一个Trigger它接收到碰撞信息,但不会与任何东西发生冲突。

    添加碰撞器:
    在这里插入图片描述
    勾选

    •  Is Trigger

    之后,我们可以为级别的右侧和顶部再添加两个triggers :
    在这里插入图片描述
    如果我们看看场景然后,我们可以看到触发器是如何与我们的背景很好地对齐的:
    在这里插入图片描述
    现在我们仍然必须确保任何进入边界的东西都会立即被销毁。此类行为可以通过脚本Borders:

    创建脚本Borders.cs:
    在这里插入图片描述
    将其添加到边界对象物体上面:
    在这里插入图片描述
    让我们也将脚本移动到一个新的Scripts文件夹,只是为了保持清洁:
    在这里插入图片描述
    编辑Borders.cs脚本:

    using UnityEngine;
    using System.Collections;
    
    public class Borders : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们不需要启动或者更新函数,所以让我们移除它们。相反,我们将使用OnTriggerEnter2D函数,每当有东西进入其中一个边界触发器时,统一将自动调用该函数:

    using UnityEngine;
    using System.Collections;
    
    public class Borders : MonoBehaviour {
    
        void OnTriggerEnter2D(Collider2D co) {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个函数中,无论什么东西进入Triggers,我们都将Destroy这个物体:

    using UnityEngine;
    using System.Collections;
    
    public class Borders : MonoBehaviour {
    
        void OnTriggerEnter2D(Collider2D co) {
            Destroy(co.gameObject);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    保存脚本后,我们的边界就完成了。我们稍后会看到,如果我们试图将一只鸟射出水平之外,它就会消失。

    4.云彩设置

    我们将花几分钟额外添加云到背景,以使水平看起来更好。像往常一样,我们首先画一个:
    在这里插入图片描述
    让我们在项目区然后修改云的导入设置在Inspector:
    在这里插入图片描述
    现在我们要做的就是把它从项目区进入场景几次,将每一片云放置在我们想要的位置:
    在这里插入图片描述
    注意:只要使用一些重复的模式和一些非常颜色,我们可以使水平看起来相当好,无需付出很大的努力。

    5.弹弓设计

    弹弓图片
    一个飞弹将产生新的鸟类,并允许用户发射到水平。和往常一样,我们将从画Sprites开始:
    这里是导入设置:
    在这里插入图片描述
    稍后,我们将创建一个脚本,在弹弓的位置生成一只新的鸟,或者确切地说是在弹弓的Pivot位置生成一只鸟。
    我们想要在弹弓顶部而不是中间处出现,这就是为什么我们要在“导入设置”中设置Pivot在顶部。

    下面的图像显示了中心和顶:
    在这里插入图片描述
    注意:如果我们将数据透视设置为中心然后变换位置是弹弓中心的点。如果我们把Pivot 设为顶,然后变换位置是弹弓顶端的点。

    好了,现在我们可以将弹弓拖到场景中去了(-22, 3):

    在这里插入图片描述
    生成鸟脚本
    如前所述,我们的弹弓应该是生成鸟。确切地说,它应该在一开始就生成一个,然后等待用户启动它,然后在所有的物理计算完成之后再生成另一个。(当什么都不动的时候).

    我们可以通过脚本来实现这样的行为。
    添加脚本Spawn.cs:
    在这里插入图片描述
    我们可以双击脚本来打开它:

    using UnityEngine;
    using System.Collections;
    
    public class Spawn : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个启动函数在开始游戏时由Unity自动调用。这个更新函数被一次又一次地自动调用,大约每秒60次。我们不需要它们中的任何一个,这样我们就可以从脚本中删除它们。

    还有另一种类型的更新函数,它被称为FixedUpdate…它也被一次又一次的调用,但是是在单位物理完全相同的时间间隔内计算的,所以在做物理工作的时候使用FixedUpdate是一个好主意(我们很快就会这么做).

    下面是修改后的脚本FixedUpdate脚本:

    using UnityEngine;
    using System.Collections;
    
    public class Spawn : MonoBehaviour {
    
        void FixedUpdate() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    好的,让我们添加一个变量,允许我们稍后指定BirdPrefab(我们想生的鸟):

    using UnityEngine;
    using System.Collections;
    
    public class Spawn : MonoBehaviour {
        // Bird Prefab that will be spawned
        public GameObject birdPrefab;
    
        void FixedUpdate() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    以下是我们如何生成它的方法:

    void spawnNext() {
        // Spawn a Bird at current position with default rotation
        Instantiate(birdPrefab,transform.position,Quaternion.identity);
    }
    
    • 1
    • 2
    • 3
    • 4

    生成的触发区域

    现在我们不能只生一只又一只鸟。相反,我们将不得不生成一个,然后等待它被发射。有几种方法可以实现这一点,但最简单的方法是使用Triggers.

    Trigger是一个简单的Collider ,接收碰撞信息,但实际上并不是物理世界的一部分。所以如果我们添加一个Trigger然后,每当有东西进入Trigger、留在Trigger中或离开Trigger时,我们都会收到通知。然而,由于它只是一个Trigger,事情不会像普通Collider 那样与它相撞(这很快就更有意义了).

    我们可以将Trigger添加到弹弓中,方法是在Hierarchy面板中,然后点击添加组件Circle Collider 2D,给它一个合适的半径和中心然后启用触发:

    •  Is Trigger
      在这里插入图片描述
      我们还可以在场景中查看:
      在这里插入图片描述
      在添加触发器之后,每当有东西进入时,我们都会收到通知。(OnTriggerEnter2D),停留(OnTriggerStay2D)或离开(OnTriggerExit2D)上面那个绿色的圆圈。

    现在,我们可以通过创建一个使用中变量,然后将其设置为bool值,当生下一只鸟的时候为false,当它离开触发器时为true:

    using UnityEngine;
    using System.Collections;
    
    public class Spawn : MonoBehaviour {
        // Bird Prefab that will be spawned
        public GameObject birdPrefab;
    
        // Is there a Bird in the Trigger Area?
        bool occupied = false;
    
        void FixedUpdate() {
    
        }
    
        void spawnNext() {
            // Spawn a Bird at current position with default rotation
            Instantiate(birdPrefab, transform.position, Quaternion.identity);
            occupied = true;
        }
    
        void OnTriggerExit2D(Collider2D co) {
            // Bird left the Spawn
            occupied = false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    之后我们可以修改我们的FixedUpdate函数,因此每当触发区域不再被占用时,它总是生成一只鸟:

    void FixedUpdate() {
        // Bird not in Trigger Area anymore?
        if (!occupied)
            spawnNext();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注:!被占领手段未被占用…我们也可以用if(occupied == false).

    我们的生成脚本现在可以正常工作了,但是让我们再添加一个特性。在射杀一只鸟之后,会有很多东西相互碰撞,坠落,滚来滚去,甚至爆炸。在最初的“愤怒的小鸟”游戏中,只有在水平上的所有东西停止移动之后,才会产生一只新的鸟。

    我们可以很容易地创建一个sceneMoving函数,该函数查找场景中是否有任何对象仍在移动,而不仅仅是一点点:

    bool sceneMoving() {
        // Find all Rigidbodies, see if any is still moving a lot
        Rigidbody2D[] bodies = FindObjectsOfType(typeof(Rigidbody2D)) as Rigidbody2D[];
        foreach (Rigidbody2D rb in bodies)
            if (rb.velocity.sqrMagnitude > 5)
                return true;
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:我们使用了FindObjectsOfType找到所有带有刚体的物体,之后我们会检查每个物体的velocity,如果这个刚体的sqrMagnitude大于5,说明这个刚体还在移动就返回True,没有就返回false

    使用这个整洁的小脚本,我们可以轻松地修改FixedUpdate功能,因此只有在没有任何移动的情况下才会产生新的鸟:

    void FixedUpdate() {
        // Bird not in Trigger Area anymore? And nothing is moving?
        if (!occupied && !sceneMoving())
            spawnNext();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在我们已经完成了生成鸟的脚本,我们可以在Inspector面板看到弹弓上面挂载的脚本:

    在这里插入图片描述

    注意:我们还不能在没有鸟的情况下测试生成鸟脚本,但是它确实工作得很好,我们将在创建鸟之后看到这一点。

    6.鸟的设置

    鸟的图片
    让我们开始更有趣的事情:鸟。我们首先画一个16 x 16一只大圆身躯和一些小小的翅膀和眼睛的鸟的像素图像:
    在这里插入图片描述
    我们将使用以下方法导入设置为此:
    在这里插入图片描述
    让我们从项目区进入场景若要从其中创建游戏对象,请执行以下操作:
    在这里插入图片描述
    鸟的物理

    让我们为鸟添加碰撞器Circe Collider 2D:

    在这里插入图片描述
    现在有一个Physics Material 2D对撞机的缝隙,让我们可以给鸟一些特殊的物理特性。在“Unity愤怒的小鸟”教程中,物理材料将是非常有用的,因为现在,如果这只鸟掉在地上,它看起来会是这样的:
    在这里插入图片描述
    看起来有点不自然。相反,我们想让这只鸟从下面的东西中跳出来:
    在这里插入图片描述
    要在第二张图片中创建弹跳效果,我们所要做的就是在项目区并选择Create>Physics2D Material,说出来鸟类材料把它变成一个新的物理材料文件夹:
    在这里插入图片描述
    一旦被选中,我们就可以修改Inspector:
    在这里插入图片描述
    注:Bounciness值越大,鸟就越会反弹。

    最后,我们可以再次选择鸟,然后拖动鸟类材料从项目区进入Collider Material插槽:
    在这里插入图片描述
    这只鸟也应该四处走动。刚体负责物体的重力、速度和其他使物体运动的力。根据经验法则,在物理世界里,所有应该移动的东西都需要一个刚体.:
    在这里插入图片描述
    注意:我们设置了Gravity Scale到4因为它能让鸟飞得更快。

    如果我们按下Play现在我们可以看到鸟从地上掉下来并弹跳起来:
    在这里插入图片描述
    我们的鸟类物理已经完成了,但是有一个小的调整是我们必须在这里进行的。现在,如果我们在弹弓中生成的话,由于它的刚体引力,它会立即坠落到地面。我们只希望用户一开火,鸟就会受到重力的影响,所以让我们现在启用Is Kinematic,然后在脚本中禁用它:
    在这里插入图片描述
    现在,刚体是运动学的,这意味着它不受重力或速度的影响,因此不会立即坠落。

    注意:为了更清楚地说明这一点,任何像英雄、汽车或鸟之类的东西都应该有一个刚体,它是运动学的。我们只使能只要鸟还在弹弓里就能运动。

    鸟预制体
    如前所述,这只鸟从一开始就不应该出现在场景中。相反,弹弓应该在需要的时候生成出一只新的鸟。为了使弹弓能够生成鸟,我们必须创建一个预制件 (换句话说,我们必须在我们的项目区有鸟的资源).

    要创建预制件,我们所要做的就是从hierarchy变成一个新的预制文件夹中的项目区:

    在这里插入图片描述
    现在,我们可以在任何时候将鸟装载到场景中,这意味着我们现在也可以从Hierarchy中删除这个对象:
    在这里插入图片描述
    生成鸟
    让我们将预制体bird拖到到我们的Spawn.cs的脚本中的BirdPrefab插槽中:

    在这里插入图片描述
    如果我们按下Play现在我们可以看到弹弓是如何生出一只鸟的:
    在这里插入图片描述

    拉and释放脚本
    用户应该能够把鸟在弹弓周围,然后释放它,以便把它射向所希望的方向。

    我们将创造一个新的C#脚本给它起个名字PullAndRelease :

    using UnityEngine;
    using System.Collections;
    
    public class PullAndRelease : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    用户将能够拖动鸟绕一个圆圈。每个圆都需要一个中心,在我们的例子中,它是鸟的生成位置,所以让我们确保将它保存在一个变量中:

    using UnityEngine;
    using System.Collections;
    
    public class PullAndRelease : MonoBehaviour {
        // The default Position
        Vector2 startPos;
    
        // Use this for initialization
        void Start () {
            startPos = transform.position;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意:我们还删除了更新函数因为我们不需要它。

    好的,为了让用户把鸟拖曳成一个圆圈,我们必须找出这只鸟是否被点击了。(确切地说:拖着)…我们还需要知道用户是否释放了鼠标,在这种情况下,我们必须发射鸟的愿望方向。

    当然,如果没有这方面的功能,那就不是统一了。Unity自动调用Onmouseup和OnmouseDrag函数,当我们用鼠标拖动游戏对象或随后释放鼠标时:

    using UnityEngine;
    using System.Collections;
    
    public class PullAndRelease : MonoBehaviour {
        // The default Position
        Vector2 startPos;
    
        // Use this for initialization
        void Start () {
            startPos = transform.position;
        }
    
        void OnMouseUp() {
            // ToDo: fire the Bird
        }
    
        void OnMouseDrag() {
            // ToDo: move the Bird
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注:鼠标拖动,意思是用户在GameObject上按住鼠标按钮,然后移动鼠标。

    移动鸟真的很容易。我们所要做的就是将当前的鼠标位置转换到游戏世界的某个点,然后将鸟移到那里。当然,只有在一定半径内:

    void OnMouseDrag() {
        // Convert mouse position to world position
        Vector2 p= Camera.main.ScreenToWorldPoint(Input.mousePosition);
    
        // Keep it in a certain radius
        float radius = 1.8f;
        Vector2 dir = p - startPos;
        if (dir.sqrMagnitude > radius)
            dir = dir.normalized * radius;
    
        // Set the Position
        transform.position = startPos + dir;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注:我们必须使用ScreenToWorldPoint因为鼠标总是介于(0.宽度, 0.高度),而游戏在鼠标下的位置可以是几乎任何东西,这取决于摄像机的位置。在找到世界位置之后p,我们只需从startPos到p…如果这个向量太长(sqrMagnitude > radius)然后我们用dir.normalized * radius
    好的,让我们在Project Area/Prefabs文件夹,然后单击Add Component->Scripts->Push And Release.

    如果我们按下Play然后我们可以把鸟绕个圈:
    在这里插入图片描述
    把鸟射向一个方向也同样容易。我们可以用我们的Onmouseup函数来知道鼠标何时释放。然后,我们将计算出从鸟到startPos然后使用rigidbody’s AddForce在那里启动它的功能:

    // The Force added upon release
    public float force = 1300;
    
    void OnMouseUp() {
        // Disable isKinematic
        GetComponent<Rigidbody2D>().isKinematic = false;
    
        // Add the Force
        Vector2 dir = startPos - (Vector2)transform.position;
        GetComponent<Rigidbody2D>().AddForce(dir * force);
    
        // Remove the Script (not the gameObject)
        Destroy(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意:如前所述,我们也将禁用等运动学使刚体再次受到重力和速度的影响。我们只需将当前位置减去startPos…最后,我们删除PushandReleases从鸟的剧本,这样它就不能再被烧了。

    如果我们按下Play然后我们就可以拉着这只鸟开火了:
    在这里插入图片描述
    Feather Particle Effect羽毛的粒子效果
    让我们通过增加鸟的碰撞效果来使游戏更加流畅。一旦它第一次落地,它就应该像这样在自己周围随意地长出羽毛:
    在这里插入图片描述
    当我们需要随机粒子产生、旋转和向某个方向移动时,就会使用粒子系统。粒子系统的一个简单的例子是烟雾,它产生灰色纹理,然后以锥状向上移动。

    我们将修改我们的粒子系统,使其不是使粒子向上飞,而是使它们飞向四面八方。我们还将修改一些更具体的东西,如大小,速度和旋转。我们的羽毛没有正确或错误的粒子系统,所以你可以随意使用它,直到它看起来像你想让它看起来那样。以下是我们得出的结论:
    在这里插入图片描述
    这是我们用来做这件事的图像:
    Feather
    注意:右击图像,选择但作为.。并将其保存在项目的Assets/Sprites文件夹。
    在这里插入图片描述
    我们将使用通常的设置导入它:
    在这里插入图片描述
    之后,我们可以从项目区进入我们粒子系统所以它使用图像对所有的粒子。

    现在我们可以拖动羽毛对象中的游戏对象。Hierarchy进入预制我们的文件夹项目区为了创建一个预制件:
    在这里插入图片描述
    然后我们可以在Hierarchy中删除羽毛游戏对象

    最后一件事是给我们的鸟添加一个脚本,这样羽毛粒子系统就会在发生碰撞时产生。让我们在项目区然后创建新脚本…我们给它起个名字CollisionSpawnOnce。我们也会把它移到我们的Sprits文件夹,然后双击它以打开它:

    using UnityEngine;
    using System.Collections;
    
    public class CollisionSpawnOnce : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们不需要启动或更新功能。相反,我们将使用OnCollisionEnter2D功能和a公共游戏对象应生成的预制件的变量:

    using UnityEngine;
    using System.Collections;
    
    public class CollisionSpawnOnce : MonoBehaviour {
        // Whatever should be spawned (Particles etc.)
        public GameObject effect;
    
        void OnCollisionEnter2D(Collision2D coll) {
            // Spawn Effect, then remove Script
            Instantiate(effect,transform.position,Quaternion.identity);
            Destroy(this);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意:为了确保只产生一次效果,我们将从销毁(这个)(这只会破坏剧本,而不是整只鸟)。
    保存脚本后,我们可以看到效应变量中的检验员…现在我们可以拖着羽毛粒子系统预制件项目区进入效应插槽:
    在这里插入图片描述
    如果我们按下Play然后把这只鸟烧到地上,然后我们就可以看到它周围的羽毛在生成:

    在这里插入图片描述
    路径
    我们还会给我们的鸟添加另一个效果,让它看起来更流畅:一条白点的轨迹,显示鸟的轨迹:
    在这里插入图片描述
    首先,我们需要一些大小不同的跟踪图像:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我们会用同样的导入设置对于每一幅图像:
    在这里插入图片描述
    我们希望能够在我们想要的任何时候产生轨迹部分,这意味着我们将需要一个预制件为每一个。因此,让我们选择尾迹大图像并将其拖到场景从那里回到预制文件夹。我们会重复这段话尾迹大和小径图片,直到我们有三个预制板:
    在这里插入图片描述
    现在我们只需要一个脚本来生成一个又一个的TRAIL元素,大约每秒钟一次。让我们创建一个新的C#脚本给它起个名字Trail :

    using UnityEngine;
    using System.Collections;
    
    public class Trail : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们可以移除更新因为我们不需要它。让我们添加一个公共游戏对象[]保存所有跟踪元素的变量。我们将使用Array,这意味着它不仅仅是一个GameObject:

    using UnityEngine;
    using System.Collections;
    
    public class Trail : MonoBehaviour {
    
        // Trail Prefabs
        public GameObject[] trails;
    
        // Use this for initialization
        void Start () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们还需要一个函数来生成下一条线索。例如,它应该产生第一个TRAIL元素,然后下一次应该生成第二个,然后是第三个,然后是第一个。这可以通过使用实例化有一个额外的计数器变量:

    using UnityEngine;
    using System.Collections;
    
    public class Trail : MonoBehaviour {
    
        // Trail Prefabs
        public GameObject[] trails;
        int next = 0;
    
        // Use this for initialization
        void Start () {
    
        }
    
        void spawnTrail() {
            Instantiate(trails[next], transform.position, Quaternion.identity);
            next = (next+1) % trails.Length;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们将其设置为0,这意味着trails spawnTrail中的第一个元素被调用。然后使用next+1来增加next。为了保持它在trails数组的范围内,我们还将使用% trails。长度,它使用模(%)运算。对于那些不了解模的人,这里有一个更明显的版本:

    void spawnTrail() {
        Instantiate(trails[next], transform.position, Quaternion.identity);
        next = next + 1;
        if (next == trails.Length) next = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在我们有了一个生成轨迹函数,我们可以使用它生成一个新的trail 元素100 ms通过使用InvokeRepeting职能:

    using UnityEngine;
    using System.Collections;
    
    public class Trail : MonoBehaviour {
    
        // Trail Prefabs
        public GameObject[] trails;
        int next = 0;
    
        // Use this for initialization
        void Start () {
            // Spawn a new Trail every 100 ms
            InvokeRepeating("spawnTrail", 0.1f, 0.1f);
        }
    
        void spawnTrail() {
            Instantiate(trails[next], transform.position, Quaternion.identity);
            next = (next+1) % trails.Length;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    现在,小径元素会一直产卵,甚至当鸟不飞的时候也是如此。让我们添加一个小小的修改,只在鸟飞得足够快的情况下才会产生轨迹:

    void spawnTrail() {
        // Spawn Trail if moving fast enough
        if (GetComponent<Rigidbody2D>().velocity.sqrMagnitude > 25) {
            Instantiate(trails[next], transform.position, Quaternion.identity);
            next = (next+1) % trails.Length;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    好的,让我们保存脚本,看看检验员再来一次。在这里,我们将从我们的三条小径预制板中拖走项目区:
    在这里插入图片描述
    如果我们按下Play然后我们就可以看到这只鸟射击后的踪迹:
    在这里插入图片描述

    7.木片

    让我们添加一些结构,如石头,冰和木材,我们的统一2D愤怒的小鸟游戏。

    我们先画一块木片:
    Pixel Art Wood Big在这里插入图片描述
    注意:右击图像,选择但作为.。并将其保存在项目的资产/Sprits文件夹。

    这里是导入设置为此:
    在这里插入图片描述
    现在我们可以把它拖到场景把它放在地上的某个地方:
    在这里插入图片描述
    木片应该是物理世界的一部分,所以我们将一如既往地在添加Box Collider 2D组件:
    在这里插入图片描述
    木片也应该能够四处移动。现在它不会自己移动,但是如果鸟飞进它,它就会移动。它也应该受到重力的影响,所以我们需要的是刚体…我们可以通过选择添加组件->物理二维->Rigidbody 2D…我们也会增加质量到4所以它更重了一点:

    在这里插入图片描述
    现在我们有一块木头,它是物理世界的一部分!

    对于这个略有不同的木片,我们将重复相同的工作流程:
    在这里插入图片描述
    这是我们的游戏如何看待添加第二块木材和旋转第一个90°:
    在这里插入图片描述

    8.石头

    为了在我们的游戏中有几种不同的结构,我们还将添加两种不同类型的石头:
    在这里插入图片描述在这里插入图片描述
    工作流将和以前一样,只是这一次我们将使用更大的工作流。质量为刚体:
    在这里插入图片描述
    下面是我们的游戏中有一些石头的样子:
    在这里插入图片描述
    注:我们再次实现了一些体面的外观与基本的形状,只有少数颜色和抖动。

    9.冰

    冰的图片
    我们将为我们的游戏增加一个结构:冰。不过,这一次会更有趣一些。

    像往常一样,我们首先画一块冰:
    在这里插入图片描述
    这个导入设置与以往相同:
    在这里插入图片描述
    冰物理
    添加 Boxcollider2D组件 刚体组件:
    在这里插入图片描述
    冰应该很滑,所以让我们右击项目区并选择创造->物理二维材料给它起个名字冰料:
    在这里插入图片描述
    设置参数:
    在这里插入图片描述
    之后,我们可以选择冰在层次性然后将冰材料从项目区进入对撞机材料插槽:
    在这里插入图片描述
    撞击时摧毁冰
    如果被足够的力量撞击,我们也希望冰层被摧毁,因为这就是冰的自然作用。我们添加脚本BreakOnImpact.cs脚本:

    using UnityEngine;
    using System.Collections;
    
    public class BreakOnImpact : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们不需要启动或者更新函数,所以让我们移除这两个函数。我们需要一种方法来估计碰撞的力量。我们将保持简单,并将速度与质量相乘:

    float collisionForce(Collision2D coll) {
        // Estimate a collision's force (speed * mass)
        float speed = coll.relativeVelocity.sqrMagnitude;
        if (coll.collider.GetComponent<Rigidbody2D>())
            return speed * coll.collider.GetComponent<Rigidbody2D>().mass;
        return speed;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注:Collision2D是传递给OnCollisionEnter2D功能。它包含相对速度方向与速度相乘。如果我们只关心速度,那么我们可以使用relativeVelocity…现在,如果造成碰撞的对象也有一个刚体然后我们把速度乘以刚体的质量…否则我们只返回速度。

    好了,现在我们可以用OnCollisionEnter2D函数以获得有关碰撞的通知。然后,我们将比较碰撞的力量和一个可配置的变量。如果它比力大,那么冰就会破裂:

    using UnityEngine;
    using System.Collections;
    
    public class BreakOnImpact : MonoBehaviour {
        public float forceNeeded = 1000;
    
        float collisionForce(Collision2D coll) {
            // Estimate a collision's force (speed * mass)
            float speed = coll.relativeVelocity.sqrMagnitude;
            if (coll.collider.GetComponent<Rigidbody2D>())
                return speed * coll.collider.GetComponent<Rigidbody2D>().mass;
            return speed;
        }
    
        void OnCollisionEnter2D(Collision2D coll) {
            if (collisionForce(coll) >= forceNeeded)
                Destroy(gameObject);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如果我们保存脚本,请按Play把鸟碰撞冰层,然后它就会破裂:
    在这里插入图片描述
    现在,我们可以花几分钟来复制这些结构,并将它们放在一起,这样我们就可以在它们之间添加猪了:
    在这里插入图片描述

    10.绿猪

    鸟儿想要消灭所有的猪,所以让我们把一些猪加入到我们的游戏中,这样鸟儿就不会觉得无聊了。

    我们首先画一个:
    在这里插入图片描述
    我们使用了以下方法导入设置为了我们的猪:
    在这里插入图片描述
    添加碰撞器和刚体:
    在这里插入图片描述
    如果有足够大的力量袭击猪,猪就会死。幸运的是,我们已经有了一个脚本。给我们的pig物体添加脚本BreakOnImpact.cs,并且设置Force Needed的值:
    在这里插入图片描述
    注意:能够重用这样的脚本是基于组件的开发非常棒的原因。

    现在我们可以复制这头猪并把它移到一些结构之间:
    在这里插入图片描述
    如果我们按下Play然后我们就可以试着消灭猪了:
    在这里插入图片描述

    11.橡胶

    我们将为我们的游戏添加最后一个功能:弹弓橡胶,所以拖拽和释放鸟看起来要好得多:
    在这里插入图片描述
    我们首先画一半的橡胶,这几乎只是一条粗线:
    在这里插入图片描述
    这里是导入设置:
    在这里插入图片描述
    注意:这次我们设置了Pivot到右(边),正确的让一些旋转变得更容易。

    现在我们可以从项目区进入场景两次,说出其中一个左橡胶,另一个右橡胶把它们放在弹弓的上部:
    在这里插入图片描述
    我们要确保左边的那个总是画出来的。后面弹弓和右弹弓总是被拉进去的。前面其中的一部分。我们可以再加两个分类层对于我们的游戏,但我们将保持简单,只需更改层序到-1左边的橡胶和1正确的橡胶:
    在这里插入图片描述
    在这里插入图片描述
    现在橡胶看起来就像在外弹弓:
    在这里插入图片描述
    在开始编写脚本之前,让我们在Hierarchy然后移动它们进这个弹弓所以他们是它的子节点:
    在这里插入图片描述
    注意:每当我们移动弹弓时,橡胶部件就会随之移动。

    让我们创建一个新的C#脚本给它起个名字Rubber:

    using UnityEngine;
    using System.Collections;
    
    public class Rubber : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
        }
    
        // Update is called once per frame
        void Update () {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个脚本的目的是让两个橡胶部分跟随鸟,直到它离开产卵的触发圈。

    我们需要两个变量,让我们在以后指定橡胶。我们不需要启动或者更新职能:

    using UnityEngine;
    using System.Collections;
    
    public class Rubber : MonoBehaviour {
        // The Rubber objects
        public Transform leftRubber;
        public Transform rightRubber;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    现在,稍微复杂一些的部分。我们将需要一个功能,定位右侧的橡胶在弹弓和左边的鸟。目前还没有统一功能,因此我们必须先将橡胶旋转到鸟的方向,然后根据鸟的距离使橡胶变长或变短:

    void adjustRubber(Transform bird, Transform rubber) {
        // Rotation
        Vector2 dir = rubber.position - bird.position;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
        rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    
        // Length
        float dist = Vector3.Distance(bird.position, rubber.position);
        dist += bird.GetComponent<Collider2D>().bounds.extents.x;
        rubber.localScale = new Vector2(dist, 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:首先我们计算迪尔从鸟到橡胶。然后我们计算这个方向的角度。然后我们把橡胶旋转通过使用四元数角轴…最后,我们使用基本向量数学计算区在橡胶和鸟之间。之后,我们将水平刻度设置为该距离(再加上鸟的一半宽度,即区x).
    这个OnTriggerStay2D功能将通知我们,每当鸟改变它的位置时,它还在弹弓。我们可以使用这个函数来调整左右橡皮筋:

    using UnityEngine;
    using System.Collections;
    
    public class Rubber : MonoBehaviour {
        // The Rubber objects
        public Transform leftRubber;
        public Transform rightRubber;
    
        void adjustRubber(Transform bird, Transform rubber) {
            // Rotation
            Vector2 dir = rubber.position - bird.position;
            float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
            rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    
            // Length
            float dist = Vector3.Distance(bird.position, rubber.position);
            dist += bird.GetComponent<Collider2D>().bounds.extents.x;
            rubber.localScale = new Vector2(dist, 1);
        }
    
        void OnTriggerStay2D(Collider2D coll) {
            // Stretch the Rubber between bird and slingshot
            adjustRubber(coll.transform, leftRubber);
            adjustRubber(coll.transform, rightRubber);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    快好了。我们将再增加一个离开时候触发的事件,使橡皮筋在发射后变短:

    void OnTriggerExit2D(Collider2D coll) {
        // Make the Rubber shorter
        leftRubber.localScale = new Vector2(0, 1);
        rightRubber.localScale = new Vector2(0, 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在,我们可以通过首先选择弹弓对象中的游戏对象。层次性然后点击添加组件->Scitps->Rubber…我们亦会把这两个橡胶拖到相应的槽内:

    在这里插入图片描述
    如果我们按下Play现在我们可以看到小鸟和弹弓之间的橡皮筋:
    在这里插入图片描述
    当然,现在我们也可以玩一轮愤怒的小鸟了:
    在这里插入图片描述

    12.总结

    我们刚刚在Unity中创造了一个漂亮的小愤怒的小鸟翻版。有很多东西要学。我们使用简单的形状和颜色来达到良好的视觉效果。我们广泛使用了Unity的2D物理系统,给我们的鸟增加了很多的效果。

    所有这些小把戏,比如易碎的冰,一只在与某物相撞时会长出羽毛的鸟,以及一条标志着游戏轨迹的痕迹,都会让游戏在稍后的时候感觉到。这就是为什么保持小游戏和专注于每一个小细节使游戏感觉正确是如此重要。

    展开全文
  • 花了几天时间,边做愤怒的小鸟,变熟悉Unity3D 今天做完了,算是对Unity3D开发2D游戏有了一个了解 Unity3D确实强大了,省了很多功夫。 说说在开发过程中遇到的难题: (一)没有用第三方的2D开发插件 我是自己...

    花了几天时间,边做愤怒的小鸟,边熟悉Unity3D


    今天做完了,算是对Unity3D开发2D游戏有了一个了解


    Unity3D确实强大了,省了很多功夫。


    说说在开发过程中遇到的难题:

    (一)没有用第三方的2D开发插件

    我是自己弄个Panel来替代的

    由于Panel默认是躺着的。。。所以所有的GameObject需要先  X旋转90°  Y旋转180°

    本来是没有问题的,可后来发现:当我需要把一个GameObject(也是Panel)旋转到面向右边时

    我本以为只是把Z轴的旋转角度改为90°就可以了的啊。。可后来发现不是这么回事


    好像是因为这是世界坐标系,之前的X和Y都旋转了,当再次旋转Z的时候,不是我们想的那样了


    后来自己计算了下,发现改为X:0 Y:-90 Z:90才对(与我预想的X:90 Y:180 Z:90不一样的)


    (二)脚本使用的不熟悉

    比如当小鸟在弹弓上的时候,需要忽略碰撞(不然会和弹弓已经其它小鸟碰在一起)。

    当时不知道怎么做,后来查找才发现有两种方法

    Physic.IgnoreCliision(GameObject obj1, GameObject ojb2);
    这个方法是让物理引擎忽略掉obj1和obj2的碰撞

    还有一种

    设置gameObject.rigidbory.isKinematic = true;

    这是让物体只受脚本的影响,物理引擎不能影响

    然后可以设置物体的isTrriger为true,表示这是一个触发器(只接受碰撞事件,但不会阻碍物体穿过它)


    (三)Unity3D功能不熟悉

    之前不知道Unity3D有动画编辑系统

    小鸟跳上弹弓的动画是自己写代码弄的。。。后来才知道的。。。


    在计算得分的时候,之前检测木板的碰撞就得分

    后来发现一个木板可以碰很多次。

    加了限制也不好使。。。

    后来网上找到方法

    原来可以写个脚本,用来动态检查gameObject的位置是否改变巨大

    遇过改变的距离大于一个值,就表示木板动了

    然后把这个脚本赋值给所有木板。。。。。

    这方便多了





    总结:

    通过这个游戏,学到了不少知识

    不只是了解了Unity3D的基本界面,还学会使用GUI,Prefab等等


    发现自己不足之处,太缺乏3D方面的知识,坐标旋转啊。。。本地坐标,世界坐标。。感觉有些混乱


    继续学习Unity3D 等有了新的体会,或者新的知识,再来和大家分享

    展开全文
  • Unity 通过LineRenderer绘制两点之间的直线; public class Bird:MonoBehaviour{ private bool isClick=false;//判断是否点击鼠标 public float maxDis=3; private SpringJoint2D sp; private ...
  •  实现个性化的鼠标 实现弹弓 选择小鸟、拉升弹弓、发射小鸟 弹弓橡皮筋 声音   1、实现个性化鼠标   效果   2、添加弹弓 建立两个材质 创建一个空GameObject 把两个shoot拖进来统一管理 3、...
  • 在本教程中,我们将在“Unity”中实现“愤怒的小鸟”翻版。游戏中最复杂的部分是物理系统,但是多亏了Unity,我们就不用担心太多了。使用Unity将使它如此容易,我们将只需要大约100行的代码! 像往常一样,一切都会...
  • 非常高兴我能学到热门游戏愤怒的小鸟制作游戏的课程。 实现小鸟的拖拽 使用**Spring Joint弹簧关节:弹簧关节允许一个刚体物件被拖向一个指定的"目标"点。这个点即可以是另一个刚体物件,也可以是世界。当物件远离...
  • Unity愤怒的小鸟

    2018-05-14 14:04:54
    哭了倒腾了几个礼拜总算弄好了,这里是菜单界面游戏场景,游戏代码挑些麻烦的来做记录吧QAQ整个游戏最难的算法就是这个用鼠标控制发射设计了,代码参考了siki学院的教学 if (isClick) { transform.position = ...
  • 下面我们开始今天的Unity3D游戏开发技能培训。 我们学习Unity3D培训目标:让U3D初学者可以更快速的掌握U3D技术,自行制作修改素材,可以独立完成2D、3D小规模游戏及网页游戏开发。 今天我们来做一个高端大气上档次的...
  • 该组件主要用来描绘弹弓两边的弹簧线,弹弓是由两张图片组成,在这两个弹弓上添加LineRenderer组件之后,就可以在脚本里面控制其来划线,这里就是简单的在拖动鸟的时候画上两条直线就是了。参数没什么要改的,可以看...
  • 说明:本次学习开始于2018年9月23日,至9月26日已看完全部教学视频,并已基本完成该案例的制作,历时四天。...siki学院官网在线视频 愤怒的小鸟 Unity2017.3.0f3开发 讲师lvy 一 新建工程 2d 在d盘的u...
  • 物理向的小游戏,首先我们需要知道这个组件:SpringJoint,也就是2D弹簧组件,很有意思的组件,做UI的时候可以用来实现那种绳子吊着木牌悬挂的感觉。 这是这个小游戏实现的核心之一。 哦,对了,还有这个组件:...
1 2 3
收藏数 41
精华内容 16