Unity3D开发游戏坦克大战

写在前面

参考学习视频是siki学院Trigger老师的课程《如何使用unity开发坦克大战》:av29751762
感谢Trigger老师的辛勤付出!
|•’-’•) ✧我在编写脚本时很多地方进行了修改和完善,和老师的教学视频稍有出入。
我使用的是Unity2019.4.0f1Rider,其实Unity2017以上版本都可以,脚本我个人更喜欢用Rider。

游戏效果图示

分别是游戏开始界面,正在游戏界面和游戏结束界面,玩家死亡3秒后返回主界面。
开始游戏游戏ing游戏结束

文件结构图

Scripts

Player.cs

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

public class Player : MonoBehaviour
{
    public float moveSpeed = 3; // 移动速度
    private Vector3 bulletEulerAngles;
    private float timeVal; // 子弹发射计时器
    private bool isDenfended = true; // 被保护状态
    private float denfenTimeVal = 3; // 被保护的计时器

    // 引用
    private SpriteRenderer sr;
    public Sprite[] tankSprite; // 坦克精灵组 上右下左
    public GameObject bulletPrefab;
    public GameObject explosionPrefab; // 爆炸效果
    public GameObject shieldPrefab; // 受保护的预制体

    // 声音组建
    public AudioSource moveAudio;
    public AudioClip[] tankAudio;

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();
    }

    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        // 是否处于无敌状态
        if (isDenfended)
        {
            shieldPrefab.SetActive(true); // 如果无敌则显示
            // 受保护时间累减
            denfenTimeVal -= Time.deltaTime;
            if (denfenTimeVal <= 0)
            {
                isDenfended = false;
                shieldPrefab.SetActive(false); // 无敌消失
            }
        }
    }

    // 固定物理帧 每一秒匀称的情况下
    private void FixedUpdate()
    {
        // 玩家不能再移动攻击
        if (PlayerManager.Instance.isDefeat)
            return;
        Move();
        // 子弹攻击CD
        if (timeVal >= 0.4f)
        {
            Attack();
        }
        else
        {
            timeVal += Time.fixedDeltaTime;
        }
    }

    // 坦克移动方法
    private void Move()
    {
        // 通过监听h和v的输入来控制player在x和y轴位移的方向
        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * (v * moveSpeed * Time.fixedDeltaTime),
            Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
            bulletEulerAngles = new Vector3(0, 0, 180); // -180和正180 都是向下
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
            bulletEulerAngles = new Vector3(0, 0, 0);
        }

        if (Mathf.Abs(v) > 0.05f)
        {
            // 坦克前进音效
            moveAudio.clip = tankAudio[1];
            if (!moveAudio.isPlaying)
            {
                moveAudio.Play();
            }
        }

        // 优先级
        if (v != 0)
        {
            return;
        }

        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * (h * moveSpeed * Time.fixedDeltaTime),
            Space.World);
        if (h < 0)
        {
            sr.sprite = tankSprite[3];
            bulletEulerAngles = new Vector3(0, 0, 90);
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];
            bulletEulerAngles = new Vector3(0, 0, -90);
        }

        if (Mathf.Abs(h) > 0.05f)
        {
            // 坦克前进音效
            moveAudio.clip = tankAudio[1];
            if (!moveAudio.isPlaying)
                moveAudio.Play();
        }
        else
        {
            moveAudio.clip = tankAudio[0];
            if (!moveAudio.isPlaying)
                moveAudio.Play();
        }
    }

    // 坦克的攻击方法
    private void Attack()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 子弹产生的的角度等于当前坦克角度加子弹旋转角度
            Instantiate(bulletPrefab, transform.position,
                Quaternion.Euler(transform.eulerAngles + bulletEulerAngles));
            timeVal = 0;
        }
    }

    // 坦克死亡方法
    private void Die()
    {
        // 无敌状态不会爆炸
        if (isDenfended)
        {
            return;
        }

        PlayerManager.Instance.isDead = true;
        // 产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        // 死亡
        Destroy(gameObject);
    }
}

Explosion.cs

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

public class Explosion : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    { 
        // 爆照0.167秒后自动销毁
        Destroy(gameObject,0.167f);
    }

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

Bullet.cs

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

public class Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    public bool isPlayerBullet; // 是否敌人子弹

    public AudioClip hitAudio;

    public void PlayAudio()
    {
        AudioSource.PlayClipAtPoint(hitAudio,transform.position);
    }

    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(transform.up * (moveSpeed * Time.deltaTime), Space.World);
    }

    // collision 子弹碰撞到的物体
    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch (collision.tag)
        {
            case "Tank":
                if (!isPlayerBullet)
                {
                    // 敌人子弹打到玩家die
                    collision.SendMessage("Die");
                    Destroy(gameObject);
                }
                
                break;
            case "Base":
                collision.SendMessage("Die");
                Destroy(gameObject);
                break;
            case "Enemy":
                if (isPlayerBullet)
                {
                    collision.SendMessage("Died");
                    Destroy(gameObject);
                }
                break;
            case "Wall":
                // 碰到墙销毁
                Destroy(collision.gameObject);
                // 自身也要销毁
                Destroy(gameObject);
                break;
            case "Barrier":
                if (isPlayerBullet)
                {
                    PlayAudio();
                }
                // 碰到障碍 自身销毁
                Destroy(gameObject);
                break;
        }
    }
}

MapCreator.cs

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Random = UnityEngine.Random;

public class MapCreator : MonoBehaviour
{
    // 用来装饰初始化地图所需物体的数组
    // 0.base 1 wall 2.barrier 3.born 4.river 5.grass 6 airWall
    public GameObject[] item;

    // 装已经存物体的位置
    private List<Vector3> itemPositionList = new List<Vector3>();

    private void Awake()
    {
       InitMap(); 
    }

    private void CreatItem(GameObject creatGO, Vector3 cPosition, Quaternion cRotation)
    {
        // 游戏物体,位置,旋转角度
        GameObject itemGo = Instantiate(creatGO, cPosition, cRotation);
        // 父物体设置成装游戏物体的容器 set成当前的游戏物体 保证不会散落出来
        itemGo.transform.SetParent(gameObject.transform);

        // 已产生的位置存放进列表
        itemPositionList.Add(cPosition);
    }

    // 产生随机位置方法
    private Vector3 CreatRandomPosition()
    {
        // 不生成x=-10,10的两列,y=8,y = -8这两行的位置
        while (true)
        {
            // 声音v3接收随机生成的物体
            Vector3 creatPosition = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);
            // 在列表中有位置不返回,再次循环,无位置返回
            if (!itemPositionList.Contains(creatPosition))
                return creatPosition;
        }
    }

    // 判断位是否存在列表中
    private bool IsPosition(Vector3 cP)
    {
        for (int i = 0; i < itemPositionList.Count; i++)
        {
            if (cP == itemPositionList[i])
            {
                return true;
            }
        }

        return false;
    }

    // 产生敌人的方法
    private void CreateEnemy()
    {
        // 随机三个位置
        int num = Random.Range(0, 3);
        Vector3 EnemyPos;
        if (num == 0)
        {
            EnemyPos = new Vector3(-10, 8, 0);
        }
        else if (num == 1)
        {
            EnemyPos = new Vector3(0, 8, 0);
        }
        else
        {
            EnemyPos = new Vector3(10, 8, 0);
        }

        CreatItem(item[3], EnemyPos, Quaternion.identity);
    }
    
    // 地图实例化的方法
    private void InitMap()
    {
        // 实例化基地
        CreatItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
        // 用墙围基地
        CreatItem(item[1], new Vector3(-1, -8, 0), Quaternion.identity);
        CreatItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
        CreatItem(item[1], new Vector3(-1, -7, 0), Quaternion.identity);
        CreatItem(item[1], new Vector3(0, -7, 0), Quaternion.identity);
        CreatItem(item[1], new Vector3(1, -7, 0), Quaternion.identity);
        // 格挡砖
        CreatItem(item[2], new Vector3(10, -4, 0), Quaternion.identity);
        CreatItem(item[2], new Vector3(-10, -4, 0), Quaternion.identity);
        CreatItem(item[2], new Vector3(0, -5, 0), Quaternion.identity);

        // 实例化外围墙 最上,最下,最左,最右
        for (int i = -11; i < 12; i++)
        {
            CreatItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
            CreatItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);
        }

        for (int i = -8; i < 9; i++)
        {
            CreatItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);
            CreatItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
        }

        // 实例化地图 墙 障碍 河流 草 不能写在同一个for 保证均匀
        for (int i = 0; i < 60; i++)
        {
            CreatItem(item[1], CreatRandomPosition(), Quaternion.identity);
        }

        for (int i = 0; i < 17; i++)
        {
            CreatItem(item[2], CreatRandomPosition(), Quaternion.identity);
        }

        for (int i = 0; i < 20; i++)
        {
            CreatItem(item[4], CreatRandomPosition(), Quaternion.identity);
        }

        for (int i = 0; i < 20; i++)
        {
            CreatItem(item[5], CreatRandomPosition(), Quaternion.identity);
        }

        // 实例化玩家和敌人
        // 产生出生的特效
        GameObject go = Instantiate(item[3], new Vector3(-2, -8, 0), Quaternion.identity);
        go.GetComponent<Born>().createPlayer = true;
        // 产生敌人 随机顶部三个位置 游戏开始先产生三个敌人
        CreatItem(item[3], new Vector3(-8, 8, 0), Quaternion.identity);
        CreatItem(item[3], new Vector3(8, 8, 0), Quaternion.identity);
        CreatItem(item[3], new Vector3(0, 8, 0), Quaternion.identity);

        // 每隔一断时间产生敌人 每隔一段时间重复调用
        InvokeRepeating("CreateEnemy", 4, 5);
    }
}

PlayerManager.cs

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

public class PlayerManager : MonoBehaviour
{
    // 玩家生命值
    public int playerHP = 3;
    public int score = 0;
    public bool isDead;

    public bool isDefeat; // 游戏失败

    public GameObject born;

    public Text playerHpText;
    public Text playerScoreText;
    public GameObject isDefeatUI;

    // 单例
    private static PlayerManager instance;

    public static PlayerManager Instance
    {
        get { return instance; }
        set { instance = value; }
    }

    private void Awake()
    {
        instance = this;
    }

    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        // 如果失败,播放失败图片
        if (isDefeat)
        {
            isDefeatUI.SetActive(true);
            Invoke("ReturnMenu", 3);
            return; // 所有方法不再执行
        }

        // 如果死亡 玩家复活
        if (isDead)
        {
            Recover();
        }

        // 实时更新
        playerScoreText.text = score.ToString();
        playerHpText.text = playerHP.ToString();
    }

    // 复活的方法
    private void Recover()
    {
        if (playerHP <= 0)
        {
            // 游戏失败,返回主界面
            isDefeat = true;
            // 延时3秒回到主页
            Invoke("ReturnMenu", 3);
        }
        else
        {
            playerHP--;
            // 并且在出生地born一次
            GameObject go = Instantiate(born, new Vector3(-2, -8, 0), Quaternion.identity);
            go.GetComponent<Born>().createPlayer = true;
            isDead = false;
        }
    }

    private void ReturnMenu()
    {
        SceneManager.LoadScene("Main");
    }
}

Enemy.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class Enemy : MonoBehaviour
{
    public float moveSpeed = 3; // 移动速度
    private Vector3 bulletEulerAngles;
    private float timeVal; // 子弹发射计时器
    private float turnTimeVal; // 控制转向的计时器 默认为4 一产生就转向

    private float v = -1; // 出生往下走
    private float h;

    // 引用
    private SpriteRenderer sr;
    public Sprite[] tankSprite; // 坦克精灵组 上右下左
    public GameObject bulletPrefab;
    public GameObject explosionPrefab; // 爆炸效果

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();
    }

    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        // 子弹攻击的时间间隔,敌人每隔一段时间射出子弹
        if (timeVal >= 2)
        {
            Attack();
        }
        else
        {
            timeVal += Time.deltaTime;
        }
    }

    // 固定物理帧 每一秒匀称的情况下
    private void FixedUpdate()
    {
        Move();
    }

    // 坦克移动方法
    private void Move()
    {
        // 敌人一直往Base走 往Base几率更大
        if (turnTimeVal >= 4)
        {
            int num = Random.Range(0, 8);
            if (num > 4)
            {
                v = -1;
                h = 0;
            }
            else if (num == 0)
            {
                v = 1;
                h = 0;
            }
            else if (num > 0 && num <= 2)
            {
                h = -1;
                v = 0;
            }
            else if (num > 2 && num <= 4)
            {
                h = -1;
                v = 0;
            }

            // 归0, 否则一直旋转
            turnTimeVal = 0;
        }
        else
        {
            turnTimeVal += Time.fixedDeltaTime;
        }

        transform.Translate(Vector3.up * (v * moveSpeed * Time.fixedDeltaTime),
            Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
            bulletEulerAngles = new Vector3(0, 0, 180); // -180和正180 都是向下
        }
        else if (v > 0)
        {
            sr.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)
        {
            sr.sprite = tankSprite[3];
            bulletEulerAngles = new Vector3(0, 0, 90);
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];
            bulletEulerAngles = new Vector3(0, 0, -90);
        }
    }

    // 坦克的攻击方法 自动射击
    private void Attack()
    {
        // 子弹产生的的角度等于当前坦克角度加子弹旋转角度
        Instantiate(bulletPrefab, transform.position,
            Quaternion.Euler(transform.eulerAngles + bulletEulerAngles));
        timeVal = 0;
    }

    // 坦克死亡方法
    private void Died()
    {
        // 产生爆炸特效并且玩家得分加1
        PlayerManager.Instance.score++;
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        // 死亡
        Destroy(gameObject);
    }

    // 转换方向 如果两个坦克有触碰,顺势转换方向
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Enemy")
        {
            v *= -1;
            h *= -1;
        }
    }
}

Option.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // 场景管理
public class Option : MonoBehaviour
{
    // 选择的选项 为0 空格加载场景 为1 时指针向下移动到pos2
    private int choose = 1;
    public Transform posOne;
    public Transform posTwo;

    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        // 监听ws 根据键盘按下的位置选择值
        if (Input.GetKeyDown(KeyCode.W)||Input.GetKeyDown(KeyCode.UpArrow))
        {
            choose = 1;
            transform.position = posOne.position;
        }
        else if (Input.GetKeyDown(KeyCode.S)||Input.GetKeyDown(KeyCode.DownArrow))
        {
            choose = 2;
            transform.position = posTwo.position;
        }

        if (choose == 1 && (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.Return)))
        {
            SceneManager.LoadScene("Game");
        }
    }
}

Base.cs

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

public class Base : MonoBehaviour
{
    private SpriteRenderer sr;

    // 控制渲染 如果被子弹碰到,消失,游戏结束
    public Sprite BrokenSprite;

    // 爆炸效果的引用
    public GameObject explosionPrefab;
    // base死时的音效
    public AudioClip dieAudio;

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();
    }

    void Start()
    {
    }

    public void Die()
    {
        // 一调用死亡方法,切换游戏死亡图片
        sr.sprite = BrokenSprite;
        // 图片切换后产生一个爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        PlayerManager.Instance.isDefeat = true;
        // 当前位置播放
        AudioSource.PlayClipAtPoint(dieAudio,transform.position);
    }
}

Born.cs

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

public class Born : MonoBehaviour
{
    // 让特效播放一度半时间消失
    public GameObject playerPrefab;

    // 敌人数组 随机产生
    public GameObject[] enemyPrefabList;

    public bool createPlayer; // 初始产生敌人

    void Start()
    {
        // 延时调用
        Invoke("BornTank", 1);
        // 延时销毁
        Destroy(gameObject, 1);
    }

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

    private void BornTank()
    {
        if (createPlayer)
        {
            // 出生当前位置 无旋转
            Instantiate(playerPrefab, transform.position, Quaternion.identity);
        }
        else
        {
            int num = Random.Range(0, 2);
            Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
        }
    }
}

Unity工程源码

GitHub:Unity2D_TankWar
|•’-’•) ✧上面是开发完成的工程项目,如果从0开始搭建需要资源包的话,可以私信我哟。

补充:如何加载GitHub上下载的Unity工程
需要在本地新建一个Unity工程项目,然后将下载的资源包里的Assets文件夹进行替换,重新打开新建工程就可以了。此外,浏览完整工程还需要根据项目的具体情况在File->Build Settings下对场景按照顺序进行关联

本文的坦克大战小游戏项目需要将Main场景和Game场景进行关联。
在这里插入图片描述


写在最后

感谢Trigger老师的辛勤付出!
如果代码中有我写错或者没有理解的部分,恳请指正,一起学习讨论,共同进步。
最后,祝自己早日咸鱼翻身,斩获心仪的Offer,冲呀!

猜你喜欢

转载自blog.csdn.net/wankcn/article/details/106991439