文章目录
- 一、 介绍
- 二、 制作基本物体
- 三、 控制玩家坦克移动、转向
- 四、 子弹脚本、爆炸脚本
- 五、 敌人AI寻路算法
- 六、 坦克生成点脚本
- 七、 用链表实例化地图
- 八、 玩家游戏控制器脚本
- 九、 添加音效
- 十、 资源包
一、 介绍
儿时经典游戏《坦克大战》完整复刻
发射子弹、生成敌人、消灭敌人、保护老巢、记录生命值。
二、 制作基本物体
制作玩家坦克
制作敌人坦克
制作玩家心脏
制作玩家保护罩
制作河流
制作砖头墙
制作铁墙
制作出生位置
制作出生动画
三、 控制玩家坦克移动、转向
wsad移动
数组控制转向
定义子弹发射时间间隔
发射子弹
坦克死亡效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
//定义坦克的移动速度属性
public float moveSpeed=3;
//定义子弹的旋转角度
private Vector3 bullectEulerAngles;
//定义攻击CD时间
private float timeVal;
//定义无敌状态持续时间
private float defendTimeVal=3;
//定义是否处于无敌状态
private bool isDefended=true;
//引用组件
private SpriteRenderer sr; //获取坦克的SpriteRenderer组件
public Sprite[] tankSprite; //存储坦克的四个方向的图片
public GameObject bullectPrefab; //存储子弹的预制体
public GameObject explosionPrefab; //存储爆炸特效的预制体
public GameObject defendEffectPrefab; //存储无敌特效的预制体
public AudioSource moveAudio; //获取坦克的AudioSource组件
public AudioClip[] tankAudio; //存储坦克的音效
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
//是否处于无敌状态
if (isDefended)
{
defendEffectPrefab.SetActive(true);
defendTimeVal -= Time.deltaTime;
if (defendTimeVal<=0)
{
isDefended = false;
defendEffectPrefab.SetActive(false);
}
}
}
private void FixedUpdate()
{
if (PlayerManager.Instance.isDefeat)
{
return;
}
Move();
//攻击的CD
if (timeVal >= 0.4f)
{
Attack();
}
else
{
timeVal += Time.fixedDeltaTime;
}
}
//坦克的攻击方法
private void Attack()
{
if (Input.GetKeyDown(KeyCode.Space))
{
//子弹产生的角度:当前坦克的角度+子弹应该旋转的角度。
Instantiate(bullectPrefab, transform.position,Quaternion.Euler(transform.eulerAngles+bullectEulerAngles));
timeVal = 0;
}
}
//坦克的移动方法
private void Move()
{
float v = Input.GetAxisRaw("Vertical");
transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, 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 (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];
bullectEulerAngles = new Vector3(0, 0, 90);
}
else if (h > 0)
{
sr.sprite = tankSprite[1];
bullectEulerAngles = 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 Die()
{
if (isDefended)
{
return;
}
PlayerManager.Instance.isDead = true;
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
}
四、 子弹脚本、爆炸脚本
子弹撞击不同的物体,会有不同的效果
播放爆炸动画效果
设置子弹移动速度
检测是否为玩家子弹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullect : MonoBehaviour {
public float moveSpeed = 10;
public bool isPlayerBullect;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);
}
private void OnTriggerEnter2D(Collider2D collision)
{
switch (collision.tag)
{
case "Tank":
if (!isPlayerBullect)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
case "Heart":
collision.SendMessage("Die");
Destroy(gameObject);
break;
case "Enemy":
if (isPlayerBullect)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
case "Wall":
Destroy(collision.gameObject);
Destroy(gameObject);
break;
case "Barrier":
if (isPlayerBullect)
{
collision.SendMessage("PlayAudio");
}
Destroy(gameObject);
break;
default:
break;
}
}
}
爆炸效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explosion : MonoBehaviour {
// Use this for initialization
void Start () {
Destroy(gameObject, 0.167f);
}
// Update is called once per frame
void Update () {
}
}
五、 敌人AI寻路算法
敌人在上面有三个刷新点
出生后每三秒发射一枚子弹
敌人走动的方向,是random随机值,往下走的概率最大
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
//属性值
public float moveSpeed = 3;
private Vector3 bullectEulerAngles; // 子弹的旋转角度
private float v = -1; // 坦克的竖直方向的移动量
private float h; // 坦克的水平方向的移动量
//引用
private SpriteRenderer sr; // 坦克的SpriteRenderer组件
public Sprite[] tankSprite; // 坦克朝向四个方向的精灵
public GameObject bullectPrefab; // 子弹的预制体
public GameObject explosionPrefab; // 爆炸特效的预制体
//计时器
private float timeVal; // 攻击计时器
private float timeValChangeDirection; // 改变方向计时器
private void Awake()
{
sr = GetComponent<SpriteRenderer>(); // 获取坦克的SpriteRenderer组件
}
void Start()
{
}
void Update()
{
//攻击的时间间隔
if (timeVal >= 3) // 如果攻击计时器大于等于3秒
{
Attack(); // 调用坦克的攻击方法
}
else
{
timeVal += Time.deltaTime; // 否则,增加攻击计时器的值
}
}
private void FixedUpdate()
{
Move(); // 每一帧调用坦克的移动方法
}
//坦克的攻击方法
private void Attack()
{
//子弹产生的角度:当前坦克的角度+子弹应该旋转的角度。
Instantiate(bullectPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bullectEulerAngles)); // 实例化子弹预制体
timeVal = 0; // 重置攻击计时器的值
}
//坦克的移动方法
private void Move()
{
// 当计时器 timeValChangeDirection 大于等于4时,改变坦克的移动方向
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.fixedDeltaTime, 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.fixedDeltaTime, 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 Die()
{
// 玩家得分加1
PlayerManager.Instance.playerScore++;
// 产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
// 销毁坦克
Destroy(gameObject);
}
// 当坦克与其他游戏对象发生碰撞时,检测是否为敌方坦克,如果是,则重置移动方向
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Enemy")
{
timeValChangeDirection = 4;
}
}
六、 坦克生成点脚本
上面三个出生点刷新敌人坦克,有两种坦克,随机产生一种
下面两个出生点刷新玩家坦克
延时调用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Born : MonoBehaviour {
// 玩家坦克预制件
public GameObject playerPrefab;
// 敌人坦克预制件数组
public GameObject[] enemyPrefabList;
// 是否产生玩家坦克
public bool createPlayer;
// Use this for initialization
void Start () {
// 延迟1秒后执行 BornTank 方法
Invoke("BornTank", 1f);
// 一秒后销毁当前游戏对象
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);
}
}
}
七、 用链表实例化地图
游戏地图并不是预先设置好的,是随机生成的(除了心脏),每次都不一样
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[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);
for (int i = -1; i < 2; i++)
{
CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
}
//实例化外围墙
for (int i = -11; i < 12; i++)
{
CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
}
for (int i = -11; i < 12; i++)
{
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);
}
for (int i = -8; i < 9; i++)
{
CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
}
//初始化玩家
GameObject go = Instantiate(item[3], new Vector3(-2, -8, 0), Quaternion.identity);
go.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", 4, 5);
//实例化地图
for (int i = 0; i < 60; i++)
{
CreateItem(item[1], CreateRandomPosition(), Quaternion.identity);
}
for (int i = 0; i < 20; i++)
{
CreateItem(item[2], CreateRandomPosition(), Quaternion.identity);
}
for (int i = 0; i < 20; i++)
{
CreateItem(item[4], CreateRandomPosition(), Quaternion.identity);
}
for (int i = 0; i < 20; i++)
{
CreateItem(item[5], CreateRandomPosition(), Quaternion.identity);
}
}
// 创建物体的方法
private void CreateItem(GameObject createCameObject, Vector3 createPosition, Quaternion createRotation)
{
GameObject itemGo = Instantiate(createCameObject, createPosition, createRotation);
itemGo.transform.SetParent(gameObject.transform);
itemPositionList.Add(createPosition);
}
// 产生随机位置的方法
private Vector3 CreateRandomPosition()
{
// 不生成x=-10,10的两列,y=-8,8正两行的位置
while (true)
{
Vector3 createPosition = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);
// 判断是否该位置是否已经存在
if (!HasThePosition(createPosition))
{
return createPosition;
}
}
}
//用来判断位置列表中是否有这个位置
private bool HasThePosition(Vector3 createPos)
{
// itemPositionList 是之前记录所有敌人和道具生成位置的列表
for (int i = 0; i < itemPositionList.Count; i++)
{
if (createPos == itemPositionList[i])
{
return true;
}
}
return false;
}
//产生敌人的方法
private void CreateEnemy()
{
int num = Random.Range(0, 3);
Vector3 EnemyPos = new Vector3();
// 随机选择一个 Y 轴最高(即在顶部)的位置作为敌人的位置
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);
}
// 生成敌人
CreateItem(item[3], EnemyPos, Quaternion.identity);
}
心脏周边的墙体不是随机的,每次都必须预先设定好
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Heart : MonoBehaviour {
// SpriteRenderer组件,用于设置坦克被击中后显示的图片
private SpriteRenderer sr;
// 爆炸特效预制件
public GameObject explosionPrefab;
// 死亡音效
public AudioClip dieAudio;
// 被击中后显示的破碎图片
public Sprite BrokenSprite;
// Use this for initialization
void Start () {
// 获取SpriteRenderer组件
sr = GetComponent<SpriteRenderer>();
}
// ...
// 坦克死亡的方法
public void Die()
{
// 将坦克的SpriteRenderer组件的图片设置为破碎图片
sr.sprite = BrokenSprite;
// 产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
// 设置PlayerManager类中的isDefeat变量为true,表示游戏失败
PlayerManager.Instance.isDefeat = true;
// 在坦克死亡的位置播放死亡音效
AudioSource.PlayClipAtPoint(dieAudio, transform.position);
}
}
八、 玩家游戏控制器脚本
负责游戏的基本逻辑
设置玩家生命值
设置分数
using System.Collections; // 导入系统集合命名空间
using System.Collections.Generic; // 导入泛型集合命名空间
using UnityEngine; // 导入 Unity 引擎命名空间
using UnityEngine.UI; // 导入 Unity 引擎 UI 命名空间
using UnityEngine.SceneManagement; // 导入 Unity 引擎场景管理命名空间
public class PlayerManager : MonoBehaviour { // 声明一个名为 PlayerManager 的类并继承自 MonoBehaviour
// 属性值
public int lifeValue = 3; // 玩家生命值,默认为 3
public int playerScore = 0; // 玩家分数,默认为 0
public bool isDead; // 是否死亡,默认为 false
public bool isDefeat; // 是否失败,默认为 false
// 引用
public GameObject born; // 出生点游戏对象
public Text playerScoreText; // 显示玩家分数的文本对象
public Text PlayerLifeValueText; // 显示玩家生命值的文本对象
public GameObject isDefeatUI; // 显示失败界面的游戏对象
// 单例
private static PlayerManager instance; // 声明一个静态的 PlayerManager 对象
public static PlayerManager Instance // 声明一个静态的 Instance 属性
{
get
{
return instance; // 返回 instance 对象
}
set
{
instance = value; // 设置 instance 对象
}
}
private void Awake() // Awake 方法在脚本实例被加载时执行
{
Instance = this; // 设置 instance 对象为当前实例
}
// Use this for initialization
void Start () { // Start 方法在脚本实例被启用时执行,本例中未实现
}
// Update is called once per frame
void Update () { // Update 方法在每一帧渲染时执行
if (isDefeat) // 如果失败
{
isDefeatUI.SetActive(true); // 显示失败界面
Invoke("ReturnToTheMainMenu", 3); // 延迟 3 秒后返回主界面
return; // 返回
}
if (isDead) // 如果死亡
{
Recover(); // 复活
}
playerScoreText.text = playerScore.ToString(); // 更新分数文本
PlayerLifeValueText.text = lifeValue.ToString(); // 更新生命值文本
}
private void Recover() // 复活方法
{
if (lifeValue<=0) // 如果生命值小于等于 0
{
// 游戏失败,返回主界面
isDefeat = true; // 设置失败状态为 true
Invoke("ReturnToTheMainMenu", 3); // 延迟 3 秒后返回主界面
}
else // 如果生命值大于 0
{
lifeValue--; // 生命值减一
GameObject go = Instantiate(born, new Vector3(-2, -8, 0), Quaternion.identity); // 在出生点位置实例化玩家
go.GetComponent<Born>().createPlayer = true; // 设置出生点的 createPlayer 属性为 true
isDead = false; // 设置死亡状态为 false
}
}
private void ReturnToTheMainMenu() // 返回主界面方法
{
SceneManager.LoadScene(0); // 加载场景 0
}
}
九、 添加音效
添加游戏音效、坦克行进音效、开火音效。
增加双人模式。
添加ui
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Option : MonoBehaviour {
private int choice = 1;
public Transform posOne;
public Transform posTwo;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.W))
{
choice = 1;
transform.position = posOne.position;
}
else if (Input.GetKeyDown(KeyCode.S))
{
choice = 2;
transform.position = posTwo.position;
}
if (choice==1&&Input.GetKeyDown(KeyCode.Space))
{
SceneManager.LoadScene(1);
}
}
}
十、 资源包
https://pan.baidu.com/s/1eGVzYU-pQLZntuGcFfm0Ww
密码:exqi
工程文件私