使用的是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