文章目录
写在前面
参考学习视频是siki学院Trigger老师的课程《如何使用unity开发坦克大战》:av29751762
感谢Trigger老师的辛勤付出!
|•’-’•) ✧我在编写脚本时很多地方进行了修改和完善,和老师的教学视频稍有出入。
我使用的是Unity2019.4.0f1和Rider,其实Unity2017以上版本都可以,脚本我个人更喜欢用Rider。
游戏效果图示
分别是游戏开始界面,正在游戏界面和游戏结束界面,玩家死亡3秒后返回主界面。
文件结构图
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,冲呀!