11、子弹朝向问题的解决

在坦克大战的学习笔记上中,我们解决了子弹的朝向问题,这里就不介绍了,直接进入下一节的学习。

12、加入攻击CD,触发器与碰撞器的区别

在解决了子弹朝向问题后,我们现在就需要让子弹动起来了,对子弹添加一个脚本控制他的运动。

    public float moveSpeed = 10;//子弹移动速度

    void Update()
    {
        transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);
    }

然后在PlayerAI里面加入攻击CD的功能,我们在Update里面调用攻击函数,不在FixedUpdate里面调用了:

    private float timeVal;//子弹发射计时器

    void Update()//每一帧的处理时间不同
    {
        if (timeVal >= 0.4f)
        {
            TankAttack();
        }
        else
            timeVal += Time.deltaTime;
    }

    private void TankAttack()//坦克攻击函数
    {
        if(Input.GetKeyDown(KeyCode.Space))//如果玩家按下了空格键
        {
            //子弹的角度=坦克的角度+子弹应该旋转的角度
            Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+ bulletEulerangles));//子弹进行实例化
            timeVal = 0;
        }
    }

然后就可以看到坦克攻击有CD限制,不能连续发射导弹:

因为我们不需要子弹有碰撞的效果,所以我们给子弹设置触发器组件。碰撞器顾名思义是检测碰撞的,在碰撞时会产生力的效果,常常会阻挡物体通过,而触发器则是在碰撞的时候响应一个特定的函数,不一定会阻碍物体通过,往往是一些别的操作。

我们给Bullet的预制体添加触发器和刚体组件:

                                   

然后进入到BulletAI脚本进行触发器触发函数的编写,因为我们是2D游戏,所以主要通过OnTriggerEnter2D、OnTriggerExit2D和OnTriggerStay2D这三个函数进行实现。

13、制作空气墙,添加标签

为了能够实现子弹碰到不同物体有不同的反映,我们需要对场景内的物体设置标签,遇到了某个物体,通过标签识别是什么对象,然后实现相应的方法与事件。

标签的设定主要通过Tag的Add Tag进行新建标签,我们设置一些需要的标签,在Tag换上对应的标签即可。

然后我们现在给游戏做一个空气墙边界,让坦克和子弹都无法透过空气墙,我们复制Barrier的预制体,把他的Sprite Renderer组件给移除掉,放在四周,这样就形成了空气墙。

14、编写玩家无敌方法和死亡方法

我们首先编写一下玩家死亡的方法,挡子弹触碰到Tank也就是我们玩家时,会死亡,我们在PlayerAI里面写入坦克死亡函数,死亡时还会有爆炸特效,所以我们把死亡后爆炸特效也实现一下。

    public GameObject explosionPrefab;//爆炸特效的预制体

    private void TankDie()//坦克的死亡方法
    {
        if (isDefended)//如果玩家无敌则不会死亡
            return;

        //产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);

        //死亡
        Destroy(gameObject);
    }

然后在BulletAI脚本里面的碰撞检测函数里面调用方法

    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch(collision.tag)//根据碰撞物体的标签来响应事件
        {
            case "Barrier":
                break;
            case "Grass":
                break;
            case "Heart":
                break;
            case "Wall":
                break;
            case "Tank":
                collision.SendMessage("TankDie");
                break;
            case "Enemy":
                break;
            default:
                break;
        }
    }

另外爆炸特效在爆炸完之后还要进行销毁,我们新建一个脚本挂到爆炸特效的预制体上

    void Start()
    {
        Destroy(gameObject, 0.167f);//在0.167s的爆炸后删除特效
    }

这样我们就完成了死亡的函数,接下来坦克在开始出生时会有无敌的时间,我们设置一下,并且无敌时会有外面的一个护盾特效,我们也实现一下护盾的效果:

    private bool isDefended=true;//玩家是否无敌状态
    private float defendTimeVal=0;//玩家无敌时间

    public GameObject defendPrefab;//保护特效的预制体

    void Update()//每一帧的处理时间不同
    {
        //是否无敌
        if(isDefended)
        {
            defendPrefab.SetActive(true);//无敌则显示护盾特效
            defendTimeVal += Time.deltaTime;
            if (defendTimeVal>=3)//无敌时间过了就设置状态为不无敌
            {
                defendPrefab.SetActive(false);//取消护盾显示
                isDefended = false;
                defendTimeVal = 0;
            }
        }

        //设置攻击CD为0.4s
        if (timeVal >= 0.4f)
        {
            TankAttack();
        }
        else
            timeVal += Time.deltaTime;
    }

我们将护盾预制体拖到玩家的预制体身上,然后当无敌时间存在时让他显示,无敌事件过了就不显示,可以看到3s后护盾消失。

                                           

15、区别玩家与敌人的子弹

玩家的子弹碰到玩家时应该是不会造成死亡的,敌人的子弹碰到敌人也不会死亡,那么我们就需要区分子弹时敌人的还是玩家的,我们在BulletAI脚本里面设置一个isPlayerBullet变量用于区别。

    public bool isPlayerBullet;//判断是否为玩家的子弹

    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch(collision.tag)//根据碰撞物体的标签来响应事件
        {
            case "Barrier":
                Destroy(gameObject);//销毁子弹
                break;
            case "AirBarrier":
                Destroy(gameObject);//销毁子弹
                break;
            case "Grass":
                break;
            case "Heart":
                collision.SendMessage("HeartDie");
                break;
            case "Wall":
                Destroy(collision.gameObject);//销毁墙
                Destroy(gameObject);//销毁子弹
                break;
            case "Tank":
                if(isPlayerBullet)
                    collision.SendMessage("TankDie");
                break;
            case "Enemy":
                break;
            default:
                break;
        }
    }

然后我们新建一个脚本挂载到我们的老家Heart上,用来检测是否被攻击了,攻击则触发死亡事件

    private SpriteRenderer spriteRenderer;//渲染的组件
    public Sprite HeartBroken;//死亡图片

    // Start is called before the first frame update
    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();//获取渲染的组件
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void HeartDie()//老家中枪,游戏结束
    {
        spriteRenderer.sprite = HeartBroken;
    }

下面是效果:

16、制作敌人的子弹

我们将之前的子弹预制体分为敌人的和玩家的,分别设置IsPlayerBullet属性却分开来:

                                 

17、制作敌人,添加玩家出生效果

我们首先创建敌人的预制体,选择好渲染的图片源,生成两个不同的敌人。然后添加脚本EnemyAI来控制敌人的移动:

 

我们先完善一下之前子弹攻击完老家之后的一个爆炸效果,在HeartAI脚本里面进行编写:

    public GameObject explosionPrefab;//爆炸特效的预制体

    public void HeartDie()//老家中枪,游戏结束
    {
        spriteRenderer.sprite = HeartBroken;
        Instantiate(explosionPrefab, transform.position, transform.rotation);
    }

接着我们添加一下玩家出生的一个特效,新建一个Born脚本,挂载到Born特效上:

    //引用
    public GameObject playerPrefab;//玩家的预制体

    // Start is called before the first frame update
    void Start()
    {
        Invoke("TankBorn", 1f);//延迟调用1s后产生坦克
        Destroy(gameObject, 1f); //延迟1s销毁出生特效
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void TankBorn()//产生坦克
    {
        Instantiate(playerPrefab, transform.position, Quaternion.identity);//实例化玩家
    }

然后将玩家的预制体拖到脚本的playerPrefab上,就可以在出生特效那里产生玩家了。

                                  

18、用同样的产生特效随机生成两种敌人

敌人制作好了,我们需要用同样的产生特效也生成敌人。

敌人的AI运动我们先暂时copy一份玩家的AI,然后给敌人设置必要的一些预制体:

                                    

然后我们修改Born脚本,并且给Born挂上敌人的预制体:

    //属性
    public bool createPlayer;//是否产生玩家

    //引用
    public GameObject playerPrefab;//玩家的预制体
    public GameObject[] enemyPrefabList;//敌人预制体的列表

    // Start is called before the first frame update
    void Start()
    {
        Invoke("TankBorn", 1f);//延迟调用1s后产生坦克
        Destroy(gameObject, 1f); //延迟1s销毁出生特效
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void TankBorn()//产生坦克
    {
        if(createPlayer)
            Instantiate(playerPrefab, transform.position, Quaternion.identity);//实例化玩家
        else
        {
            int num = Random.Range(0, 2);
            Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);//实例化敌人
        }
    }

 通过勾选CreatePlayer来设置是让敌人生成还是玩家生成:

                                                          

19、编写敌人AI

这小节我们主要来编写敌人的AI,敌人的移动肯定不能通过玩家控制,而是需要他自己移动或者攻击,我们通过编写EnemyAI实现。

    //属性值
    public float moveSpeed = 3;//坦克的移动速度
    private Vector3 bulletEulerangles;//子弹应该旋转的欧拉角
    private float v;//敌人垂直位移
    private float h;//敌人水平位移

    //计时器
    private float timeVal=0f;//子弹发射计时器
    private float timeValE;//敌人子弹发射计时器
    private float DirChangeTime=4f;//改变方向的时间间隔


    //引用
    private SpriteRenderer spriteRender;//坦克的渲染组件
    public Sprite[] tankSprite;//坦克的精灵 上右下左
    public GameObject bulletPrefab;//子弹的预制体
    public GameObject explosionPrefab;//爆炸特效的预制体
    

    Stack<float> directionKey = new Stack<float>();

    private void Awake()
    {
        spriteRender = GetComponent<SpriteRenderer>();//获取坦克的渲染组件
        timeValE = (float)Random.Range(1, 5);
    }

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame

    void Update()//每一帧的处理时间不同
    {
        //设置攻击时间间隔
        if (timeVal >= timeValE)
        {
            TankAttack();
            timeValE= Random.Range(1, 5);
        }
        else
            timeVal += Time.deltaTime;
    }

    private void FixedUpdate()//生命周期函数,在Update之后执行,固定了每一帧的时间
    {
        TankMove();
    }

    private void TankMove()//坦克移动控制函数
    {
        if (DirChangeTime >= 3)
        {
            int num = Random.Range(0, 8);
            if (num > 5)//坦克向下走
            {
                v = -1;
                h = 0;
            }
            else if (num == 0)//坦克向上走
            {
                v = -1;
                h = 0;
            }
            else if (num > 0 && num <= 2) //坦克向左走
            {
                v = 0;
                h = -1;
            }
            else if (num > 2 && num <= 4)//坦克向下走
            {
                v = 0;
                h = 1;
            }
            DirChangeTime = 0;
        }
        else
            DirChangeTime += Time.fixedDeltaTime;

        //开始移动
        transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);//垂直移动

        //调整坦克的朝向图片
        if (v < 0)
        {
            spriteRender.sprite = tankSprite[2];
            bulletEulerangles = new Vector3(0, 0, 180);
        }
        else if (v > 0)
        {
            spriteRender.sprite = tankSprite[0];
            bulletEulerangles = new Vector3(0, 0, 0);
        }

        if (v != 0)//如果已经按下了水平方向,则不允许有垂直操作,直接返回
            return;

        //开始移动
        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//水平移动

        //调整坦克的朝向图片
        if (h < 0)
        {
            spriteRender.sprite = tankSprite[3];
            bulletEulerangles = new Vector3(0, 0, 90);
        }
        else if (h > 0)
        {
            spriteRender.sprite = tankSprite[1];
            bulletEulerangles = new Vector3(0, 0, -90);
        }
    }

    private void TankAttack()//坦克攻击函数
    {
        //子弹的角度=坦克的角度+子弹应该旋转的角度
        Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletEulerangles));//子弹进行实例化
        timeVal = 0;
    }

    private void TankDie()//坦克的死亡方法
    {
        //产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);

        //死亡
        Destroy(gameObject);
    }

然后给BulletAI里面添加子弹碰到玩家和敌人时不同的效果:

    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch(collision.tag)//根据碰撞物体的标签来响应事件
        {
            case "Barrier":
                Destroy(gameObject);//销毁子弹
                break;
            case "AirBarrier":
                Destroy(gameObject);//销毁子弹
                break;
            case "Grass":
                break;
            case "Heart":
                collision.SendMessage("HeartDie");
                Destroy(gameObject);//销毁子弹
                break;
            case "Wall":
                Destroy(collision.gameObject);//销毁墙
                Destroy(gameObject);//销毁子弹
                break;
            case "Tank":
                if(!isPlayerBullet)
                    collision.SendMessage("TankDie");//玩家死亡
                Destroy(gameObject);//销毁子弹
                break;
            case "Enemy":
                if(isPlayerBullet)
                    collision.SendMessage("TankDie");//敌人死亡
                Destroy(gameObject);//销毁子弹
                break;
            default:
                break;
        }
    }

下面是设计好的敌人AI自动运动与发射子弹的效果,因为没有设置游戏边界,所以最后他们都跑不见了。

20、修复敌人BUG

这里的BUG我们上一小节解决了,只需要把EnemyAI中的垂直和水平输入语句去掉就可以了。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐