Unity3D坦克大战游戏开发——学习笔记(下)

上一次的学习我们完成了敌人的AI编写,但是有一个问题就是敌人会跑到地图外,所以我们这次需要开始初始化地图了。

21、初始化地图工具的创建

我们新建一个游戏物体,命名为MapCreation,用来创建地图,并挂在一个MapCreation的脚本用来初始化地图。

    //0.老家 1.墙 2.障碍 3.出生效果 4.河流 5.草 6.空气墙
    public GameObject[] item;//初始地图所需物体的数组

    private void Awake()//对地图进行实例化
    {
        //实例化老家
        CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
        CreateItem(item[1], new Vector3(-1, -8, 0), Quaternion.identity);//老家旁边的保护墙
        CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);//老家旁边的保护墙
        CreateItem(item[1], new Vector3(-1, -7, 0), Quaternion.identity);//老家旁边的保护墙
        CreateItem(item[1], new Vector3(0, -7, 0), Quaternion.identity);//老家旁边的保护墙
        CreateItem(item[1], new Vector3(1, -7, 0), Quaternion.identity);//老家旁边的保护墙
    }

    private void CreateItem(GameObject obj,Vector3 pos,Quaternion qua)//创建物体到Map里
    {
        GameObject createObj = Instantiate(obj, pos, qua);//实例化游戏物体
        createObj.transform.SetParent(gameObject.transform);//设置父物体
    }

我们给MapCreation脚本挂载上他需要的预制体,然后在地图中直接创建老家,下面是效果:

22、编写产生随机位置的方法

接下来我们就需要创建一些其他的物体,但是因为这些物体位置信息每一局都不能一样,所以我们需要让这些物体产生随机位置。

    public List<Vector3> itemPosList=new List<Vector3>();//用于存放已经生成的位置

    private Vector3 CreatePosRandomly()//产生随机位置
    {
        //约束最外围的边界不产生物体,确保可以通行(x=-10,10,y=-8,8)
        while(true)
        {
            Vector3 tempPos = new Vector3(Random.Range(-9,10),Random.Range(-7,8),0);//产生一个随即位置
            for (int i = 0; i < itemPosList.Count; i++) //判断列表中是否存在该位置
            {
                if (tempPos == itemPosList[i])
                    continue;
            }
            return tempPos;
        }
    }

然后我们实例化一下外层的空气墙,在Awake函数里面实现:

        //实例化边界的空气墙
        for (int i = -11; i < 12; i++) 
        {
            CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);//上面的围墙
            CreateItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);//下面的围墙
        }

        for(int i = -8; i < 9; i++)
        {
            CreateItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);//左边的围墙
            CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);//右边的围墙
        }

23、初始化地图其他的游戏物体

我们现在初始化一下游戏的其他物体,这里我们先初始化草、水、墙和障碍物

        //实例化地图其他物体
        for (int i = 0; i < 20; i++)//每个种类产生20个
        {
            CreateItem(item[1], CreatePosRandomly(), Quaternion.identity);//实例化墙
            CreateItem(item[1], CreatePosRandomly(), Quaternion.identity);//实例化墙
            CreateItem(item[1], CreatePosRandomly(), Quaternion.identity);//实例化墙
            CreateItem(item[2], CreatePosRandomly(), Quaternion.identity);//实例化障碍
            CreateItem(item[4], CreatePosRandomly(), Quaternion.identity);//实例化河流
            CreateItem(item[5], CreatePosRandomly(), Quaternion.identity);//实例化草
        }

然后我们添加一下玩家和敌人,这些都是Awake函数中完成

        //实例化玩家
        GameObject player = Instantiate(item[3], new Vector3(-2, -8, 0), Quaternion.identity);
        itemPosList.Add(new Vector3(-2, -8, 0));//将玩家的位置添加到列表中
        player.GetComponent<Born>().createPlayer = true;

        //实例化敌人
        CreateItem(item[3], new Vector3(-10, 8, 0), Quaternion.identity);
        CreateItem(item[3], new Vector3(0, 8, 0), Quaternion.identity);
        CreateItem(item[3], new Vector3(10, 8, 0), Quaternion.identity);

        InvokeRepeating("CreateEnemy", 4f, 5f);//延迟调用,第一次4s后随机产生敌人,随后每隔5s产生敌人

其中调用到了随机产生敌人的函数CreateEnemy()

    private void CreateEnemy()//产生敌人
    {
        int num = Random.Range(0, 3);
        Vector3 enemyPos=new Vector3();
        if (num == 0)
            enemyPos = new Vector3(-10, 8, 0);
        else if(num==1)
            enemyPos = new Vector3(0, 8, 0);
        else if (num == 2)
            enemyPos = new Vector3(10, 8, 0);
        CreateItem(item[3], enemyPos, Quaternion.identity);//在随机位置产生敌人
    }

我们看一下游戏运行的效果:

     

24、敌人AI的优化

我们发现当敌人过多的时候,有可能会扎堆,为了解决这个问题,我们让两个敌人碰到一起的时候立即改变方向:

    private void OnCollisionEnter2D(Collision2D collision)//碰撞检测
    {
        if (collision.gameObject.tag == "Enemy")//如果两个敌人碰到一起就让他们方向发生改变,不用挤到一起
            DirChangeTime = 4;
    }

25、玩家状态的管理

现在我们需要对玩家的状态进行管理,包括玩家的生命,玩家消灭的敌人等信息,我们新建一个空物体,命名为PlayerManage,挂载上同名的脚本PlayerManage

    //属性
    public int playerLife = 3;//玩家的生命值
    public int playerScore = 0;//玩家的得分
    public bool isDead;//玩家是否死亡

    //引用
    public GameObject Born;//重生的特效

    //单例
    private static PlayerManage instance;

    public static PlayerManage Instance
    {
        get => instance;
        set => instance = value;
    }

    private void Awake()
    {
        Instance = this;
    }

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

    // Update is called once per frame
    void Update()
    {
        if (isDead)
            Reborn();//如果死亡,进入重生函数
    }

    private void Reborn()
    {
        if (playerLife == 0)
            ;
        else
        {
            playerLife--;//生命值减1
            GameObject palyer = Instantiate(Born, new Vector3(-2, -8, 0), Quaternion.identity);
            palyer.GetComponent<Born>().createPlayer = true;//设置为重生玩家
            isDead = false;
        }
    }

然后我们在PlayerAI里面坦克死亡时把IsDead的值改为true,在EnemyAI敌人死亡时把分数递增:

    //PlayerAI

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

        PlayerManage.Instance.isDead = true;//设置玩家状态为死亡

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

        //死亡
        Destroy(gameObject);
    }


    //EnemyAI

    private void TankDie()//坦克的死亡方法
    {
        PlayerManage.Instance.playerScore++;//消灭一个敌人加一分

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

        //死亡
        Destroy(gameObject);
    }

 下面是效果:

26、UI的制作

游戏的大部分功能都制作完成了,现在我们来设计UI显示玩家的状态与得分,我们先设置游戏屏幕比例为16:10,这样就可以在旁边留白部分进行UI的显示,设置一下整个UI的背景色为灰色,然后拖入一个纯黑的背景作为游戏的背景,设置渲染优先级为-2,这样就不会遮挡游戏物体。

我们先新建两个图片,用来表示坦克的生命和得分,然后分别加文本框用来显示得分数和生命值:

我们在代码中对这两个值进行实时的修改,接着我们添加一下游戏结束的处理程序,当老家死亡或者生命值为0时会显示游戏结束的界面,我们在PlayerManage脚本的Update里面添加处理程序,把结束UI的界面传到脚本的公有变量上:

    public GameObject gameOver;//游戏结束界面    
    
    void Update()
    {
        if (isDead)
            Reborn();//如果死亡,进入重生函数

        if (isDefeat)
        {
            gameOver.SetActive(true);//设置游戏结束界面
            return;
        }

        //实时更新UI
        scoreText.text = "Score:"+playerScore.ToString();
        lifeText.text = "Life:" + playerLife.ToString();
    }

下面是效果:

       

27、第一个场景的制作

游戏的场景基本制作完了,接下来我们要制作游戏刚打开时的场景,我们ctrl+N新建一个场景,命名为Begin保存到当前工程下。我们在新的场景下添加一个图片,设置图片源为资源文件里面的title图片,设置好适合的大小。

然后我们新建一个图片,用来设置选择的指针,我们把坦克的向右的图片作为他的渲染图片,设置好对应的位置和大小:

然后我们新建两个空物体,把指针的位置和大小信息给这两个空物体,然后将第二个空物体向下移动到第二个选择,这样我们就设置好了两个位置信息,到时候选择的时候直接访问这两个位置信息进行移动就可以了。我们新建一个脚本挂载到选择指针上:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ChooseOptions : MonoBehaviour
{
    //属性
    private int choose = 1;//记录玩家的选择

    //引用
    public GameObject posOne;//选择1的位置
    public GameObject posTwo;//选择2的位置

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

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            choose = 1;
            transform.position = posOne.transform.position;//将选择指针移动到选择1
        }
        else if(Input.GetKeyDown(KeyCode.DownArrow))
        {
            choose = 2;
            transform.position = posTwo.transform.position;//将选择指针移动到选择1
        }

        if(choose==1&&Input.GetKeyDown(KeyCode.Space))//如果玩家选择了1并且按下了空格确认
        {
            SceneManager.LoadScene("Main");
        }
    }
}

为了能够空格选择游戏模式顺利加载到我们的游戏主场景,我们需要在Build Settings里面拖入我们的两个场景,第一个是Begin,第二个是Main:

然后下面是效果:

        

28、音效的添加

接下来我们简单添加一下游戏的音效功能,我们先给老家死亡添加音效,在HeartAI中添加音效的引用,然后在死亡时播放:

    public AudioClip heartdieVoice;//老家死亡音效

    public void HeartDie()//老家中枪,游戏结束
    {
        Instantiate(explosionPrefab, transform.position, transform.rotation);//产生爆炸特效
        spriteRenderer.sprite = HeartBroken;//更换爆炸后的图片
        PlayerManage.Instance.isDefeat = true;//游戏结束
        AudioSource.PlayClipAtPoint(heartdieVoice,transform.position);//播放死亡音效
    }

把资源里面的Die音效托给heartdieVoice就可以了,接着我们添加爆炸音效,我们直接给爆炸动画添加一个Audio Source组件,选择Play On Awake为勾选状态,这样一初始化爆炸特效就会播放音效。不过记得要给Audio Clip设置音效的来源。

然后我们添加一下子弹打到障碍的一个音效,我们给障碍预制体添加一个脚本:

    //引用
    public AudioClip hitVoice;//子弹打到障碍物的声音特效

    public void PlayAudio()//播放音效
    {
        AudioSource.PlayClipAtPoint(hitVoice, transform.position);
    }

然后在子弹碰到障碍时调用这个函数就可以了

        //仅作事例
        switch(collision.tag)//根据碰撞物体的标签来响应事件
        {
            case "Barrier":
                collision.SendMessage("PlayAudio");//播放击打音效
                Destroy(gameObject);//销毁子弹
        }

接着我们添加游戏开始音效和子弹发射的音效,设置组件与声音来源,勾选在Awake函数里面直接调用。

最后就是坦克移动的音效,我们给PlayerAI脚本新建两个变量,一个是Audio的组件,一个是音效的资源,分别拖动赋值:

然后我们根据坦克是否处于移动状态来播放不同的音效。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值