小言_互联网的博客

Unity3D 《坦克大战》案例源码过程

349人阅读  评论(0)

使用的是Unity 2018版和VS2019
创建新的工程,导入素材包,下面为链接:
链接:https://pan.baidu.com/s/1_KLjColSJVN123aLy4_4fw
提取码:o101

导入素材

先把相机的天空盒改为黑色画面比例调为5:4还有长度改为8.5

这时候我们就会发现我们的游戏实景面板(左下角的窗口)已经变为黑色的了。

初步制作物体

接着我们要做游戏里的物体,我们打开Project面板Assets文件夹下的GameResource文件,下面有三个文件夹,从上到下分别是声音、字体和图片。我们找到玩家的图片、按照以下方法,创造一个实体。

接着按这样的方法将草丛(Grass)、可以被子弹打穿的障碍墙(Wall)、不可被子弹打穿的障碍墙(Barrier)、空气墙(AirBarriar)、老鹰标志(Heart…就是我们要保护的老窝)

动画效果

接下来我们来做会动的水(River)、爆炸效果(Explosion)、无敌效果(Shield)、出生效果(Born)!我们拿爆炸效果为例:

按住Ctrl键同时选中两张爆炸效果的图片,然后拖到左边的目录下;

这时候会弹出一个对话框,我们更改文件名为:Explosion;

更改玩之后点击保存,这时候我们点击运行,就可以发现,我们的爆炸效果会动了(别忘了调整大小为3:3:3)。

这时候可以看到在我们的Project面板下,多出了两个新朋友。

为了更加方便的管理,我们在Project面板下新创建两个文件夹一个为 Animation、另一个是AnimationController。然后把图标为三角的文件拖入Animation中,另一个拖入AnimationController中。

读者请自制其他动画组件。做完之后文件这样命名,如下图所示:

Effect为动画特效文件,Map为游戏里的障碍物、草丛、家等,Tank为坦克。

坦克移动

创建一个Player脚本托在坦克上
我将坦克的最终代码写了出来,防止分开写大家看不懂,比较绕
代码如下:

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


public class Player : MonoBehaviour
{
   
    //子弹欧拉角
    private Vector3 bullectEulerAngles;
    private float moveSpeed = 3;
    private float timeVal;
    private float defendTimeVal = 3f;
    //渲染坦克转弯后的素材
    private SpriteRenderer sr;
    public Sprite[] tankSprite; //上右下左

    private bool isDefended = true;

    public GameObject explosionPrefab;
    public GameObject bullectPrefab;
    public GameObject defendEffectPrefab;
    private void Awake()
    {
   
        sr = GetComponent<SpriteRenderer>();
    }
    private void Update()
    {
   
        //是否处于无敌状态
        if (isDefended)
        {
   
            defendEffectPrefab.SetActive(true);
            defendTimeVal -= Time.deltaTime;
            if (defendTimeVal <= 0)
            {
   
                Debug.Log(111);
                isDefended = false;
                defendEffectPrefab.SetActive(false);
            }
        }
    }

    private void FixedUpdate()
    {
   
        if (PlayerManager.Instance.isDefeat)
        {
   
            return;
        }
        Move();
        //攻击cd
        if (timeVal >= 0.4f)
        {
   
            Attack();
        }
        else
        {
   
            timeVal += Time.fixedDeltaTime;
        }
    }

    //移动
    public void Move()
    {
   
        float v = Input.GetAxisRaw("Vertical");//垂直轴值输入
        transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
        if (v < 0) 
        {
   
            sr.sprite = tankSprite[2];
            bullectEulerAngles = new Vector3(0, 0, -180);
        }
        else if (v > 0)
        {
   
            sr.sprite = tankSprite[0];
            bullectEulerAngles = new Vector3(0, 0, 0);
        }

        //优先级问题,优先向垂直方向走
        if (v != 0)
        {
   
            return;
        }

        float h = Input.GetAxisRaw("Horizontal");//水平轴值输入  返回-1到1
        transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);
        //移动时调整方向
        if (h < 0) 
        {
   
            sr.sprite = tankSprite[3];
            bullectEulerAngles = new Vector3(0, 0, 90);
        }
        else if (h > 0)
        {
   
            sr.sprite = tankSprite[1];
            bullectEulerAngles = new Vector3(0, 0, -90);
        }
    }

    //子弹射击
    private void Attack()
    {
   
        if (Input.GetKeyDown(KeyCode.J))
        {
   
            //实例化 子弹产生角度:当前坦克角度+子弹应该旋转的角度   欧拉角
            Instantiate(bullectPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bullectEulerAngles));
            timeVal = 0f;
        }
    }

    //坦克得死亡方法
    private void Die()
    {
   
        if (isDefended)
        {
   
            return;//跳过死亡方法
        }

        PlayerManager.Instance.isDead = true;

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

这里是坦克的属性面板,赋值。记得向坦克加刚体和碰撞体
将刚体里的Gravity Scale(重力改为0)不然向下掉

顺便将坦克的Z轴锁住

给River、Eagle、Barrir、Player添加上Box Collider 2D。
小技巧:直接在预制体上进行操作会省很多步骤哟!

子弹的制作

其他实体一样,我们从我们的素材中找到子弹样式的图片,拖入场景,调整大小比例为3:3:3,并且重命名为Playerbullect,添加预制体:
下面给出子弹的最终代码和属性面板
创建一个Bullect类

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

public class Bullect : MonoBehaviour {
   

    public bool IsPlayerBullect;
    // Use this for initialization
    void Start () {
   
		
	}
	
	// Update is called once per frame
	void Update () {
   
		transform.Translate(transform.up * 10 * Time.deltaTime, Space.World);
	}


    //碰撞检测
    private void OnTriggerEnter2D(Collider2D collision)
    {
   
        switch (collision.tag)
        {
   
            case "Player": //打到自己
                if (!IsPlayerBullect)
                {
   
                    collision.SendMessage("Die");
                    Destroy(gameObject);
                }
                break;

            case "Heart"://打到家
                collision.SendMessage("HeartDie");
                Destroy(gameObject);
                break;

            case "Enemy"://打到敌人
                if (IsPlayerBullect)
                {
   
                    collision.SendMessage("Die");
                    Destroy(gameObject);
                }
                break;

            case "Barrier"://打到障碍物
                Destroy(gameObject);
                //AudioSource.PlayClipAtPoint(hitAudio, transform.position);
                break;

            case "Wall"://打到墙
                Destroy(collision.gameObject);
                Destroy(gameObject);
                break;

            default:
                break;
        }
    }
}

同样向子弹中添加刚体和碰撞体
碰撞体中又Is Ttigger属性 打上对勾
将刚体里的Gravity Scale(重力改为0)不然向下掉


将我方子弹复制一个,改名为EnemyBullect,敌方子弹与我方子弹的唯一区别就是 敌方子弹不打对勾Is Player Bullect

当坦克被击中时会爆炸(爆炸特效及代码)

爆炸特效在上面已经做了 名字是Explode
代码只有一个销毁自身

public class Explosion : MonoBehaviour {
   

	// Use this for initialization
	void Start () {
   
		Destroy(gameObject, 0.2f);
	}
	
	// Update is called once per frame
	void Update () {
   
		
	}
}

出生无敌特效

上面也已经做了,现在说说吧它放哪

敌人AI编写与优化

直接将自己坦克的素材换成敌方坦克的

将Player代码remove删除掉,创建一个Enemy脚本
最终代码如下:

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

public class Enemy : MonoBehaviour {
   

    //子弹欧拉角
    private Vector3 bullectEulerAngles;
    private float v = -1;
    private float h;

    private float moveSpeed = 3;
    private float timeVal;
    private float timeValChangeDirection = 4;

    //渲染坦克转弯后的素材
    private SpriteRenderer sr;
    public Sprite[] tankSprite; //上右下左

    public GameObject explosionPrefab;
    public GameObject bullectPrefab;

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

        //攻击cd
        if (timeVal >= 3f)
        {
   
            Attack();
        }
        else
        {
   
            timeVal += Time.deltaTime;
        }
    }

    //移动
    public void Move()
    {
   
        if (timeValChangeDirection >= 4)
        {
   
            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)
            {
   
                h = -1;
                v = 0;
            }
            else if (num < 2 && num <= 4)
            {
   
                h = 1;
                v = 0;
            }
            timeValChangeDirection = 0;
        }
        else
        {
   
            timeValChangeDirection += Time.fixedDeltaTime;
        }

        transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
        if (v < 0)
        {
   
            sr.sprite = tankSprite[2];
            bullectEulerAngles = new Vector3(0, 0, -180);
        }
        else if (v > 0)
        {
   
            sr.sprite = tankSprite[0];
            bullectEulerAngles = new Vector3(0, 0, 0);
        }

        //优先级问题,优先向垂直方向走
        if (v != 0)
        {
   
            return;
        }

        transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);
        //移动时调整方向
        if (h < 0)
        {
   
            sr.sprite = tankSprite[3];
            bullectEulerAngles = new Vector3(0, 0, 90);
        }
        else if (h > 0)
        {
   
            sr.sprite = tankSprite[1];
            bullectEulerAngles = new Vector3(0, 0, -90);
        }
    }

    //子弹射击
    private void Attack()
    {
   
            //实例化 子弹产生角度:当前坦克角度+子弹应该旋转的角度   欧拉角
            Instantiate(bullectPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bullectEulerAngles));
            timeVal = 0f;
    }

    //坦克得死亡方法
    private void Die()
    {
   
        PlayerManager.Instance.playerScore++;
        //产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        //死亡
        Destroy(gameObject);
    }

    private void OnCollisionEnter2D(Collider2D collider)
    {
   
        if (collider.gameObject.tag == "Enemy")
        {
   
            timeValChangeDirection = 4;
        }
    }
}

身上的组件和需要赋值的,将名称改为SmallEnemy和BigEnemy,做一大一小

出生特效及代码

上面已经创建过了,直接上属性面板图和最终代码
创建一个Born脚本

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

public class Born : MonoBehaviour {
   

	public GameObject palyerPrefab;


	public GameObject[] enemyPrefabList;

	public bool createPlayer;
	// Use this for initialization
	void Start () {
   
		Invoke("BornTank", 1);
		Destroy(gameObject, 1);
	}
	
	private void BornTank()
    {
   
        if (createPlayer)
		{
   
			Instantiate(palyerPrefab, transform.position, transform.rotation);
		}
        else
        {
   
			int num = Random.Range(0, 2);
			Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
        }
    }

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

家特效及代码

最终代码,创建一个Heart脚本

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

public class Heart : MonoBehaviour {
   

	private SpriteRenderer sr;
	public GameObject explosionPrefab;
	public Sprite BrokenSprite;

	public AudioClip dieAudio;

	// Use this for initialization
	void Start () {
   
		sr = GetComponent<SpriteRenderer>();
	}
	
	public void HeartDie()
    {
   
		sr.sprite = BrokenSprite;
		Instantiate(explosionPrefab, transform.position, transform.rotation);
		PlayerManager.Instance.isDefeat = true;
		AudioSource.PlayClipAtPoint(dieAudio, transform.position);
    }
}

附上值

开始地图创建

这是一个新的场景,刚才做的是game场景


创建一个空物体MapCreation,将脚本托给它

0老家 1墙 2障碍 3出生效果 4河流 5草 6空气墙
按这个顺序拖进去

上最终代码,创建MapCreation脚本

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

public class MapCreation : MonoBehaviour
{
   
    //需要初始化的地图所需物体的数组
    //0空气墙1障碍2草3老家4河流5墙6出生特效
    public GameObject[] item;
    //已经有物体的位置列表
    private List<Vector3> itemPositionList = new List<Vector3>();
    private void Awake()
    {
   

        InitMap();

    }
    private void InitMap()
    {
   
        //实例化老家
        CreateItem(item[3], new Vector3(0, -8, 0), Quaternion.identity);//无旋转
        //用墙把老家围起来
        CreateItem(item[5], new Vector3(-1, -8, 0), Quaternion.identity);
        CreateItem(item[5], new Vector3(1, -8, 0), Quaternion.identity);
        for (int i = -1; i < 2; i++)
        {
   
            CreateItem(item[5], new Vector3(i, -7, 0), Quaternion.identity);
        }
        //实例化外围墙
        for (int i = -11; i < 12; i++)
        {
   
            CreateItem(item[0], new Vector3(i, 9, 0), Quaternion.identity);
            CreateItem(item[0], new Vector3(i, -9, 0), Quaternion.identity);
        }
        for (int i = -8; i < 9; i++)
        {
   
            CreateItem(item[0], new Vector3(-11, i, 0), Quaternion.identity);
            CreateItem(item[0], new Vector3(11, i, 0), Quaternion.identity);
        }
        //实例化地图
        for (int i = 0; i < 20; i++)
        {
   
            CreateItem(item[2], CreateRandomPosition(), Quaternion.identity);
            CreateItem(item[5], CreateRandomPosition(), Quaternion.identity);
            CreateItem(item[4], CreateRandomPosition(), Quaternion.identity);
            CreateItem(item[1], CreateRandomPosition(), Quaternion.identity);
        }
        //实例化玩家
        GameObject go = Instantiate(item[6], new Vector3(-2, -8, 0), Quaternion.identity);
        go.GetComponent<Born>().createPlayer = true;
        //实例化敌人
        CreateItem(item[6], new Vector3(-10, 8, 0), Quaternion.identity);
        CreateItem(item[6], new Vector3(0, 8, 0), Quaternion.identity);
        CreateItem(item[6], new Vector3(10, 8, 0), Quaternion.identity);
        //隔一段时间在3个地方随机产生敌人 invoke延时调用 "方法名"
        InvokeRepeating("CreateRandomEnemy", 4, 5);
    }
    private void CreateItem(GameObject createObject, Vector3 createPosition, Quaternion createRotation)
    {
   
        //将地图资源的父级设为MapCreation
        GameObject itemGo = Instantiate(createObject, createPosition, createRotation);
        itemGo.transform.SetParent(gameObject.transform);
        itemPositionList.Add(createPosition);
    }
    //产生随机位置的方法
    private Vector3 CreateRandomPosition()
    {
   
        //不生成x=-10,x=10两行和y=-8,y=8两列
        while (true)
        {
   
            Vector3 createPosition = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);
            if (!IshavePosition(createPosition))
            {
   
                return createPosition;
            }

        }

    }
    //判断位置有没有
    private bool IshavePosition(Vector3 createPosition)
    {
   
        for (int i = 0; i < itemPositionList.Count; i++)
        {
   
            if (createPosition == itemPositionList[i])
            {
   
                return true;
            }

        }
        return false;
    }
    //产生随机敌人的方法
    private void CreateRandomEnemy()
    {
   
        int num = Random.Range(0, 3);
        if (num == 0)
        {
   
            CreateItem(item[6], new Vector3(-10, 8, 0), Quaternion.identity);
        }
        else if (num == 1)
        {
   
            CreateItem(item[6], new Vector3(0, 8, 0), Quaternion.identity);
        }
        else
        {
   
            CreateItem(item[6], new Vector3(10, 8, 0), Quaternion.identity);
        }
    }
}

场景切换

在这里添加进去,配上最后的代码,即实现场景切换

统计分数与血量

最终代码,创建一个PlayerManager脚本和空物体PlayerManager
将脚本托给PlayerManager

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

public class PlayerManager : MonoBehaviour {
   

	public int lifeValue = 3;
	public int playerScore = 0;
	public bool isDead;
    public bool isDefeat;

    public Text PlayerScoreText;
    public Text PlayerLifeValueText;


    public GameObject isDefeatUi;
    public GameObject born;

	private static PlayerManager instance;

	public static PlayerManager Instance
    {
   
        get
        {
   
			return instance;
        }

        set
        {
   
			instance = value;
        }
    }

	private void Awake()
    {
   
		Instance = this;
    }

	private void Recover()
    {
   
        if (lifeValue <= 0)
        {
   
            isDefeat = true;
            Invoke("ReturnToTheMainMenu", 3);
        }   
        else
        {
   
			lifeValue--;
			GameObject go = Instantiate(born, new Vector3(-2, -8, 0), Quaternion.identity);
            go.GetComponent<Born>().createPlayer = true;
            isDead = false;
        }
    }

    public void ReturnToTheMainMenu()
    {
   
        SceneManager.LoadScene(0);
    }

	// Use this for initialization
	void Start () {
   
		
	}
	
	// Update is called once per frame
	void Update () {
   
        if (isDefeat)
        {
   
            isDefeatUi.SetActive(true);
            return;
        }
        if (isDead)
        {
   
            Recover();
        }
        PlayerScoreText.text = playerScore.ToString();
        PlayerLifeValueText.text = lifeValue.ToString();
    }
}

省了最后一步,游戏开始界面

搭建这样一个UI界面

最后一个最终代码,创建Pointer脚本,托给这个Image
顺便将两个空物体拖进去

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

public class Pointer : MonoBehaviour
{
   
    //属性
    private bool IsW = true;
    //引用
    public Transform[] Pos;
    private void Update()
    {
   
        if (Input.GetKeyDown(KeyCode.W))
        {
   
            IsW = true;
            transform.position = Pos[0].position;
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
   
            IsW = false;
            transform.position = Pos[1].position;
        }
        if (IsW = true && Input.GetKeyDown(KeyCode.J))
        {
   
            SceneManager.LoadScene(1);//切换场景
        }

    }
}

坦克大战结束了,有什么问题可以在评论区或私信发消息。
欢迎大家捧场,谢谢!!!


转载:https://blog.csdn.net/m0_47605113/article/details/110109752
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场