阶段学习总结-坦克大战(2D)案例
这是前几天学习的案例,这两天学习了NGUI和UGUI,被UI搞得很烦躁,但是今天还是将前几天学习的坦克大战总结以下。这个游戏是小时候的经典红白机游戏的复刻,见截图:
一.游戏物体
游戏中包含地图元素(墙、障碍、水、空气墙、水等)、敌方坦克、我方坦克、核心等一系列物体,在选择进入游戏后,由一个空物体生成场景-各种游戏物体,之后各游戏物体按照自身带着的脚本和组件进行运动或产生和玩家的交互,下面主要是分析刚才的内容。
二.主菜单界面
public class Option : MonoBehaviour
{
//位置1
public Transform pos1;
//位置2,这两个位置用于记录坦克图片所在位置
public Transform pos2;
//根据坦克图片所在位置对应好选择的游戏模式,模式有两种,这个案例只设计了其中的一种
private int choice = 1;
// Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{
//按下W键坦克图片移动到上方指向“1 PLAYER”选项
if (Input.GetKeyDown(KeyCode.W))
{
transform.position = pos1.position;
choice = 1;
}
//按下S键坦克图片移动到下方指向“2 PLAYER”选项
if (Input.GetKeyDown(KeyCode.S))
{
transform.position = pos2.position;
choice = 2;
}
//按下空格键加载游戏场景,案例中只设计了一个玩家的游戏场景,两个玩家的场景稍作更改即可得到
if(Input.GetKeyDown(KeyCode.Space) && choice == 1)
{
SceneManager.LoadScene(1);
}
}
}
这段脚本挂载在上面第一张图中的指向游戏模式的小坦克上,用于选择游戏模式并进入游戏场景。
二.游戏场景的生成
public class MapCreation : MonoBehaviour
{
//0,home 1,墙 2,障碍 3,出生效果 4,河流 5,草 6,空气墙
public GameObject[] items;
//用于记录已经加载好的场景资源的位置信息,防止在同一个位置重复加载物体
private List<Vector3> itemPositionList = new List<Vector3>(); //在Awake函数中加载资源,这个函数在Start之前调用
private void Awake()
{
//CreateItem函数见下方,就是封装了生成游戏物体、将游戏物体父类设置为空物体便于管理、将游戏物体的位置添加到list中三个步骤
//这里生成核心,并在核心左右各生成一个墙
CreateItem(items[0], new Vector3(0, -8, 0), Quaternion.identity);
CreateItem(items[1], new Vector3(-1, -8, 0), Quaternion.identity);
CreateItem(items[1], new Vector3(1, -8, 0), Quaternion.identity);
//这里生成玩家
GameObject gameObj = CreateItem(items[3], new Vector3(-2, -8, 0), Quaternion.identity);
gameObj.GetComponent<Born>().isEnemy = false;
//这里生成敌人
CreateItem(items[3], new Vector3(-10, 8, 0), Quaternion.identity);
CreateItem(items[3], new Vector3(0, 8, 0), Quaternion.identity);
CreateItem(items[3], new Vector3(10, 8, 0), Quaternion.identity);
//延迟调用敌人的生成函数,并每隔5秒生成一个新的敌人
InvokeRepeating("CreateEnemy", 4, 5);
//循环在核心周围生成墙,用墙将核心包围
for (int i = -1; i <= 1; i ++)
CreateItem(items[1], new Vector3(i, -7, 0), Quaternion.identity);
//循环生成空气墙,限制敌人和玩家的活动范围
for (int i = -11; i <= 11; i++)
{
CreateItem(items[6], new Vector3(i, 9, 0),Quaternion.identity);
CreateItem(items[6], new Vector3(i, -9, 0), Quaternion.identity);
}
for (int i = -8; i <= 8; i++)
{
CreateItem(items[6], new Vector3(-11, i, 0), Quaternion.identity);
CreateItem(items[6], new Vector3(11, i, 0), Quaternion.identity);
} //在随机位置生成墙、障碍、空气和草,各生成21个
for(int i = 0;i <= 20; i++)
CreateItem(items[1], CreateRandomPosition(), Quaternion.identity);
for (int i = 0; i <= 20; i++)
CreateItem(items[2], CreateRandomPosition(), Quaternion.identity);
for (int i = 0; i <= 20; i++)
CreateItem(items[4], CreateRandomPosition(), Quaternion.identity);
for (int i = 0; i <= 20; i++)
CreateItem(items[5], CreateRandomPosition(), Quaternion.identity); }
//生成游戏物体的方法封装
private GameObject CreateItem(GameObject item,Vector3 v,Quaternion r)
{
GameObject go = Instantiate(item, v, r);
go.transform.SetParent(gameObject.transform);
itemPositionList.Add(v); return go;
}
//获取随机位置的方法
private Vector3 CreateRandomPosition()
{
//最外圈不产生游戏物体
//循环遍历,随机生成位置,校验到随机位置没有物体即可返回
while (true)
{
Vector3 v = new Vector3(Random.Range(-9, 10), Random.Range(-8, 9), 0);
if (!itemPositionList.Contains(v))
{
return v;
}
}
}
//生成敌人的方法,在3个位置中随机一个位置生成敌人
private void CreateEnemy()
{
int position = Random.Range(0, 3);
switch (position)
{
case 0:
Instantiate(items[3], new Vector3(-10, 8, 0), Quaternion.identity);
break;
case 1:
Instantiate(items[3], new Vector3(0, 8, 0), Quaternion.identity);
break;
case 2:
Instantiate(items[3], new Vector3(10,8, 0), Quaternion.identity);
break;
}
}
}
这段代码挂载在一个空物体上,游戏开始时场景中只有这个空物体和摄像机等,接下来由脚本生成各种游戏物体。
三.不同游戏物体的脚本和组件
1.GameManager
/// <summary>
/// 用于管理游戏的进程和界面UI的显示
/// </summary>
public class GameManager : MonoBehaviour
{
//玩家剩余生命值
public int playerLife = 3;
//玩家得分
public int playScore = 0;
//玩家是否死亡
public bool isDead = false;
//玩家生成预制体
public GameObject born;
//游戏是否结束
public bool isDefeated = false;
//分数显示和生命值显示
public Text playerScoreText;
public Text playerLifeText;
//游戏结束的图片
public GameObject GameOverImg;
//使用单例模式,只能有一个实例,同时静态化便于其他脚本调用
public static GameManager instance;
public static GameManager Instance
{
get
{
return instance;
} set
{
instance = value;
}
} private void Awake()
{
Instance = this;
}
// Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{
//游戏结束
if (isDefeated)
{
//显示游戏结束的图片
GameOverImg.SetActive(true);
//开启协程回到主菜单
StartCoroutine(BackMain());
return;
}
//分数更新和生命值更新
playerLifeText.text = playerLife.ToString();
playerScoreText.text = playScore.ToString();
//玩家死亡调用的方法
if (isDead)
Recover(); }
/// <summary>
/// 回到主菜单的协程
/// </summary>
IEnumerator BackMain()
{
//等待两秒
yield return new WaitForSeconds(2);
//加载场景
SceneManager.LoadScene(0);
}
/// <summary>
/// 玩家被打死后调用的方法
/// </summary>
private void Recover()
{
//校验玩家剩余生命值,小于0结束游戏,否则复活玩家
if(playerLife <= 0)
{
isDefeated = true;
}
else
{
//重新生成玩家
GameObject go = Instantiate(born, new Vector3(-2,-8,0), Quaternion.identity);
go.GetComponent<Born>().isEnemy = false;
//设置玩家又复活了
isDead = false;
//扣除剩余生命值
playerLife--;
}
}
}
2.核心
public class Heart : MonoBehaviour
{
//核心死亡后会更换图片,所以获取renderer组件
private SpriteRenderer renderer;
//用于在核心死亡后更换的图片精灵
public Sprite deadSprite;
//核心被击中时的爆炸特效
public GameObject explosionPrefab;
// Start is called before the first frame update
void Start()
{
renderer = GetComponent<SpriteRenderer>();
} // Update is called once per frame
void Update()
{ }
/// <summary>
/// 核心的死亡方法
/// </summary>
private void Die()
{
//更换图片精灵
renderer.sprite = deadSprite;
//设置游戏结束,GameManager是自己写的游戏控制脚本
GameManager.instance.isDefeated = true;
//播放爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
}
}
可以看到,核心会被击中,因此需要进行碰撞检测,需要添加相应的collider组件
3.玩家和敌人的生成
/// <summary>
/// 管理玩家和敌人的生成
/// </summary>
public class Born : MonoBehaviour
{
//玩家和敌人的预制体
public GameObject playerPrefab;
public GameObject[] enemyPrefabs;
//是否是敌人
public bool isEnemy; // Start is called before the first frame update
void Start()
{
//在等待0.8s后调用玩家和敌人的生成函数,0.8s是为了生成动画有足够的时间播放和设置这个预制体生成的是否是敌人
Invoke("TankBorn", 0.8f);
//生成游戏物体后销毁特效
Destroy(gameObject, 0.8f);
} // Update is called once per frame
void Update()
{ }
/// <summary>
/// 生成坦克的方法
/// </summary>
private void TankBorn()
{
//根据isEnemy的布尔值确定是生成玩家还是敌人,敌人有多种类型,随机生成
if (isEnemy)
{
Instantiate(enemyPrefabs[Random.Range(0, 2)], transform.position, transform.rotation);
}else Instantiate(playerPrefab,transform.position,transform.rotation);
}
}
这个脚本挂载在坦克出生特效上,生成坦克时也是先生成出生特效,特效播放0.8s后再根据isEnemy布尔值生成坦克。
4.玩家坦克
/// <summary>
/// 用于控制玩家的移动、发射、死亡等
/// </summary>
public class Player : MonoBehaviour
{
//水平轴和竖直轴的值
private float h = 0;
private float v = 0;
//移动速度
public float moveSpeed = 1;
//无敌时间
public float timeDefended = 3;
//坦克朝向,根据坦克朝向旋转图片和发射炮弹
private Vector3 tankTowards = new Vector3(0,0,0);
//子弹预制体
public GameObject bulletPrefab;
//发射子弹的间隔
private float timeVal = 0;
//爆炸特效
public GameObject explosionPrefab;
//无敌预制体
private GameObject defendedPrefab;
//是否无敌
private bool isDefended = true;
// Start is called before the first frame update
void Start()
{
defendedPrefab = GameObject.FindGameObjectWithTag("Shield");
} // Update is called once per frame
void Update()
{
//无敌情况下
if(isDefended)
{
//减少无敌时间
timeDefended -= Time.deltaTime;
//校验剩余无敌时间
if(timeDefended <= 0)
{
//取消无敌状态和不显示无敌特效
isDefended = false;
defendedPrefab.SetActive(false);
}
} }
/// <summary>
/// 在这个方法中书写控制玩家移动的代码,避免碰撞时出现的抽搐
/// </summary>
private void FixedUpdate()
{
//如果游戏已经结束,坦克不受玩家控制
if (GameManager.instance.isDefeated) return;
//移动的方法
Move();
//调整坦克朝向,旋转游戏物体
transform.eulerAngles = tankTowards;
//校验发射子弹的时间间隔
if(timeVal > 0.4f)
{
//发射子弹攻击的方法
Attack();
}
//发射冷却时间相应增加
timeVal += Time.fixedDeltaTime;
}
/// <summary>
/// 坦克的攻击方法
/// </summary>
private void Attack()
{
//空格键发射子弹
if (Input.GetKeyDown(KeyCode.Space))
{
//生成子弹,并朝着玩家当前朝向发射
GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
bullet.GetComponent<Bullet>().isEnemyBullet = false;
//子弹发射冷却时间归零
timeVal = 0;
}
}
/// <summary>
/// 坦克的移动方法
/// </summary>
private void Move()
{
//获取水平轴值
h = Input.GetAxis("Horizontal");
//水平移动
transform.Translate(Vector3.right * h * Time.fixedDeltaTime * moveSpeed ,Space.World);
//设置坦克朝向
if (h < 0) tankTowards.z = 90;
if (h > 0) tankTowards.z = -90;
//已经水平移动的情况下无法同时上下移动,也就是以水平移动为主
if (h != 0) return;
//竖直方向上的移动同理
v = Input.GetAxis("Vertical");
transform.Translate(Vector3.up * v * Time.fixedDeltaTime * moveSpeed,Space.World);
if (v < 0) tankTowards.z = 180;
if (v > 0) tankTowards.z = 0; }
/// <summary>
/// 坦克的死亡方法
/// </summary>
private void Die()
{
//如果坦克无敌,不会死亡
if (isDefended) return;
//设置游戏结束
GameManager.instance.isDead = true;
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
}
5.敌人坦克
/// <summary>
/// 用于控制敌人的移动和自动发射子弹
/// </summary>
public class Enemy : MonoBehaviour
{
//水平轴和竖直轴
private float h = 0;
private float v = -1;
//移动速度和子弹发射间隔
public float moveSpeed = 1;
public float timeVal = 0;
//自动改变前进方向的时间
public float moveDirectionChangeTime = 0f;
//坦克朝向
private Vector3 tankTowards = new Vector3(0, 0, 0);
//子弹预制体
public GameObject bulletPrefab;
//爆炸预制体
public GameObject explosionPrefab; // Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{
//每3秒自动发射一颗子弹
if (timeVal > 3f)
{
Attack();
timeVal = 0;
}
timeVal += Time.deltaTime;
} private void FixedUpdate()
{
//调用运动方法
Move();
//调整坦克朝向
transform.eulerAngles = tankTowards; }
/// <summary>
/// 坦克的攻击方法
/// </summary>
private void Attack()
{
//生成子弹
GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
bullet.GetComponent<Bullet>().isEnemyBullet = true;
}
/// <summary>
/// 坦克的移动方法
/// </summary>
private void Move()
{
//坦克的移动方向任意,但是向下的可能性更高,每隔4秒自动改变移动方向
if(moveDirectionChangeTime >= 4f)
{
//0-7的随机数决定坦克的移动方向,0向上,1、2向左,3、4向右,5、6、7向下
int moveDirectionChange = Random.Range(0, 8);
if(moveDirectionChange == 0)
{
h = 0;
v = 1;
}else if(moveDirectionChange == 1 || moveDirectionChange == 2)
{
h = -1;
v = 0;
}
else if (moveDirectionChange == 3 || moveDirectionChange == 4)
{
h = 1;
v = 0;
}
else
{
h = 0;
v = -1;
}
//将改变移动方向的计时归零
moveDirectionChangeTime = 0;
}
else
{
//增加计时器
moveDirectionChangeTime += Time.fixedDeltaTime;
} //坦克进行水平移动,直到碰到障碍物为止
transform.Translate(Vector3.right * h * Time.fixedDeltaTime * moveSpeed, Space.World);
//设置坦克朝向
if (h < 0) tankTowards.z = 90;
if (h > 0) tankTowards.z = -90; if (h != 0) return; //坦克进行竖直方向移动
transform.Translate(Vector3.up * v * Time.fixedDeltaTime * moveSpeed, Space.World);
//设置坦克朝向
if (v < 0) tankTowards.z = 180;
if (v > 0) tankTowards.z = 0; }
/// <summary>
/// 坦克的死亡方法
/// </summary>
private void Die()
{ //产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//坦克死亡时玩家得分
GameManager.instance.playScore++;
//死亡
Destroy(gameObject);
}
/// <summary>
/// 碰撞的方法,这个方法让敌人坦克相互碰到时进行转向,避免坦克扎堆
/// </summary>
/// <param name="collision"></param>
private void OnCollisionEnter2D(Collision2D collision)
{
//遇到其他敌人坦克
if(collision.gameObject.tag == "Enemy")
{
//设置转向计时器为4,马上转向
moveDirectionChangeTime = 4f;
}
}
}
6.子弹预制体
/// <summary>
/// 用于控制子弹的生命周期
/// </summary>
public class Bullet : MonoBehaviour
{
//子弹发射速度
public float moveSpeed = 10;
//子弹有两种,玩家子弹和敌人子弹
public bool isEnemyBullet = true;
// Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{
//子弹一直朝前运动
transform.Translate(transform.up * moveSpeed * Time.deltaTime,Space.World);
}
/// <summary>
/// 碰撞的方法,根据子弹碰到的物体的标签类型确定子弹碰到的物体
/// </summary>
/// <param name="collision"></param>
private void OnTriggerEnter2D(Collider2D collision)
{
switch (collision.tag)
{
//如果是玩家,判断是否是敌人子弹,是敌人子弹调用玩家的死亡方法并销毁子弹
case "Tank":
if (isEnemyBullet)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
//如果是核心,判断是敌人子弹时调用核心的死亡方法并销毁子弹
case "Heart":
if (isEnemyBullet)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
//如果是敌人,判断是玩家子弹时调用敌人的死亡方法
case "Enemy":
if (!isEnemyBullet)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
//如果是墙,销毁墙和子弹
case "Wall":
Destroy(collision.gameObject);
Destroy(gameObject);
break;
//如果是障碍物,销毁子弹,子弹无法击毁障碍物
case "Barar":
Destroy(gameObject);
break;
//如果是其他子弹,销毁当前子弹(对弹)
case "Bullet":
Destroy(gameObject);
break;
default:
break;
}
}
}
7.爆炸特效
/// <summary>
/// 用于控制爆炸特效的生命周期
/// </summary>
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()
{ }
}
阶段学习总结-坦克大战(2D)案例的更多相关文章
- java学习之坦克大战游戏
总结:由于这几天快过年比较忙然后没怎么写,写代码途中一些经验总结现在给忘记了.这次的小项目感觉比上次写的思路清楚了点.没有之前第一次写那么逻辑混乱,结构也搞的比之前的要好,添加功能比较容易.学习了之前 ...
- 坦克大战学习笔记-TankWar
最近学习了马士兵老师直播的单机版坦克大战,模仿的做了一个,整理一下思路记录下来,项目git地址:https://github.com/zhuchangli/TankWar/tree/master 视频 ...
- 【跟我一起学Unity3D】做一个2D的90坦克大战之AI系统
对于AI,我的初始想法非常easy,首先他要能动,而且是在地图里面动. 懂得撞墙后转弯,然后懂得射击,其它的没有了,基于这个想法,我首先创建了一个MyTank类,用于管理玩家的坦克的活动,然后创建AI ...
- Unity3D_(游戏)2D坦克大战 像素版
2D坦克大战 像素版 游戏规则: 玩家通过上.下.左.右移动坦克,空格键发射子弹 敌人AI出身时朝向己方大本营(未防止游戏快速结束,心脏上方三个单位障碍物设为刚体) 当玩家被击杀次数>=3 ...
- unity案例入门(二)(坦克大战)
1. 案例简述 这个案例实现一个简单的坦克对战游戏,两个玩家在一个地图上PK. 2. 控制坦克移动 与案例一中小球的移动方式不同,坦克在横向上不能是平移,因此横向按键控制的应该是坦克旋转. publi ...
- javascript 面向对象制作坦克大战 (一)
PS:这个坦克大战是在网上下的一段源码之后,自己进行的重写. 写这个的目的是为了巩固自己这段时间对js的学习.整理到博客上,算是对自己近端时间学习js的一个整理. 同时也希望可以帮助到学习js的园 ...
- 《HTML5经典坦克大战》游戏(代码)
前几天粗略地学了HTML5,然后就用它写了一个<经典坦克大战>游戏. 现在想分享一下我写的代码,写得不好请大家多多指教. 给大家推荐一个网站,这个网站是为大学生而做,为方便学习编程的同学而 ...
- Html 5 坦克大战(韩顺平县版本号)
html 5例如,下面的代码段: <!DOCTYPE html> <html> <head> <meta charset="utf-8"/ ...
- 超多经典 canvas 实例,动态离子背景、移动炫彩小球、贪吃蛇、坦克大战、是男人就下100层、心形文字等等等
超多经典 canvas 实例 普及:<canvas> 元素用于在网页上绘制图形.这是一个图形容器,您可以控制其每一像素,必须使用脚本来绘制图形. 注意:IE 8 以及更早的版本不支持 &l ...
随机推荐
- Redis学习之路(二)Redis集群搭建
一.Redis集群搭建说明 基于三台虚拟机部署9个节点,一台虚拟机三个节点,创建出4个master.4个slave的Redis集群. Redis 集群搭建规划,由于集群至少需要6个节点(3主3从模式) ...
- 如何在Linux(CentOS7)环境搭建 Jenkins 服务器环境
最近,我自己要亲手搭建一套完整的企业级 CI/CD 环境,这个环节里面涉及了很多内容,没有办法把这么多的内容都放在一篇文章里,所以 Jenkins 的安装和Java 的 JDK 安装我就是分了两篇文章 ...
- Flutter 基础组件:输入框和表单
前言 Material组件库中提供了输入框组件TextField和表单组件Form. 输入框TextField 接口描述 const TextField({ Key key, // 编辑框的控制器,通 ...
- Linux 入门教程:基础操作 01
1.1 实验内容 实验楼环境介绍 常用 Shell 命令及快捷键 Linux 使用小技巧 1.2 实验知识点 Linux 基本命令 通配符的使用 查看帮助文档 终端的概念 通常我们在使用 Linux ...
- 【Azure Developer】Python代码通过AAD认证访问微软Azure密钥保管库(Azure Key Vault)中机密信息(Secret)
关键字说明 什么是 Azure Active Directory?Azure Active Directory(Azure AD, AAD) 是 Microsoft 的基于云的标识和访问管理服务,可帮 ...
- 【排序基础】1、选择排序法 - Selection Sort
文章目录 选择排序法 - Selection Sort 为什么要学习O(n^2)的排序算法? 选择排序算法思想 操作:选择排序代码实现 选择排序法 - Selection Sort 简单记录-bobo ...
- QPainter 绘制图像接口
阅读本文大概需要 3 分钟 我们在开发软件的过程中,绘制图像功能必不可少,使用 Qt 绘制图像时非常简单,只需要传递几个参数就可以实现功能,在 Qt 中绘制图像的 api有好几个 void drawI ...
- 【Oracle】等待事件之 V$SESSION_WAIT
(1)-V$SESSION_WAIT 这是一个寻找性能瓶颈的关键视图.它提供了任何情况下session在数据库中当前正在等待什么(如果session当前什么也没在做,则显示它最后的等待事件).当系统存 ...
- JMS监听Oracle AQ
该文档中,oracle版本为11g,jdk版本1.8,java项目为maven构建的springboot项目,springboot的版本为2.1.6,并使用了定时任务来做AQ监听的重连功能,解决由于外 ...
- 网络流量预测入门(一)之RNN 介绍
目录 网络流量预测入门(一)之RNN 介绍 RNN简介 RNN 结构 RNN原理 结构原理 损失函数$E$ 反向传播 总结 参考 网络流量预测入门(一)之RNN 介绍 了解RNN之前,神经网络的知识是 ...