20.游戏场景-游戏主逻辑-开火点相关-逻辑处理
20.1 知识点
创建开火点对象FireObject脚本继承MonoBehaviour。创建八个空对象作为八宫格的开火点对象。开火点对象的位置应该是要实时更新的,因为分辨率自适应可能造成位置的不同。创建一个表示开火点位置的类型的枚举E_Pos_Type,它有八个值,分别表示在屏幕上的左上、上、右上、左、右、左下、下和右下。在FireObject脚本关联一个开火点位置枚举。并且在场景中创建空对象。
/// <summary>
/// 表示开火点位置的类型
/// </summary>
public enum E_Pos_Type
{
TopLeft,
Top,
TopRight,
Left,
Right,
BottomLeft,
Bottom,
BottomRight,
}
public class FireObject : MonoBehaviour
{
public E_Pos_Type type;
}
在 Update() 函数中,首先要更新开火点的位置,目的是分辨率自适应。所以创建更新开火点的位置发明方法UpdatePos。在 UpdatePos() 函数中,根据开火点的类型来更新它在屏幕上的位置,并把屏幕坐标转换为世界坐标。我们应该先打印出玩家对象世界坐标转屏幕坐标的位置。看看玩家坐标所在位置(0,0,0)在屏幕坐标的z轴是多少。这样可以得到摄像机在y=0的屏幕的横截面转换成拼屏幕坐标的z轴的值,比如示例中的351。设置一个三维向量变量作为屏幕位置。在 UpdatePos() 函数中一开始就设置这个屏幕位置的z轴为351。根据所在的开火点位置不同设置好屏幕位置的值。最后把屏幕位置转成世界坐标设置到这个脚本挂载的对象上。我们可以创建一个初始向量用于开火初始方向,可以辅助不同开火组的泛型,根据不同的开火点类型赋值不同的向量。
public class FireObject : MonoBehaviour
{
// 开火点的类型
public E_Pos_Type type;
// 表示屏幕上的点
private Vector3 screenPos; // 表示屏幕中的二维坐标
// 初始发射子弹的方向,主要用于作为散弹的初始方向用于计算
private Vector3 initDir; // 表示炮管的方向
// 这个是用于发射散弹时,记录上一次的方向
private Vector3 nowDir; // 表示当前发射子弹的方向
void Update()
{
//用于测试得到 玩家转屏幕坐标后 横截面的 z轴值 要注释
//print(Camera.main.WorldToScreenPoint(PlayerObject.Instance.transform.position));
//更新位置
UpdatePos();
}
// 根据点的类型来更新它的位置
private void UpdatePos()
{
// 这里设置为和主玩家位置转屏幕坐标后的Z位置一样,目的是让点和玩家所在的横截面是一致的
screenPos.z = 351;
switch (type)
{
case E_Pos_Type.TopLeft:
screenPos.x = 0;
screenPos.y = Screen.height;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Top:
screenPos.x = Screen.width / 2;
screenPos.y = Screen.height;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.TopRight:
screenPos.x = Screen.width;
screenPos.y = Screen.height;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
case E_Pos_Type.Left:
screenPos.x = 0;
screenPos.y = Screen.height / 2;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Right:
screenPos.x = Screen.width;
screenPos.y = Screen.height / 2;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
case E_Pos_Type.BottomLeft:
screenPos.x = 0;
screenPos.y = 0;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Bottom:
screenPos.x = Screen.width / 2;
screenPos.y = 0;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.BottomRight:
screenPos.x = Screen.width;
screenPos.y = 0;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
}
// 把屏幕点转成我们的世界坐标点,得到我们想要的坐标
this.transform.position = Camera.main.ScreenToWorldPoint(screenPos);
}
}
创建一些字段帮助我们写当前要发射的开火组的方法。fireInfo 表示当前开火点的数据信息;nowNum 表示当前开火点剩余的子弹数量;nowCD 表示当前开火点的冷却时间;nowDelay 表示当前开火点的延迟时间;nowBulletInfo 表示当前开火点使用的子弹信息;changeAngle 表示散弹时每颗子弹的间隔角度;nowDir 表示用于发射散弹时记录上一次方向。创建ResetFireInfo重置当前要发射的炮台数据方法。定义一个规则,只有当 nowCD 和 nowNum 都为 0 时才认为需要重新获取发射点数据。如果 nowCD 和 nowNum 都不为 0,则直接返回。重置函数会判断组间休息时间。如果 fireInfo 不为 null,则将 nowDelay 减去 Time.deltaTime,减去组间休息的时间。如果 nowDelay 大于 0,则表示还在组间休息中,直接返回。重置函数会从开火数据列表中随机取出一条来按照规则发射炮弹。它首先获取游戏数据管理器中的开火点数据列表,然后从列表中随机选择一个开火点数据,并将其赋值给 fireInfo 变量。获得了随机的开火单条数据。由于不能直接改变数据中的内容,所以函数会使用一开始声明的成员变量来存储数据。它将 fireInfo 中的 num、cd 和 delay 分别赋值给 nowNum、nowCD 和 nowDelay 变量。重置函数会通过发火点数据取出当前要使用的子弹数据信息。它首先将 fireInfo.ids 按照 , 分割成字符串数组,然后分别解析出开始 ID 和结束 ID。然后使用 Random.Range() 函数在开始 ID 和结束 ID 之间随机选择一个子弹 ID,并根据该 ID 从游戏数据管理器中获取子弹信息,赋值给 nowBulletInfo 变量。如果当前开火点类型为散弹,则需要计算每颗子弹的间隔角度。函数会根据开火点位置不同来设置不同的间隔角度。重置函数会在Update中每帧调用。
//当前开火点的数据信息
private FireInfo fireInfo;
private int nowNum;
private float nowCD;
private float nowDelay;
//当前组开火点 使用的子弹信息
private BulletInfo nowBulletInfo;
//散弹时 每颗子弹的间隔角度
private float changeAngle;
//表示屏幕上的点
private Vector3 screenPos;
//初始发射子弹的方向 主要用于作为散弹的初始方向 用于计算
private Vector3 initDir;
//这个是用于发射散弹时 记录上一次的方向的
private Vector3 nowDir;
void Update()
{
//每一次 都检测 是否需要 重置 开火点 数据
ResetFireInfo();
}
// 重置当前要发射的炮台数据
private void ResetFireInfo()
{
// 自己定一个规则,只有当 CD 和 Num 都为 0 时才认为需要重新获取我们的发射点数据
if (nowCD != 0 && nowNum != 0)
return;
// 组间休息时间判断
if (fireInfo != null)
{
nowDelay -= Time.deltaTime;
// 如果还在组间休息中
if (nowDelay > 0)
return;
}
// 从数据中随机取出一条来按照规则发射炮弹
List<FireInfo> list = GameDataMgr.Instance.fireData.fireInfoList;
fireInfo = list[Random.Range(0, list.Count)];
// 不能直接改变数据中的内容,应该拿变量临时存储下来,不会影响数据本身
nowNum = fireInfo.num; // 当前炮塔能够发射的子弹数量
nowCD = fireInfo.cd; // 当前炮塔发射每颗子弹的时间间隔
nowDelay = fireInfo.delay; // 当前炮塔和同组炮塔的时间间隔
// 通过发火点数据取出当前要使用的子弹数据信息
// 得到开始 ID 和结束 ID,用于随机取子弹的信息
string[] strs = fireInfo.ids.Split(',');
int beginID = int.Parse(strs[0]); // 当前规则炮弹 ID 的开始值
int endID = int.Parse(strs[1]); // 当前规则炮弹 ID 的结束值
int randomBulletID = Random.Range(beginID, endID + 1); // 随机获取该规则下的一种子弹 ID
nowBulletInfo = GameDataMgr.Instance.bulletData.bulletInfoList[randomBulletID - 1];//得到当前子弹信息
// 如果是散弹就需要计算我们的间隔角度
if (fireInfo.type == 2)
{
switch (type)
{
//这四个点是旋转90度发散弹
case E_Pos_Type.TopLeft:
case E_Pos_Type.TopRight:
case E_Pos_Type.BottomLeft:
case E_Pos_Type.BottomRight:
changeAngle = 90f / (nowNum + 1);
break;
//这四个点是旋转180度发散弹
case E_Pos_Type.Top:
case E_Pos_Type.Left:
case E_Pos_Type.Right:
case E_Pos_Type.Bottom:
changeAngle = 180f / (nowNum + 1);
break;
}
}
}
创建检测开火UpdateFire函数。用于检测开火。首先,开火函数会判断当前状态是否需要发射子弹。如果 nowCD 和 nowNum 都为 0,则表示不需要发射子弹,直接返回。接下来,开火函数会更新 nowCD 的值。如果 nowCD 大于 0,则表示还在冷却中,直接返回。然后,开火函数会根据开火点数据中定义的类型来发射子弹。如果类型为 1,则表示一个一个地发射子弹朝向玩家。开火函数会动态创建子弹对象,并动态添加子弹脚本。然后将当前的子弹数据传入子弹脚本进行初始化,并设置子弹的位置和朝向。最后,将 nowNum 减 1,并重置 nowCD 的值。如果类型为 2,则表示发射散弹。如果 nowCD 为 0,则表示无 CD,一瞬间发射所有的散弹。函数会循环 nowNum 次,每次都动态创建子弹对象并动态添加子弹脚本。然后将当前的子弹数据传入子弹脚本进行初始化,并设置子弹的位置和朝向。最后,将 nowCD 和 nowNum 都重置为 0。如果 nowCD 不为 0,则表示每次都会旋转一个角度得到一个新的方向来发射一颗子弹。函数会动态创建子弹对象并动态添加子弹脚本。然后将当前的子弹数据传入子弹脚本进行初始化,并设置子弹的位置和朝向。最后,将 nowNum 减 1,并重置 nowCD 的值根据当前nowNum是不是为01重置。这个函数最后再Update中调用。
void Update()
{
//发射子弹
UpdateFire();
}
// 检测开火
private void UpdateFire()
{
// 如果当前状态不需要发射子弹,则直接返回
if (nowCD == 0 && nowNum == 0)
return;
// 更新 CD 值
nowCD -= Time.deltaTime;
if (nowCD > 0)
return;
GameObject bullet;
BulletObject bulletObj;
switch (fireInfo.type)
{
// 一个一个地发射子弹,朝向玩家
case 1:
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
bullet.transform.rotation = Quaternion.LookRotation(PlayerObject.Instance.transform.position - this.transform.position);
// 发射一颗子弹
--nowNum;
// 重置 CD 值
nowCD = nowNum == 0 ? 0 : fireInfo.cd;
break;
// 发射散弹
case 2:
// 如果无 CD,则一瞬间发射所有散弹
if (nowCD == 0)
{
for (int i = 0; i < nowNum; i++)
{
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
// 每次都会旋转一个角度得到一个新的方向
nowDir = Quaternion.AngleAxis(changeAngle * i, Vector3.up) * initDir;
bullet.transform.rotation = Quaternion.LookRotation(nowDir);
}
// 因为是瞬间创建完所有子弹,所以重置数据
nowCD = nowNum = 0;
}
//有CD一颗一颗的发
else
{
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
// 每次都会旋转一个角度得到一个新的方向
nowDir = Quaternion.AngleAxis(changeAngle * (fireInfo.num - nowNum), Vector3.up) * initDir;
bullet.transform.rotation = Quaternion.LookRotation(nowDir);
// 发射一颗子弹
--nowNum;
// 重置 CD 值
nowCD = nowNum == 0 ? 0 : fireInfo.cd;
}
break;
}
}
在场景中创建一个Main空对象,创建一个Main脚本并挂载。这个脚本是用于动态创建选择好的角色的。
public class Main : MonoBehaviour
{
void Start()
{
//根据开始场景中选择的英雄 来动态创建 飞机
RoleInfo info = GameDataMgr.Instance.GetNowSelHeroInfo();
//根据数据中的 资源路劲 进行动态创建
GameObject obj = Instantiate(Resources.Load<GameObject>(info.resName));
//动态添加脚本
PlayerObject playerObj = obj.AddComponent<PlayerObject>();
//根据信息设置值
playerObj.speed = info.speed * 20;
playerObj.maxHp = 10;
playerObj.nowHp = info.hp;
playerObj.roundSpeed = 20;
//更新界面上显示的血量
GamePanel.Instance.ChangeHp(info.hp);
}
}
给各个飞机预制体添加碰撞器后,发现在开始场景中被弹开了。因为开始场景飞机的父物体也有碰撞器,可以把父物体碰撞器移除了。
20.2 知识点代码
FireObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 表示开火点位置的类型
/// </summary>
public enum E_Pos_Type
{
TopLeft,
Top,
TopRight,
Left,
Right,
BottomLeft,
Bottom,
BottomRight,
}
public class FireObject : MonoBehaviour
{
// 开火点的类型
public E_Pos_Type type;
// 当前开火点的数据信息
private FireInfo fireInfo; // 当前炮塔的炮弹发射规则
private int nowNum; // 当前炮塔还能发射多少颗炮弹
private float nowCD; // 当前炮塔还需要等待多长时间才能再次发射炮弹
private float nowDelay; // 当前炮塔和同组炮塔之间的时间间隔
// 当前组开火点使用的子弹信息
private BulletInfo nowBulletInfo; // 当前使用的子弹信息
// 散弹时每颗子弹的间隔角度
private float changeAngle; // 散弹模式下炮塔发射子弹的角度变化
// 表示屏幕上的点
private Vector3 screenPos; // 表示屏幕中的二维坐标
// 初始发射子弹的方向,主要用于作为散弹的初始方向用于计算
private Vector3 initDir; // 表示炮管的方向
// 这个是用于发射散弹时,记录上一次的方向
private Vector3 nowDir; // 表示当前发射子弹的方向
void Update()
{
//用于测试得到 玩家转屏幕坐标后 横截面的 z轴值 要注释
//print(Camera.main.WorldToScreenPoint(PlayerObject.Instance.transform.position));
//更新位置
UpdatePos();
//每一次 都检测 是否需要 重置 开火点 数据
ResetFireInfo();
//发射子弹
UpdateFire();
}
// 根据点的类型来更新它的位置
private void UpdatePos()
{
// 这里设置为和主玩家位置转屏幕坐标后的Z位置一样,目的是让点和玩家所在的横截面是一致的
screenPos.z = 351;
switch (type)
{
case E_Pos_Type.TopLeft:
screenPos.x = 0;
screenPos.y = Screen.height;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Top:
screenPos.x = Screen.width / 2;
screenPos.y = Screen.height;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.TopRight:
screenPos.x = Screen.width;
screenPos.y = Screen.height;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
case E_Pos_Type.Left:
screenPos.x = 0;
screenPos.y = Screen.height / 2;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Right:
screenPos.x = Screen.width;
screenPos.y = Screen.height / 2;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
case E_Pos_Type.BottomLeft:
screenPos.x = 0;
screenPos.y = 0;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.Bottom:
screenPos.x = Screen.width / 2;
screenPos.y = 0;
// 初始化炮管方向为向右
initDir = Vector3.right;
break;
case E_Pos_Type.BottomRight:
screenPos.x = Screen.width;
screenPos.y = 0;
// 初始化炮管方向为向左
initDir = Vector3.left;
break;
}
// 把屏幕点转成我们的世界坐标点,得到我们想要的坐标
this.transform.position = Camera.main.ScreenToWorldPoint(screenPos);
}
// 重置当前要发射的炮台数据
private void ResetFireInfo()
{
// 自己定一个规则,只有当 CD 和 Num 都为 0 时才认为需要重新获取我们的发射点数据
if (nowCD != 0 && nowNum != 0)
return;
// 组间休息时间判断
if (fireInfo != null)
{
nowDelay -= Time.deltaTime;
// 如果还在组间休息中
if (nowDelay > 0)
return;
}
// 从数据中随机取出一条来按照规则发射炮弹
List<FireInfo> list = GameDataMgr.Instance.fireData.fireInfoList;
fireInfo = list[Random.Range(0, list.Count)];
// 不能直接改变数据中的内容,应该拿变量临时存储下来,不会影响数据本身
nowNum = fireInfo.num; // 当前炮塔能够发射的子弹数量
nowCD = fireInfo.cd; // 当前炮塔发射每颗子弹的时间间隔
nowDelay = fireInfo.delay; // 当前炮塔和同组炮塔的时间间隔
// 通过发火点数据取出当前要使用的子弹数据信息
// 得到开始 ID 和结束 ID,用于随机取子弹的信息
string[] strs = fireInfo.ids.Split(',');
int beginID = int.Parse(strs[0]); // 当前规则炮弹 ID 的开始值
int endID = int.Parse(strs[1]); // 当前规则炮弹 ID 的结束值
int randomBulletID = Random.Range(beginID, endID + 1); // 随机获取该规则下的一种子弹 ID
nowBulletInfo = GameDataMgr.Instance.bulletData.bulletInfoList[randomBulletID - 1];//得到当前子弹信息
// 如果是散弹就需要计算我们的间隔角度
if (fireInfo.type == 2)
{
switch (type)
{
//这四个点是旋转90度发散弹
case E_Pos_Type.TopLeft:
case E_Pos_Type.TopRight:
case E_Pos_Type.BottomLeft:
case E_Pos_Type.BottomRight:
changeAngle = 90f / (nowNum + 1);
break;
//这四个点是旋转180度发散弹
case E_Pos_Type.Top:
case E_Pos_Type.Left:
case E_Pos_Type.Right:
case E_Pos_Type.Bottom:
changeAngle = 180f / (nowNum + 1);
break;
}
}
}
// 检测开火
private void UpdateFire()
{
// 如果当前状态不需要发射子弹,则直接返回
if (nowCD == 0 && nowNum == 0)
return;
// 更新 CD 值
nowCD -= Time.deltaTime;
if (nowCD > 0)
return;
GameObject bullet;
BulletObject bulletObj;
switch (fireInfo.type)
{
// 一个一个地发射子弹,朝向玩家
case 1:
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
bullet.transform.rotation = Quaternion.LookRotation(PlayerObject.Instance.transform.position - this.transform.position);
// 发射一颗子弹
--nowNum;
// 重置 CD 值
nowCD = nowNum == 0 ? 0 : fireInfo.cd;
break;
// 发射散弹
case 2:
// 如果无 CD,则一瞬间发射所有散弹
if (nowCD == 0)
{
for (int i = 0; i < nowNum; i++)
{
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
// 每次都会旋转一个角度得到一个新的方向
nowDir = Quaternion.AngleAxis(changeAngle * i, Vector3.up) * initDir;
bullet.transform.rotation = Quaternion.LookRotation(nowDir);
}
// 因为是瞬间创建完所有子弹,所以重置数据
nowCD = nowNum = 0;
}
//有CD一颗一颗的发
else
{
// 动态创建子弹对象
bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
// 动态添加子弹脚本
bulletObj = bullet.AddComponent<BulletObject>();
// 把当前的子弹数据传入子弹脚本进行初始化
bulletObj.InitInfo(nowBulletInfo);
// 设置子弹的位置和朝向
bullet.transform.position = this.transform.position;
// 每次都会旋转一个角度得到一个新的方向
nowDir = Quaternion.AngleAxis(changeAngle * (fireInfo.num - nowNum), Vector3.up) * initDir;
bullet.transform.rotation = Quaternion.LookRotation(nowDir);
// 发射一颗子弹
--nowNum;
// 重置 CD 值
nowCD = nowNum == 0 ? 0 : fireInfo.cd;
}
break;
}
}
}
Main
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
void Start()
{
//根据开始场景中选择的英雄 来动态创建 飞机
RoleInfo info = GameDataMgr.Instance.GetNowSelHeroInfo();
//根据数据中的 资源路劲 进行动态创建
GameObject obj = Instantiate(Resources.Load<GameObject>(info.resName));
//动态添加脚本
PlayerObject playerObj = obj.AddComponent<PlayerObject>();
//根据信息设置值
playerObj.speed = info.speed * 20;
playerObj.maxHp = 10;
playerObj.nowHp = info.hp;
playerObj.roundSpeed = 20;
//更新界面上显示的血量
GamePanel.Instance.ChangeHp(info.hp);
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com