unity3d 二维射击游戏_unity3d向前方射击nity3d射击游戏教程 - CSDN
  • 上一节将UI简单的布置了一下,下面来实现UI...射击后子弹数减1,并且下面直接调用UI显示,这样就形成了数据绑定,使得UI的子弹数和脚本的子弹数保持一致 //弹夹内子弹数 [SerializeField] private int currentBull

    上一节将UI简单的布置了一下,下面来实现UI中当前子弹数和总子弹数和脚本的数据绑定,并且发射子弹要朝着准星的位置发射

    1:子弹和显示UI的子弹数的实现思路:在枪攻击的脚本里添加当前子弹数和背包子弹数两个变量,在发射子弹后当前弹夹子弹数减1,并且将当前子弹数显示到UI上

    下面看代码

    射击后子弹数减1,并且下面直接调用UI显示,这样就形成了数据绑定,使得UI的子弹数和脚本的子弹数保持一致

    //弹夹内子弹数
        [SerializeField]
        private int currentBullets=30;
        //剩余子弹数
        [SerializeField]
        private int surPlusBullets=120;
    
    //射击方法
        private void Shoot()
        {
            if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(0))
            {
                //判断子弹是否够用 
                if (currentBullets <= 0) {
                    //更换弹夹
                    swapClip();
                    return;
                }
                //设置开枪间隔时间是0.1秒,换弹夹的时候不能开枪
                if (Time.time - timer > 0.1&& !animator.GetBool("SwapClip"))
                {
               
                    //开枪音效
                    ShootAudio.Play();
                    //开枪动画
                    animator.SetBool("Shoot", true);
                    timer = Time.time;
                    //火花特效
                    GameObject filego = GameObject.Instantiate(firePointParticle, FirePoint);
                    filego.GetComponent<ParticleSystem>().Play();
                    //发射射线
                    RaycastHit hit;
                    //屏幕发射射线判断是否击中
                   Ray ray = sightBeadCamera.ScreenPointToRay(sightBead.transform.position);
                    if (Physics.Raycast(ray, out hit,200,5)){
                           //子弹弹痕贴图
                           GameObject go = GameObject.Instantiate(bulletTextcute, hit.point + 0.01f * hit.normal, Quaternion.Euler(Vector3.zero));
                            //注视旋转
                            go.transform.LookAt(hit.point + hit.normal);
                    }
                    //子弹减一
                    currentBullets--;           
                    //显示子弹UI
                    showCurrentBullets();
                    if (currentBullets <= 0) return;
                }
                else
                {
                    animator.SetBool("Shoot", false);
                }
            }
            else
            {
                animator.SetBool("Shoot", false);
            }
        }
    
    //显示当前弹夹子弹数
        [SerializeField]
        private Text currentBulletsNumber;
        public void showCurrentBullets() {
            currentBulletsNumber.text = this.currentBullets.ToString();
        }

    2:更换弹夹的实现思路:在当前 弹夹子弹数为0的时候,或者玩家按下R键的时候,更换弹夹,并且有播放弹夹动画

    //更换弹夹  
    public void swapClip() {
            if (surPlusBullets > 0 )
            {
                //换弹夹动画,动画播放完毕后换弹
                animator.SetBool("SwapClip", true);
                //换弹夹音效
                if(!SwapClip.isPlaying)
                SwapClip.Play();
    
            }
    
            else { 
            //没有子弹了
            
            }
    
    //在更换弹夹动画播放完毕后调用
        public void calculationBullets()
        {
            if (surPlusBullets >= (30 - currentBullets))
            {
                this.surPlusBullets -= (30 - currentBullets);
                this.currentBullets = 30;
    
            }
            else
            {
                this.currentBullets += surPlusBullets;
                this.surPlusBullets = 0;
            }
            //设置动画状态机
            animator.SetBool("SwapClip",false);
            //显示UI
            showCurrentBullets();
            showsurPlusBullets();
    
        }

    需要注意的是上面并没有更换弹夹的子弹数的逻辑,因为更换弹夹需要在更换弹夹动画播放完毕后才算更换完毕,所以我将子弹逻辑写在了弹夹动画的关键帧里面调用了

    3: 准星作为射击位置的思路:之前实现子弹是在枪口发射射线,玩家难以瞄准,所以在屏幕中间添加一个准星,所以我们之前在枪口发射射线就不满足了,我们就在准星的位置发射一条屏幕射线来实现子弹功能

    //屏幕发射射线判断是否击中
                   Ray ray = sightBeadCamera.ScreenPointToRay(sightBead.transform.position);
                    if (Physics.Raycast(ray, out hit,200,5)){
                           //子弹弹痕贴图
                           GameObject go = GameObject.Instantiate(bulletTextcute, hit.point + 0.01f * hit.normal, Quaternion.Euler(Vector3.zero));
                            //注视旋转
                            go.transform.LookAt(hit.point + hit.normal);
                    }
                    //if (Physics.Raycast(FirePoint.position, -FirePoint.right, out hit, 200))
                    //{
                    //    //子弹弹痕贴图
                    //    GameObject go = GameObject.Instantiate(bulletTextcute, hit.point + 0.01f * hit.normal, Quaternion.Euler(Vector3.zero));
                    //    //注视旋转
                    //    go.transform.LookAt(hit.point + hit.normal);
                    //}

    sightBead是准星的游戏对象,屏幕发射射线就会发射到屏幕该点透视到的三维空间,所以不需要你去考虑发射的方向,

    在第二行代码中 Physics.Raycast(ray, out hit,200,5),第三个参数是发射最大距离,第四个参数是不接受射线的层,我们需要UI不接受射线,自身玩家不接受射线。UI不接受射线在UI的Inspctor面板中可以设置 

    自身玩家不接受射线可以用layerMask去设置,直接设置前面的编号即可。

    本节结束,代码写的可能比较乱,因为没有自己搭框架,因为是练手项目,主要目的还是熟悉unity面版的操作和明白unity能干什么,不能干什么,那些是需要程序员是实现的是主要目的。

    展开全文
  • 在FPS游戏中,人物控制十分重要。人物控制包括,人物行走,跳跃,人物跟随镜头旋转,人物的右手(持枪部位)根据鼠标旋转而旋转。 首先,我们来看看如何使人物进行移动,在这里我使用了Sphere和Capsule作为身体,将...

    在FPS游戏中,人物控制十分重要。人物控制包括,人物行走,跳跃,人物跟随镜头旋转,人物的右手(持枪部位)根据鼠标旋转而旋转。

    首先,我们来看看如何使人物进行移动,在这里我使用了Sphere和Capsule作为身体,将摄像头放在Character的对象下,位置放在身体里面。


    人物移动有许许多多多的方法,这里我简单介绍几种。

    我们统一设定移动的速度为mMoveSpeed,移动的距离distance为Time.deltatime*mMoveSpeed.

    第一种:通过transform.Translate(new Vector3(X,X,X))

    第一种方法实现所有的行走模式,之后的方法只用向前(A)作为演示。

    这种方法最简单,也最普遍

    例如向前走

    if(Input.GetKey(KeyCode.W))

    {

    transform.Translate(new Vector3(0,0,distance);

    return true;

    }

     // 后
            if (Input.GetKey(KeyCode.S)) {
                transform.Translate(new Vector3(0, 0, -distance));
                return true;
            }


            // 左
            if (Input.GetKey(KeyCode.A)) {
               transform.Translate(new Vector3(-distance, 0, 0));
                return true;
            }


            // 右
            if (Input.GetKey(KeyCode.D)) {
                transform.Translate(new Vector3(distance, 0, 0));
                return true;
            }

    //左前方移动
            if (Input.GetKey(KeyCode.W) && Input.GetKey(KeyCode.A))
            {
                transform.Translate(new Vector3(-distance, 0, distance));
                return true;
            }
            //右前方
            if (Input.GetKey(KeyCode.W) && Input.GetKey(KeyCode.D))
            {
                transform.Translate(new Vector3(distance,0,distance));
                return true;
            }
            //左下方
            if(Input.GetKey(KeyCode.A)&&Input.GetKey(KeyCode.S))
            {
                transform.Translate(new Vector3(-distance,0,-distance));
                return true;
            }
            //右下方
            if(Input.GetKey(KeyCode.S)&&Input.GetKey(KeyCode.D))
            {
                transform.Translate(new Vector3(distance,0,-distance));
                return true;
            }
            //前后不动
            if(Input.GetKey(KeyCode.W)&&Input.GetKey(KeyCode.S))
            {
                return true;
            }
            //左右不动
            if(Input.GetKey(KeyCode.A)&&Input.GetKey(KeyCode.D))
            {
                return true;
            }
           这里实现了,总共八个方向的移动,并且在同时按住前进和后退,以及向左和向右键,保持不动,大家可以对照自己玩过的FPS游戏,是不是所有的移动方法都已经包括了。

    第二种方法:

    使用系统给的Vector.forward等方法。

    例如

    //向前

    if(XXXX)

    {

         transform.Translate(Vector.forward*distance);

    return true;

    }

    其他同样的道理,向右是Vector.right,向上时Vector.up.

    Vector没有向后和向左,但很简单,-Vector.forward 就是向后,向左也一样,加个符号。

    Vector.forward实际上就是(0,0,1),是一个Z轴上长度为1的向量,是系统给定的。

    第三种方法:

    以上两种方法,移动的速度都是mMoveSpeed,距离都是distance,可以说移动的速度是不变的(这里的Time.deltaTime会有细微的变化)

    第三种方法事实上有一种加速的效果,就是速度从慢到快

    例如

    //向前

    if(XXX)

    {

    transform.Translate(Input.GetAxis("Horizontal")*distance);

    return true;

    };

    这段代码最重要的就是Input.GetAxis("Horizontal")这个方法。

    这个方法用于获得键盘输入在当前X轴上的变化量。



    在Edit->project Setting->Input中修改和查看

    它以你当前所在X轴为中心,当你按住A键,人物向左移动,Input.GetAxis("Horizontal")返回的是移动的量,从0到-1,D键向右,value从0到1.

    所以,当我们按住向左的A键时,速度是从0*distance,上升到1*distance的,所以产生了一个加速效果。

    第四种方法:

            // 前
            if (Input.GetKey(KeyCode.W)) {
              float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
                transform.Translate(new Vector3(x,0,z), Space.World);
                return true;
            }

    这是网上一个教材的源代码,这种实现方法我也看不太懂,但的确可以实现。原理还是通过transform.Translate。

    至于为什么用正弦函数用来赋值给X不清楚原作者的用意。不排除像第三种方法那样实现了某种特殊的加速效果。

    第五种也是最简单的一种方法:

    private Vector3 move = Vector3.zero;

    move = new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical"));

    transform.Translate(move*distance);

    使用move变量时时获取物体在X轴和Z轴上的变化量,通过transform.Translate函数时时变化。

    总结:

    第一种方法:最自由,可以根据自己的意愿来修改任意的行走速度

    第二种方法:与第一种类似,只是使用了Vector类自带的向量

    第三种方法:比较麻烦,但是可以模拟产生加速的效果

    第五种方法:最简单,省力。



    展开全文
  • 各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。 首先博主要自我反省,过了这么久才来更新博客,这段时间主要是在忙着写期末的作业,所以博主基本上没有空闲时间来...

           各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei

           首先博主要自我反省,过了这么久才来更新博客,这段时间主要是在忙着写期末的作业,所以博主基本上没有空闲时间来研究技术,希望大家谅解啊。有时候,博主会不由自主地想,如果当初选择了计算机专业,此时此刻,我可能会有更多的时间来和大家分享我在学习过程中的感悟吧,有时候博主感觉自己很孤独,在身边找不到和自己志同道合对技术感兴趣的人,这可能是博主在大学里最大的遗憾了,一个人的成长环境很重要,当你无法改变身边的环境的时候,你只能努力地改变自己,每当博主对自己说这句话的时候,博主总是一个静静地像现在这样敲写代码,做自己喜欢的事情是幸福的事情。好了,闲话有空再叙,我们正式开始今天的内容。相信大多数的80后、90后一定对《魂斗罗》这款游戏感到熟悉吧,这款游戏可以说是FC时代引领射击类游戏潮流的一款经典之作,以致于其设计深刻影响到了像后来的《合金弹头》等游戏。这个世界上有很多经典的东西,即使时过境迁永远无法被超越。博主在移动平台上看到这款游戏后,果断下载重温了这款经典游戏。作为一名有追求的游戏设计人员,我们应该在玩游戏的过程中有所收获,积极寻找可以作为我们在技术领域研究的东西,博主的博客几乎都是这么写下来的,除非是有针对性地学习某些东西,所以呢,就有了这篇文章。

           好了,不卖关子了,大家在玩《魂斗罗》的时候一定知道游戏中有一种追踪弹吧,这种炮弹会随着玩家位置的改变而改变,就像我们在某些军事题材的电影中经常看到的镜头一样,我方战斗机将敌机目标锁定后发射炮弹,炮弹就会对敌机紧追不舍并最最终摧毁敌机。可是不管影视作品特效如何炫酷夺目,最终留给我们的就是华丽的特效背后蕴藏的原理。在游戏开发领域,这种技术称为追踪算法,它是属于AI的一个范畴。常见的追中算法主要有三种,即坐标追踪、视线追踪、拦截追踪。下面呢,我们来分别讲解这三种不同的追踪算法:

           一、坐标追踪

          坐标追踪是最简单、最基本的一种追踪算法,如图,


         其基本思路是根据追踪目标的坐标来改变追踪物体的坐标,使两者间距离缩短。举一个简单地例子,假设我们使用二维坐标mPosition来表示追踪物体的坐标,使用mTargetPos来表示追踪目标的坐标,则坐标追踪的算法可以简单表示成下面的代码:

    if(mPosition.x<mTargetPos.x) mPosition.x+=Speed*Time.deltaTime;
    if(mPosition.x>mTargetPos.x) mPosition.x-=Speed*Time.deltaTime;
    if(mPosition.y<mTargetPos.y) mPosition.y+=Speed*Time.deltaTime;	
    if(mPosition.y>mTargetPos.y) mPosition.y-=Speed*Time.deltaTime;
    	
         二、视线追踪,主要是指每一时刻都追踪者会沿着被追逐者之间的直线方向运动。如图所示:


            由图易知,该算法的关键是求解两个位置间的连线。由向量的知识我们知道这条直线可以通过向量可以通过向量a-向量b得到。此时追踪者的速度应该满足约束条件:

            水平分速度Vx/垂直分速度Vy=c向量的水平分量/c向量的垂直向量。

             

            三、拦截追踪,拦截追踪是在前两种方法的基础上发展而来的一种方法,如果考虑的是追踪目标太远,如果两者者速度一样,或者相差不大,有可能很难追上。如图


            此时,对于追踪物体而言,它只需要知道追踪目标的位置、方向与速度,就会计算一个最佳的拦截位置,且所需要的时间最短。我们假设最佳拦截点是S2,由速度与位移的关系很容易知道 S2=Sp+t*V。其中t是追踪者追上猎物的时间。接下来问题变为一个简单的追击问题,求追击时间t。

           首先我们建立追踪者与猎物的速度向量Va与Vp,及位置向量Sa与Sp。  设速度差向量 Vd =Vp-Va 。称为靠拢速度。 设距离差向量 Sd =Sp-Sa 。称为靠拢距离。  于是,靠拢时间 t=|Sd|/|Vd| 。即路程除以速度等于时间。  套用公式S2=Sp+t*V 就得到了拦截点S2,剩下的过程就与视线追踪一样。

          好了,在理解了追踪算法的基本原理以后,下面我们以一个最简单的例子来演示今天的内容。首先我们在Unity3D中建立一个简单地场景,如图所示:


           我们采取正交投影的方式来实现一个2D场景,场景中红色的方块为目标物体,绿色的方块为追踪物体。我们这里采取的是视线追踪的方法。我们首先来为追踪物体创建脚本Follow.cs。脚本定义如下:

    using UnityEngine;
    using System.Collections;
    
    public class Follow : MonoBehaviour {
    
    	//追踪的目标物体
    	public Transform Target;
    	//两个物体间的最小距离
    	public float MinDistance=1F;
    	//两个物体间的最大距离
    	public float MaxDistance=5F;
    	//定义追踪的速度
    	public float Speed=0.25F;
    	//定义追踪物体的坐标
    	private Vector2 mPosition;
    	//定义目标物体的坐标
    	private Vector2 mTargetPos;
    	//是否在追踪
    	private bool isFollow=false;
    
    	void Start () 
    	{
    		mPosition=new Vector2(transform.position.x,transform.position.y);
    		mTargetPos=new Vector2(Target.transform.position.x,Target.position.y);
    	}
    
    	void Update () 
    	{
    	  //获取目标物体的位置
    	  mTargetPos=new Vector2(Target.transform.position.x,Target.position.y);
    	  //如果追踪物体与目标物体之间距离大于等于最大距离,则开始追踪
    	  if(Vector2.Distance(mPosition,mTargetPos)>=MaxDistance)
    	  {
    			isFollow=true;
    	  }
    	  //如果追踪物体与目标物体之间距离小于等于最小距离,则停止追踪
    	  if(Vector2.Distance(mPosition,mTargetPos)<=MinDistance)
    	  {
    			isFollow=false;
    	  }
    	  //如果开始追踪,则执行下面的代码
    	  if(isFollow)
    	  {
    			//计算坐标值
    			if(mPosition.x<mTargetPos.x) mPosition.x+=Speed*Time.deltaTime*Mathf.Abs(Mathf.Cos(getAngle()));
    			if(mPosition.x>mTargetPos.x) mPosition.x-=Speed*Time.deltaTime*Mathf.Abs(Mathf.Cos(getAngle()));
    			if(mPosition.y<mTargetPos.y) mPosition.y+=Speed*Time.deltaTime*Mathf.Abs(Mathf.Sin(getAngle()));
    			if(mPosition.y>mTargetPos.y) mPosition.y-=Speed*Time.deltaTime*Mathf.Abs(Mathf.Sin(getAngle()));
    			//改变追踪物体的坐标
    			transform.position=new Vector3(mPosition.x,mPosition.y,0);
    	  }
    	}
    
    
    	private float getAngle()
    	{
    		float angle=0;
    		//获取水平方向与竖直方向的变化量
    		float deltaX=mTargetPos.x-mPosition.x;
    		float deltaY=mTargetPos.y-mPosition.y;
    		//计算角度
    		if(deltaX>0 && deltaY>0)
    			angle= Mathf.Atan(deltaY/deltaX);
    		if(deltaX<0 && deltaY>0)
    			angle= Mathf.PI-Mathf.Atan(deltaY/deltaX);
    		if(deltaX<0 && deltaY<0)
    			angle= Mathf.PI+Mathf.Atan(deltaY/deltaX);
    		if(deltaX>0 && deltaY<0)
    			angle= 2*Mathf.PI-Mathf.Atan(deltaY/deltaX);
    		if(deltaX==0)
    		{
    			angle=Mathf.PI/2;
    		}
    		if(deltaY==0)
    		{
    			angle=0;
    		}
    		return angle;
    	}
    }
    
          在这段脚本中,我们定义了一个getAngle()方法,该方法用于获取追踪物体与目标物体连线与X轴正方向所成的夹角。这里主要用到三角函数知识,大家可以再温习下以前学过的知识,无论是程序设计还是游戏开发,数学都应该是我们最应该掌握的东西。好了,通过角度计算,我们可以将速度分解到水平和垂直两个方向,从而保证视线追踪中的约束条件成立。如果我们将getAngle()方法从脚本中去除,则这就是最简单的坐标追踪,希望大家自己去探讨和研究啊,如果博主有时间的话,会将三种算法的代码都展示出来的,希望大家继续关注我的博客啊。呵呵。好了,接下来,我们来为追踪目标创建一个脚本,以便玩家可以控制追踪目标躲避追踪。脚本定义如下:

    using UnityEngine;
    using System.Collections;
    
    public class Control : MonoBehaviour {
    
    	//定义移动的速度
    	public float Speed=0.25F;
    	//定义当前位置
    	private float mX,mY;
    	void Start () 
    	{
    		mX=transform.position.x;
    		mY=transform.position.y;
    	}
    
    	void Update () 
    	{
    	  if(Input.GetKey(KeyCode.A))
    	  {
    			mX-=Speed*Time.deltaTime;
    	  }
    	  if(Input.GetKey(KeyCode.D))
    	  {
    			mX+=Speed*Time.deltaTime;
    	  }
    	  if(Input.GetKey(KeyCode.W))
    	  {
    			mY+=Speed*Time.deltaTime;
    	  }
    	  if(Input.GetKey(KeyCode.S))
    	  {
    			mY-=Speed*Time.deltaTime;
    	  }
    	  transform.position=new Vector3(mX,mY,0);
    	}
    }
    

             好了,下面我们来测试一下程序运行的效果:

     

            不知道为什么录制的GIF动画看不到追踪物体,囧啊,不过博主可以负责任地对大家说,程序没什么问题,哈哈。

            好了,今天的内容就是这样了,希望大家喜欢,如果大家希望继续在《魂斗罗》游戏中寻找有趣的设计,请关注我的下一篇博客:Unity3D游戏开发之从《魂斗罗》游戏说起(下) 。再次感谢大家关注我的博客,谢谢。


    每日箴言:你专注于一个方向,终会比别人走得远些。




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



    展开全文
  • 今天我们要用Unity3D做一个植物大战僵尸的仿制版本。为了保持简单,我们将忽略所有花哨的东西,如菜单,多层次或剪裁场景,并专注于在后院与僵尸作战。 以下是游戏的预览: 、版本 Unity5.0.1f1 三、正文 1.主...

    一、前言

    今天我们要用Unity3D做一个植物大战僵尸的仿制版本。为了保持简单,我们将忽略所有花哨的东西,如菜单,多层关卡或过场动画,并专注于在后院与僵尸作战。

    效果图:

    在这里插入图片描述

    二、源码

    UI资源和源代码请搜索QQ群:1040082875下载

    三、正文

    版本

    Unity5.0.1f1

    1.主摄像机设置

    如果我们选择主照相机在层次性然后我们可以设置背景色若要黑色,请调整大小而位置如下图所示:
    在这里插入图片描述

    2.创造草地

    在这里插入图片描述
    注意:右击图像,选择另存为。保存到项目的资产文件夹并将其保存到新的Sprites文件夹。

    让我们在项目区选择它:
    在这里插入图片描述
    然后修改导入设置在Inspector:

    在这里插入图片描述
    注:Pixels to Unit 值设成32这意味着32 x 32像素将适合在游戏世界的一个单位。我们将使用这个值作为我们所有的纹理。

    之后,我们可以将图片从项目区拖入到场景中:
    在这里插入图片描述
    我们会把它定位在(0, 0)所以它在我们游戏的左下角:
    在这里插入图片描述
    添加排序层
    我们正在制作一个2D游戏,所以我们不能用三维作为深度效果,也不能轻易区分背景和前景。相反,我们将使用Sorting Layer告诉统一它应该先画哪些元素。例如,我们应该先画背景图,然后再画植物。(因此在背景之上).

    我们可以换草Sorting Layer如果我们看看Inspector中的Sprite Renderer组件:

    在这里插入图片描述
    让我们选择添加排序层.。从分选层列表,添加Background层,并将其移动到顶部,如下所示:
    在这里插入图片描述
    之后,我们再次选择草,并分配先前创建的Background分类层:
    在这里插入图片描述
    注意:Unity从上到下画层,因此背景中的任何内容都将位于列表的顶部。

    现在,草将永远被植物和僵尸所吸引。

    3.草皮音效

    稍后,在处理“构建”菜单时,我们需要一种方法来确定是否单击了草瓦。在United中有各种不同的方法来实现这一点,但最简单的方法是只使用OnMouseUpAsButton函数,如果单击了草,则由United自动调用该函数。

    现在这里有一件事要记住:只有当游戏对象有对撞机时,Unity才能做到这一点。所以让我们选择添加组件->物理二维->Box Collider 2D在Inspector并启用Is Trigger 选项:

    • Is Triggers
      在这里插入图片描述
      注意:Is Trigger意味着草会收到各种各样的碰撞信息,但实际上不会发生碰撞。所以,如果僵尸走进草瓦,它就不会与它相撞。

    复制草
    现在我们的游戏中只有一片草瓦。让我们在Hierarchy,选择Duplicate然后把它放在(1, 0)…然后我们将再次复制它,并将其放置在(2, 0)…我们会继续复制,直到我们有10 * 5左下角瓷砖位于(0, 0)右上方的瓷砖在(9, 4):

    在这里插入图片描述
    注意:重要的是所有的瓷砖都有圆形的位置(2, 3)从来没有(2.01, 3.023).

    4.生命值脚本

    僵尸应该能够攻击植物,并且开火的植物也可以攻击讲师。

    我们需要创建一个Health.cs

    我们可以通过右键单击项目区然后选择Create->C#脚本:
    在这里插入图片描述
    我们给它起个名字Health.cs然后拖入到新的Scripts文件夹:
    在这里插入图片描述
    我们可以通过双击脚本打开并修改脚本:

    using UnityEngine;
    using System.Collections;
    
    public class Health : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
        
        }
        
        // Update is called once per frame
        void Update () {
        
        }
    }
    

    我们不需要Start或者Update函数,所以让我们移除这两个函数。
    相反,我们将添加一个INT变量cur,该变量跟踪当前的生命值,并添加一个减少该变量的函数。
    如果当前的生命值低于0然后,应摧毁该实体:

    using UnityEngine;
    using System.Collections;
    
    public class Health : MonoBehaviour {
        // Current Health
        [SerializeField]
        int cur = 5;
    
        public void doDamage(int n) {
            // Subtract damage from current health
            cur -= n;
    
            // Destroy if died
            if (cur <= 0)
                Destroy(gameObject);
        }
    }
    

    注:我们使用[SerializeField]属性让Unity知道我们希望能够在Inspector面板中修改cur变量。这通常这是通过public访问权限,但在这种情况下,我们不希望其他脚本能够访问cur变量。相反,他们应该使用doDamage函数。
    该脚本稍后将添加到所有植物和僵尸。

    5.创建向日葵植物

    向日快图片

    由于我们正在开发一个2D游戏,动画是非常容易的。

    对于我们的向日葵,我们只需要一个闲置的动画,它的头部轻微移动:
    在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    让我们在项目区然后在Inspector面板中修改导入设置

    在这里插入图片描述
    这个Filter Mode和Format影响图片的分辨率。
    设置Sprite Mode为Multiple告诉Unity,这一张照片可以分割成几个向日葵。

    切片

    我们可以打开Inspector面板中按Sprite Editor按钮

    之后,我们可以在Sprite Editor中看到我们的向日葵:在这里插入图片描述
    我们要做的就是打开Slice菜单,将类型设置为格网像素大小为32 x 32…之后,我们按下Slice按钮:
    在这里插入图片描述
    让我们按Apply然后关闭 Sprite Editor

    如果我们看看项目区然后我们可以看到我们的向日葵现在有两个子节点:
    在这里插入图片描述

    6.向日葵动画

    从这两个slices创建一个Unity动画是非常容易的。我们所要做的就是在项目区然后把他们拖到场景中:
    在这里插入图片描述
    一旦我们把它拖到场景中,Unity会问我们在哪里保存动画。
    我们可以在我们的项目区创造一个新的SunflowerAnimation文件夹,然后将其保存为idle.anim.

    Unity会为我们创建两个文件:
    在这里插入图片描述
    这就是我们的动画,如果我们按下Play:
    在这里插入图片描述
    注意:我们可以在SunflowerAnimation文件夹中通过双击sunflower_0,选择idle状态,然后可以在Inspector面板更改speed

    7.添加物理、标签与生命值

    我们的植物应该是物理世界的一部分,这意味着我们必须分配一个Collider给它。
    一个Collider确保物体会与植物发生碰撞,而植物也会与其他物体发生碰撞。

    让我们选择植物,然后在Inspector面板按下添加组件->Physics 2D->Box Collider 2D:
    在这里插入图片描述
    我们还需要找出某个游戏对象是一个植物,一个僵尸,还是完全不同的东西。
    这就需要Unity标签系统了。
    如果我们看看Inspector,我们可以看到当前标记是Untagged:
    在这里插入图片描述
    我们选择Plant标签。
    在标记列表中,然后添加Plant标记到可用标签列表,
    再次选择plant,然后从标记列表中分配标记:
    在这里插入图片描述
    最后在Inspector那里添加组件->Scripts->Health
    僵尸可以在稍后对草地plant造成伤害:

    在这里插入图片描述
    注:我们的向日葵刚刚被随意放置在现场的某个地方。我们将把它保存在那里,下一步我们将生成阳光

    5.生成阳光

    画阳光
    向日葵植物每隔几秒钟就会孕育出一个新的阳光,所以让我们先画一个:
    在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    我们将使用以下方法导入设置:
    在这里插入图片描述
    Foreground Layer前景层
    我们希望太阳被画在背景前面和植物前面。让我们点击添加排序层。
    再一次创造另一个分选层,说出来前景并将其移至列表的底部:
    在这里插入图片描述
    之后,我们可以再次选择sun并分配Foreground排序层:
    在这里插入图片描述
    创建预制件

    让我们把阳光拖到Hierarchy一次,创建一个游戏对象:
    在这里插入图片描述
    然后按下添加组件->Physics 2D>Circle Collider 2D
    碰撞器可以让阳光可以接收碰撞信息:
    在这里插入图片描述
    注意:在实际的植物大战僵尸游戏中,阳光并没有真正地与植物或僵尸发生碰撞。我们可以通过在Circle Collider 2D中勾选Is Trigger。这意味着阳光接收到碰撞信息,但从未真正与任何物体发生碰撞。僵尸将能够穿过它,而不是与它相撞。

    之后,我们创建一个新的文件Prefabs,将sun拖到Prefabs文件中创建一个预制体:
    在这里插入图片描述
    现在我们可以将sun从场景中删除。

    注意:稍后将通过使用实例化功能生成阳光。

    8.创建生成脚本

    我们希望向日葵每隔几秒钟产生一个新的阳光。这种行为可以通过脚本实现。让我们选择向日葵,然后点击添加组件->New Script:
    在这里插入图片描述
    我们给它起个名字SunSpawn,选择C#作为语言
    然后将其移动到项目的Scrips文件夹中。之后,我们将打开它:

    using UnityEngine;
    using System.Collections;
    
    public class SunSpawn : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
        
        }
        
        // Update is called once per frame
        void Update () {
        
        }
    }
    

    我们将添加一个公共GameObject变量,允许我们稍后在Inspector中指定预制体:

    public class SunSpawn : MonoBehaviour {
        public GameObject prefab;
        ...
    

    这个实例化函数允许我们将预置体加载到场景中。
    这个InvokeRepeting函数允许我们从某个时候开始,重复调用某个函数。
    例如,如果我们想在1秒内第一次生成,然后每2秒重复一次,我们将使用
    InvokeRepeting(“Spawn”,1,2)
    我们希望在10秒内产生第一个阳光,然后每10秒继续生成更多的阳光,下面是我们的代码:

    using UnityEngine;
    using System.Collections;
    
    public class SunSpawn : MonoBehaviour {
        public GameObject prefab;
    
        // Use this for initialization
        void Start() {
            // Spawn first Sun in 10 seconds, repeat every 10 seconds
            InvokeRepeating("Spawn", 10, 10);
        }
        
        void Spawn() {
            // Load prefab into the Scene
            // -> transform.position means current position
            // -> Quaternion.identity means default rotation
            Instantiate(prefab,
                        transform.position,
                        Quaternion.identity);
        }
    }
    

    现在我们可以保存脚本并再一次查看Inspector面板。
    脚本有一个预制体插槽,因为我们的预制体变量是公开的。
    让我们拖着我们的SunPrefab从项目区到预制件脚本的插槽:
    在这里插入图片描述
    如果我们按下Play然后我们每10秒就能看到一个新的太阳生成。

    阳光移动
    阳光生成后,它应该慢慢地消失在屏幕的顶端。我们将使用 Rigidbody 2D。
    刚体通常被用于物理世界中的任何东西,而这些东西应该是在移动的。

    让我们在Inspector那里单击添加组件->Physics2D->Rigidbody2D。我们将为它分配以下属性:

    在这里插入图片描述
    现在我们要做的就是给刚体一些速度(这是movement direction * speed)…以下图像显示某些运动方向所需的不同矢量:
    在这里插入图片描述
    我们将创建另一个C#脚本,命名为DefaultVelocity然后一直用它来设定速度:

    using UnityEngine;
    using System.Collections;
    
    public class DefaultVelocity : MonoBehaviour {
        // The Velocity (can be set from Inspector)
        public Vector2 velocity;
    
        void FixedUpdate () {
            GetComponent<Rigidbody2D>().velocity = velocity;
        }
    }
    

    注意:我们在每个FixedUpdate调用此函数,以便无论发生什么情况,该脚本所附加的内容都将尝试继续移动。这对于僵尸来说是很有用的,当他们跑进一个植物时,可能无法继续移动,但是我们的脚本将确保他们在植物被摧毁后再次开始移动。

    之后,我们再次选择Sun预置,然后单击添加组件->Scripts->Default Velocity
    我们指定一个速度向上 (进入y方向):

    在这里插入图片描述
    如果我们按下Play然后,我们现在可以看到太阳阳光生成,然后向上移动:
    在这里插入图片描述
    收集阳光
    当我们谈到阳光的时候,我们必须做的最后一件事就是让Player收集它。
    我们将需要一个全局变量得分,以跟踪多少阳光被收集,每当玩家点击阳光,我们将增加这个数的大小。

    Unity有Onmousedown函数,该函数确定我们是否单击了GameObject。
    所以让我们创建一个新的C#脚本SunCollect然后使用Onmousedown函数:

    using UnityEngine;
    using System.Collections;
    
    public class SunCollect : MonoBehaviour {
        // Global score
        public static int score = 100;
    
        void OnMouseDown() {
            // Increase Score
            score += 20;
    
            // Destroy Sun
            Destroy(gameObject);
        }
    }
    

    注:我们必须使用静态以使分数变量全局化。这意味着其他脚本可以使用SunCollection.score获取到数值。最初的分数是100,所以玩家有一些阳光来开始建造植物。

    一旦我们将脚本添加到SunPrefab中,我们就可以按Play,等待太阳生成阳光,然后点击它收集它。

    清理层级
    现在向日葵还在场景中,但我们不希望它从一开始就在那里。
    游戏应该在启动后自动生成它。
    因此,让我们从它拖到Prefabs文件夹:
    在这里插入图片描述

    6.植物开火

    植物图片
    好吧,让我们再创造一种植物,这种植物能够向僵尸射击。和往常一样,我们将从绘制所有动画开始。顶部的两个切片是空闲动画,底部的两个切片是开火动画:
    在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    导入设置
    在这里插入图片描述
    点击Sprite Editor,然后使用以下设置对其进行切片:
    在这里插入图片描述

    7.植物动画

    创造动画
    这一次,我们有两个动画在我们的形象。前两个切片是空闲动画,最后两个切片是开火动画。

    将前两个切片拖入到场景中然后创建空闲动画:
    在这里插入图片描述
    命名它idle.anim并将其保存在一个新FiringplantAnimation文件夹中

    射击动画的操作方式也是一样的:
    在这里插入图片描述
    如果我们按下Play然后我们可以在游戏中看到我们的两个动画:
    在这里插入图片描述
    注意:它们仍然是两个不同的游戏对象,但是我们很快就会处理好的。

    清理对象
    让我们在层级面板上删除firingplant_2,然后去项目区删除firingplant_2动画控制器,删完以后的样子:
    在这里插入图片描述
    在这里插入图片描述
    注意:这么做只是为了整洁,不删除也没有影响

    修改动画控制器
    在Project区,双击firingplant_0动画控制器,打开动画控制器,可以看到当前状态机firingplant_0在项目区:
    在这里插入图片描述
    现在我们看到的就是闲散状态。如果我们想让它在某个时候播放射击动画,那么我们将再创建一个状态,我们将firing.anim动画也拖入到动画控制器:
    在这里插入图片描述
    我们需要某种参数来帮助Unity确定何时进入射击状态。
    我们将选择参数选项卡,然后在右边单击+,添加一个Trigger类型的参数给它起个名字IsFiring:

    在这里插入图片描述
    注意:触发器是只触发一次的参数。稍后,我们将在脚本中设置此参数。

    当IsFiring参数被触发,我们将切换到射击状态。让我们右键单击idle状态,选择Make Transition然后将白色箭头拖到firing状态:
    在这里插入图片描述
    之后,点击白色箭头,我们将禁用Has Exit Time属性并设置条件IsFiring:
    在这里插入图片描述
    注意:禁用Has Exit Time,是因为转换不应该在一段时间之后自动发生

    现在,我们再从firing到idle。这一次我们将开启Has Exit Time
    因为我们希望动画状态机从firing到idle在下一秒自动执行。
    我们还将设置Exit Time为1:
    在这里插入图片描述
    这是我们最后的状态机:
    在这里插入图片描述
    注意:状态机现在可以根据设置的IsFiring参数自动切换idle和firing状态

    8.植物的物理和Tag

    添加碰撞器
    在这里插入图片描述
    设置Tag为Plant:
    在这里插入图片描述
    添加Health.cs脚本给植物,以便僵尸稍后能够对植物造成伤害:
    在这里插入图片描述

    9.植物的子弹

    画个子弹
    我们将使用16x16px纹理作为子弹:

    在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    导入设置

    在这里插入图片描述
    子弹预制体
    我们将把子弹纹理拖到场景中,然后将其拖回项目区,它创建了一个Prefab:
    在这里插入图片描述
    之后,我们可以再次从层次面板中删除它。

    子弹物体
    添加碰撞器和刚体:
    在这里插入图片描述
    注意:我们启用了Is Trigger所以子弹会穿过植物而不是与它们相撞。

    发射子弹
    子弹一被实例化就应该飞向右边。
    我们已经有了一个脚本,通过使用刚体的velocity。
    所以让我们选择添加DefaultVelocity.cs脚本,指定一个速度让它向右飞:
    在这里插入图片描述
    注意:这是基于组件的开发的美妙之处。我们可以写一次脚本,然后重复使用它在我们的游戏中的各种实体。

    碰撞造成伤害

    为了检查碰撞,我们将使用Unity的OnTriggerEnter2D函数
    一旦子弹飞进另一个GameObject,就会调用该函数
    在这种情况下,我们将检查它是否是僵尸(通过比较标签)然后降低它的生命值。

    让我们创建一个新的C#脚本,命名为BulletDamage.cs然后双击打开,不需要Stat函数和Update函数删除,然后添加一个OnTriggerEnter2D函数:

    using UnityEngine;
    using System.Collections;
    
    public class BulletDamage : MonoBehaviour {
    
        void OnTriggerEnter2D(Collider2D co) {
            // Zombie?
            if (co.tag == "Zombie") {
                // Deal Damage, destroy Bullet
                co.GetComponent<Health>().doDamage(1);
                Destroy(gameObject);
            }
        }
    }
    

    注意:我们使用GetComponent找到僵尸上面的组件Health,然后我们通过调用doDamage功能。之后,我们通过以下方法将子弹从游戏中销毁

    我们可以通过选择Add Component->Scripts->Bullet Damage:
    在这里插入图片描述

    10.植物发射子弹

    既然我们有了子弹,我们就可以让我们的开火植物一看到僵尸就射它。
    我们新添加一个脚本Firing.cs,然后双击打开它:

    using UnityEngine;
    using System.Collections;
    
    public class Firing : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
        
        }
        
        // Update is called once per frame
        void Update () {
        
        }
    }
    

    首先,我们得找出植物右边是否有僵尸。
    我们可以通过使用RaycastAll功能。一个RaycastAll射出一条射线穿过这个世界,然后发现它是否击中了任何东西。
    所以,如果我们从屏幕的最右边向植物发射射线,如果它与标有“僵尸”标签的物体相撞,那么植物就应该在发射子弹。

    这是我们的功能:

    bool zombieInFront() {
        // Raycast from the right of the game to the plant
        Vector2 origin = new Vector2(9.5f, transform.position.y);
        RaycastHit2D[] hits = Physics2D.RaycastAll(origin, -Vector2.right);
    
        // Find out if any Zombie was hit
        foreach (RaycastHit2D hit in hits) {
            if (hit.collider != null &&
                hit.collider.gameObject.tag == "Zombie")
                return true;
        }
        return false;
    }
    

    注意:RaycastAll函数返回所有点击,而不像Raycast函数那样只返回第一个点击。我们也可以从植物向屏幕右侧射出一束光线。但是从右向植物发射更好,因为如果我们给植物增加一个碰撞器,我们就会遇到麻烦,在这种情况下,射线只会击中植物本身。我们使用-Vector2.right。因为它是向左。我们使用向量(9.5f, pos.y)作为光线投射的位置,因为这是草的右边界,还有植物的高度。

    我们还需要一个公共的Prefab变量,这个变量将被设置为子弹预制体:

    public class Firing : MonoBehaviour {
        // The Bullet Prefab
        public GameObject bulletPrefab;    
        ...
    }
    

    现在我们可以写我们的Shoot函数,实例化生成子弹,并设置IsFiring参数在我们的动画状态机中,
    每当它看到僵尸时:

    void Shoot() {
        if (zombieInFront()) {
            // Animation
            GetComponent<Animator>().SetTrigger("IsFiring");
    
            // Instantiate Bullet
            Instantiate(bulletPrefab, transform.position, Quaternion.identity);
        }
    }
    

    如果我们把它们放在一起的话,情况是这样的:

    using UnityEngine;
    using System.Collections;
    
    public class Firing : MonoBehaviour {
        // The Bullet Prefab
        public GameObject bulletPrefab;
        
        // Shooting Interval
        public float interval = 0.5f;
    
        // Use this for initialization
        void Start () {
            // Try to shoot every few seconds
            InvokeRepeating("Shoot", 0, interval);
        }
    
        bool zombieInFront() {
            // Raycast from the right of the game to the plant
            Vector2 origin = new Vector2(9.5f, transform.position.y);
            RaycastHit2D[] hits = Physics2D.RaycastAll(origin, -Vector2.right);
    
            // Find out if any Zombie was hit
            foreach (RaycastHit2D hit in hits) {
                if (hit.collider != null &&
                    hit.collider.gameObject.tag == "Zombie")
                    return true;
            }
            return false;
        }
        
        void Shoot() {
            if (zombieInFront()) {
                // Animation
                GetComponent<Animator>().SetTrigger("IsFiring");
    
                // Instantiate Bullet
                Instantiate(bulletPrefab, transform.position, Quaternion.identity);
            }
        }
    }
    

    然后我们点击植物,将我们的预制体子弹拖入到预制体插槽中:
    在这里插入图片描述
    注意:一旦我们加入僵尸,我们就可以尝试一下射击了。

    然后将植物firingplant拖入到prefab文件夹,做成一个预制体。

    11.生成菜单

    在这里插入图片描述
    现在我们创建菜单,并实现需要的功能。

    Build Info组件
    我们的建造菜单将需要每个植物的价格和一个小预览图像。
    现在这种数据必须存储在某个地方。
    要做到这一点,最好的方法是将其存储在每个植物的预制体中。

    让我们选择我们的两个植物预制体,然后点击添加组件,新脚本,命名为BuildInfo,然后双击打开:

    using UnityEngine;
    using System.Collections;
    
    public class BuildInfo : MonoBehaviour {
        public Texture previewImage;
        public int price;
    }
    

    我们将使用以下预览图像:
    在这里插入图片描述在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    导入设置选中两幅图片:
    在这里插入图片描述
    然后我们分别选择我们的Build Info组件,分配以下属性:
    在这里插入图片描述
    向日葵
    在这里插入图片描述
    寒冰射手

    12.绘制菜单

    为了显示菜单名,我们将使用OnGUI函数:

    • 画阳光和设置阳光分数
    • 每种植物
    • 画出植物的样子和需要的阳光数量

    让我们先梳理一下步骤:

    • 使用GUILayout将菜单定位在最上面的中心
    • 使用Horizontal 画出框框(带有方框式)
    • 画阳光图像和阳光的分数
    • 画出每个植物
    • 绘制按钮预览图像和价格

    注意:GUILayout.Button只能显示一个图像或者一个文本,不能两个都显示,我们希望显示预览图和价格,我们将使用GUIContent

    我们点击Main Camera,选择添加组件,新建一个脚本,命名为BuildMenu.cs

    这是我们的BuildMenu脚本:

    using UnityEngine;
    using System.Collections;
    
    public class BuildMenu : MonoBehaviour {
        // Sun Image
        public Texture sunImage;
    
        // Plant Prefabs
        public BuildInfo[] plants;
    
        void OnGUI() {
            // Begin Gui
            GUILayout.BeginArea(new Rect(Screen.width/2 - 100, -7, 200, 100));
            GUILayout.BeginHorizontal("box");
    
            // Draw the Sun
            GUILayout.Box(new GUIContent(SunCollect.score.ToString(), sunImage));
    
            // Draw each Plant's BuildInfo
            foreach (BuildInfo bi in plants) {
                GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage));
            }
    
            // End Gui
            GUILayout.EndHorizontal();
            GUILayout.EndArea();
        }
    }
    

    注意:植物预制板是一个数组(因此[ ])。这意味着这不仅仅是一个而是一组。

    保存脚本后,我们将把我们的太阳图像和植物预制板从我们的项目区进入脚本的插槽:
    在这里插入图片描述
    如果我们按下Play然后我们就可以看到我们的菜单了:
    在这里插入图片描述
    启用和禁用按钮
    现在有更多的东西要在这里实现。首先,每个按钮应该启用,只有当玩家收集到足够的太阳支付它。这可以很容易地通过使用GUI.enabled:

    // Draw each Plant's BuildInfo
    foreach (BuildInfo bi in plants) {
        GUI.enabled = SunCollect.score >= bi.price;
        GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage));
    }
    

    注意:这会将一个bool值(真或假)分配给GUI.enabled。bool值是通过判断SunCollect.score是否植物的价格要高来判断是否为真。

    如果我们按下Play,我们就可以看到我们的按钮被禁用,除非我们有足够的阳光:

    13.生成植物

    让我们来处理最重要的部分:生成植物。
    玩家应首先单击“生成”菜单中的“植物”按钮,然后单击“草地”。之后,植物应该建在那块草地上。

    因此,首先,我们需要跟踪玩家想要建造的植物:

    // Currently building...
    public static BuildInfo cur;
    

    注:public static因为我们希望以后能够从另一个脚本访问它。

    一旦玩家点击一个按钮,我们就会设置cur变量为玩家想要创建的植物:

    // Draw each Plant's BuildInfo
    foreach (BuildInfo bi in plants) {
        GUI.enabled = SunCollect.score >= bi.price;
        if (GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage)))
            cur = bi;
    }
    

    14.草地的脚本

    现在,我们将不得不等待玩家点击草地。
    如果他点击了一个,那么我们想要构建BuildInfo.cur 植物。
    让我们选择场景中的所有草块,然后单击Add Component->New Script。
    我们给它起个名字Grass,再一次将它移到我们的Scripts 文件夹中,然后打开它。
    我们将使用OnMouseUpAsButton函数以确定是否单击了草地:

    using UnityEngine;
    using System.Collections;
    
    public class Grass : MonoBehaviour {
    
        void OnMouseUpAsButton() {
            // Build Stuff
        }
    }
    

    现在我们将找出是否有什么东西要建,如果有,我们将建造它,并让玩家支付阳光:

    using UnityEngine;
    using System.Collections;
    
    public class Grass : MonoBehaviour {
    
        void OnMouseUpAsButton() {
            // Is there something to build?
            if (BuildMenu.cur != null) {
                // Build it
                Instantiate(BuildMenu.cur.gameObject, transform.position, Quaternion.identity);
                SunCollect.score -= BuildMenu.cur.price;
                BuildMenu.cur = null;
            }
        }
    }
    

    注意:我们可以从任何位置访问BuildMenu的cur变量。这起作用是因为public static。我们用实例化使用草地的位置(transform.position),默认的旋转(Quaternion.identity)。之后我们清除cur变量,因为我们已经完成了创建。

    如果我们按下Play然后我们现在可以创建一些植物:
    在这里插入图片描述

    15.僵尸

    画僵尸
    现在我们一直在等待的是:僵尸:
    和往常一样,我们将从绘制一个包含所有动画帧的图像开始。第一行包含行走动画,第二行包含攻击动画:
    在这里插入图片描述
    注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

    导入设置:
    在这里插入图片描述
    图像将被切片为32 x 32网格:
    在这里插入图片描述

    16.僵尸动画

    让我们创建moving动画,通过选择前4个切片并将它们拖到场景中来创建动画:
    在这里插入图片描述
    我们会把它命名为moving.anim存放在一个新的ZombieAnimation文件夹

    接下来的两片将是attacking动画:
    在这里插入图片描述
    我们会把它命名为attacking.anim存放在一个新的ZombieAnimation文件夹

    在这里插入图片描述
    清理
    与往常一样,我们将在场景中删除zombie_4,在项目文件中删除zombie_4的动画状态机

    17.僵尸动画状态机

    我们前面已经创建了很多次状态机了,已经很熟悉了:
    在这里插入图片描述
    参数是:

    • Trigger类型的IsAttacking变量

    这些Transitions 是:

    • 进攻:=IsAttacking
    • 进攻到移动:= Exit Time 1.0

    这意味着只要我们说IsAttacking,动画状态机将播放进攻动画一秒钟。然后它会自动回到移动状态。

    18.僵尸物理

    我们的僵尸应该是物理世界的一部分,这意味着它需要一个BoxCollider2D。
    它也应该四处移动,这意味着它需要一个Rigidbody 2D:

    在这里插入图片描述

    19.僵尸移动

    僵尸应该向左走。当然,我们可以再次使用我们的DefaultVelocity脚本,选择Add Component->Scripts->Default Velocity然后分配一个速度,让它慢慢地向左走:
    在这里插入图片描述

    20.僵尸攻击脚本

    一旦僵尸与植物相撞,它就应该每秒钟攻击一次。

    添加一个新脚本ZombieAttacking.cs,然后双击打开,不需要Start函数和Update函数,删除,然后添加OnCollisionStay2D函数:

    using UnityEngine;
    using System.Collections;
    
    public class ZombieAttacking : MonoBehaviour {
    
        void OnCollisionStay2D(Collision2D coll) {
            // Collided with a Plant?
            if (coll.gameObject.tag == "Plant") {
                // Do Stuff...
            }
        }
    }
    

    僵尸站在植物前应播放攻击动画:

    void OnCollisionStay2D(Collision2D coll) {
        // Collided with a Plant?
        if (coll.gameObject.tag == "Plant") {
            // Play Attack Animation
            GetComponent<Animator>().SetTrigger("IsAttacking");
        }
    }
    

    它也应该对植物造成伤害,但每秒钟只能造成一次:

    using UnityEngine;
    using System.Collections;
    
    public class ZombieAttacking : MonoBehaviour {
        // Last Attack Time
        float last = 0;
    
        void OnCollisionStay2D(Collision2D coll) {
            // Collided with a Plant?
            if (coll.gameObject.tag == "Plant") {
                // Play Attack Animation
                GetComponent<Animator>().SetTrigger("IsAttacking");
                // Deal damage once a second
                if (Time.time - last >= 1) {
                    coll.gameObject.GetComponent<Health>().doDamage(1);
                    last = Time.time;
                }
            }
        }
    }
    

    注意:我们只是用Time.time找出从那时起已经过去了多少时间最后的时间,然后进入植物 Health脚本,调用doDamage函数并重置最后的时间到了。

    还记得我们是如何检查僵尸射击和子弹记录上的标签?
    让我们添加僵尸Tag到我们的僵尸,以确保它可以被子弹射中:
    在这里插入图片描述
    我们还将添加一个Health脚本:
    在这里插入图片描述
    如果我们按下Play建造一个向日葵
    然后我们可以看到僵尸将如何尝试攻击到它。
    如果我们建了一个寒冰射手,那么我们就可以看到它是如何在几枪之后杀死僵尸的!

    我们还将通过复制几个僵尸,他们将僵尸定位在水平的右边,这样他们在步行几秒钟后才能到达:
    在这里插入图片描述
    如果我们按下Play然后我们就可以看到游戏的行动:
    在这里插入图片描述

    21.摘要

    我们刚刚创造了一个干净而简单的植物大战僵尸游戏。
    该教程相当长,但仍然有许多功能和改进可以添加到游戏。
    和往常一样,现在是你让游戏变得有趣的时候了!

    展开全文
  • 众所周知,Unity3D是一个能够实现轻松创作的多平台的游戏开发工具,是一个全面整合的专业游戏引擎。在现有的版本中,其强大的游戏制作功能已经达到和其他顶级游戏引擎媲美的地步。但是 其制作并不如想象中的困难。...
  • Unity3D开发小游戏】《愤怒的小鸟》开发教程 2019年09月11日 10:34:36恬静的小魔龙阅读数 1698更多 分类专栏:Unity3D日常Unity3d手游开发Unity3D开发小游戏 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA...
  • 1.如何开始 打开Visual Studio,左上角:文件—新建—项目,选择以下项 创建即可。 创建该项目的同时会创建一个解决方案,一个解决方案下可以有多个项目,每个项目下可以有多个类。 要打开一个解决方案,打开....
  • Unity3D也算是好久了,但是每次做项目总还是能学到新的东西。这次做一个TPS的项目就遇到了这样一个问题,如何同时在上下半身播放不同的动画?解决方法其实是很简单,但由于对于动画资源的了解不足导致问题不断,...
  • 尤其是它在3.0版本里面制作的那款第一人称战争游戏,画质效果丝毫不逊色于当下十分流行的《穿越火线》、《战地之王》等主流 第一人称射击游戏。下图为Demo中的显示效果: 看到如此绚丽的效果,让我马上走进精彩...
  • Unity3D插件大全

    2019-05-07 00:26:04
    【转】...200个插件免费分享约5G。 【清单如下】 2D_Toolkit_1.51动画开发插件包 FingerGestures触摸插件 ORK_Okashi_RPG_Kit Unity3D的角色扮演游戏开发工具包 uScript视觉脚本工具Un...
  • 例如在射击游戏中子弹是否击中敌人,在RPG游戏中是否捡到装备等等。在进行碰撞检测时,我们最常用的工具就是射线,Unity 3D的物理引擎也为我们提供了射线类以及相关的函数接口。本文将对射线的使用进行一个总结。 ...
  • http://unity3d.9ria.com/?p=22 ...尤其是它在3.0版本里面制作的那款第一人称战争游戏,画质效果丝毫不逊色于当下十分流行的《穿越火线》、《战地之王》等主流第一人称射击游戏。下图为Demo中的显示效果:
  • 例如在射击游戏中子弹是否击中敌人,在RPG游戏中是否捡到装备等等。在进行碰撞检测时,我们最常用的工具就是射线,Unity 3D的物理引擎也为我们提供了射线类以及相关的函数接口。本文将对射线的使用进行一个总结。 ...
  • 花了一天的时间,照着官方的说明将spaceshoot这个DEMO撸了一遍,实现了一个简单飞行射击游戏应有的小功能,本想着用Unet这个组件把这个游戏做成一个联网的,无奈只能实现飞机运动之间的同步,其余部分画面都是不同步...
  • 今天博主想和大家聊聊unity3D中各种坐标系。自从Unity4.6版本推出uGUI后,unity3d坐标系的大家庭中便增加了RectTransform这个新成员,如果你不想被各种坐标系搞得晕头转向的话,那么请随我一起来梳理下Unity3D中各种...
  • Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三视频游戏、建筑可视化、实时三动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。Unity类似于Director,...
  • Unity3D之射线检测

    2017-08-08 17:01:35
    开发中我们经常遇到鼠标和三物体交互的需求,要操作三空间的物体,首先要选中这个物体,这时候就需要我们经常用到的射线检测。 一.单射线检测单个物体 代码使我们通用的射线检测代码,注释也已经标识上去了。 //...
  • 使用Unity3D进行网络游戏开发 一.Unity3d简介  Unity3d是时下比较流行的一款游戏引擎,流行是因为用它做游戏很方便,无论是3d还是2d都会有非常好的效果,  即便某些朋友不懂编程,也可以通过Unity自带的组件做出...
  • 游戏中最复杂的部分是物理系统,但是多亏了Unity,我们就不用担心太多了。使用Unity将使它如此容易,我们将只需要大约100行的代码! 像往常一样,一切都会尽可能简单地解释,这样每个人都能理解它。 以下是项目的...
1 2 3 4 5 ... 13
收藏数 259
精华内容 103
关键字:

unity3d 二维射击游戏