3D雷霆战机的核心功能:玩家3D飞机的控制移动、攻击等,敌人生成、移动、攻击、死亡等。
玩家3D飞机的控制移动、攻击等功能使用一个脚本或分多个脚本处理,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//可序列化 让这个Boundary类生成的对象能够在U3D中可见
[System.Serializable]
public class Boundary
{
public float xMin=-6.5f;
public float xMax=6.5f;
public float zMin=-4.5f;
public float zMax=14f;
}
public class PlayerController : MonoBehaviour {
public AudioClip fire; //射击声音
private AudioSource m_AudioSource;//声音源
private Rigidbody m_Rigidbody;//飞机刚体组件
public float speed = 10f;//飞机速度
public Boundary bound;//画面界限范围
public GameObject bolt;//子弹 从U3D界面直接拖动获得
public Transform spawnPos;//子弹位置(U3D的特点,从外面拉进来的物体进来后会根据这个变量而强制性转换为Transform)
public float fireRate = 0.01f;//发射时间间隔
private float nextFire;//下次发射时间
void Start()
{
m_Rigidbody = gameObject.GetComponent();
m_AudioSource = gameObject.GetComponent();
}
void Update()
{
//鼠标按下左击 若当前时间大于下一次发射时间,可以发射
if (Input.GetButton("Fire1") && Time.time>nextFire)
{
nextFire = Time.time + fireRate;//获取下一次发射时间
//产生子弹位置位于飞机前方
Instantiate(bolt, spawnPos.position, this.transform.rotation);
//产生子弹生效
m_AudioSource.PlayOneShot(fire);
}
}
void FixedUpdate()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//摄像机水平方向的大小就是x,物体的y是0,摄像机垂直方向就是物体的z
Vector3 move = new Vector3(h, 0f, v);
//给刚体一个速度为10m的速度,方向根据W,A,S,D或方向箭头按钮而控制
m_Rigidbody.velocity=speed * move;
//限制飞机的行走范围 Mathf.Clamp(value,min,max)限制value的值在min~max之间
m_Rigidbody.position = new Vector3(
Mathf.Clamp(m_Rigidbody.position.x,bound.xMin,bound.xMax),
0,
Mathf.Clamp(m_Rigidbody.position.z,bound.zMin,bound.zMax)
);
}
}
敌人生成(孵化器)代码如下:其实是一个游戏控制器,有些许复杂稍微花点时间能看懂游戏是怎么个流程,模拟敌人生成。
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
public class GameController : MonoBehaviour {
public GameObject followObject;
public BigShip bigShip;//BigShip脚本控制新版玩家等级
public Vector3 spawnValues;//岩石出现位置
public Vector3 enemyValue;//敌人出现位置
public Vector3 streamBoatValue;//蒸汽船出现位置
public GameObject[] hazards;//岩石种类
public int[] numPerWave;//每波数目
public float[] spawnWait;//小行星产生间隔时间
public float startWait;//出现小行星之前的等待时间
public float[] waveWait;//每两波之间的时间间隔
public GameObject enemyObject;//敌人主体
public GameObject streamBoat;//蒸汽船主体
private int wave1 = 0; //行星波数
private int wave2 = 0; //敌机波数
public float enemyStartWait;//第一波波开始等待敌机时间
public float[] enemyWaveWait;//每波敌机出现间隔
public float[] enemyWaveNum;//每波敌机数量
public float[] enemyWait;//每个敌机产生间隔
public GUIText scoreText;//显示分数文本
private int power;//记录能量
private int score;//记录分数
public GUIText powerText;//显示能量文本
private bool gameOver;//游戏结束标志位
public GUIText gameOverText;//结束游戏文本
public GUIText helpText;//帮助文本
public GameObject[] light;//控制灯光亮灭
// Use this for initialization
void Start () {
//灯光在游戏开始的时候点亮
//for (int i = 0; i < light.Length; i++)
//{
// light[i].gameObject.SetActive(true);
//}
StartCoroutine("StreamBoat");
StartCoroutine("SpawnWaves");
StartCoroutine("Enemy");
powerText.text = "Power: " + power;
scoreText.text = "Score: " + score;
gameOverText.text = "";
helpText.text = "";
}
// Update is called once per frame
void Update () {
//结束游戏而且按下了R键 就重新启动程序
if (gameOver && Input.GetKeyDown(KeyCode.R))
{
// Application.LoadLevel(Application.loadedLevel);
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
IEnumerator StreamBoat()
{
yield return new WaitForSeconds(15f);//15秒后登场
while (true)
{
CreateStreamBoat();
yield return new WaitForSeconds(6f);//1秒出一个
if (gameOver)
break;
}
}
///
/// 随机产生蒸汽船障碍物
///
public void CreateStreamBoat()
{
Vector3 v = new Vector3(Random.Range(100f, 500f), streamBoatValue.y, Random.Range(987f, 1172f));
Quaternion q = Quaternion.identity;
GameObject o3=Instantiate(streamBoat, v, q) as GameObject;
o3.GetComponent().parent = followObject.GetComponent();
}
///
/// 协同程序延时产生每波敌机
///
///
IEnumerator Enemy()
{
yield return new WaitForSeconds(enemyStartWait);//2秒出现后一波敌机
while (true)
{
//第一波 8次(32s)(1个/次) 第二波15次(45s)(2个/次) 第三波 25个(50s)(3个/次)
for (int i = 0; i < enemyWaveNum[wave2]; i++)
{
for(int j=0;j<=wave2;j++)
CreateEnemy();
yield return new WaitForSeconds(enemyWait[wave2]);//4,3,2秒出现一次敌机
}
//当三波都通过的时候结束游戏并且优化结束界面
if (gameOver || wave2 == 2)
{
if (wave2 == 2)
GameOver();
break;
}
yield return new WaitForSeconds(enemyWaveWait[wave2]);//0,12,8秒出现下一波 其实第三波的时间不用设置
wave2++;
}
}
///
/// 随机单个产生敌人飞机
///
public void CreateEnemy()
{
Vector3 v = new Vector3(Random.Range(50f, 600f), enemyValue.y, Random.Range(enemyValue.z+30f, enemyValue.z + 60.0f));
Quaternion q = Quaternion.identity;
GameObject o = Instantiate(enemyObject, v, q) as GameObject;
o.GetComponent().parent = followObject.GetComponent();
}
//IEnumberator是协同程序关键字 不要弄错了IEnumerable这个不是!!!
///
/// 协同程序批量产生岩石(启动游戏2秒后执行,一波10个岩石,产生间隔是1s,每波间隔4s
///
///
IEnumerator SpawnWaves()
{
int k = 2;
yield return new WaitForSeconds(startWait);//2秒后出现
while (true)
{
//第一波 10个(30)第二波 18个(54)第三波 25个(50)
for (int i = 0; i < numPerWave[wave1]; i++)
{
for(int j=0;j<=wave1+k;j++)
Spawn();
yield return new WaitForSeconds(spawnWait[wave1]);//3, 3 ,2
}
k++;
//结束游戏就不再生成
if (gameOver || wave1 == 2)
{
if (wave1 == 2)
GameOver();
break;
}
yield return new WaitForSeconds(waveWait[wave1]);//2,3,4
wave1++;
}
}
///
/// 随机地产生石头
///
public void Spawn()
{
Vector3 p = new Vector3();
GameObject o = hazards[Random.Range(0, hazards.Length)];//从3种岩石中随机选一种生成
if(o.gameObject.name=="Asteroid")
p = new Vector3(Random.Range(150f, 600f), spawnValues.y, Random.Range(412f,450f));//生成位置随机
if(o.gameObject.name=="Asteroid2")
p = new Vector3(Random.Range(150f, 600f), spawnValues.y, Random.Range(505f,572f));//生成位置随机
if(o.gameObject.name=="Asteroid3")
p = new Vector3(Random.Range(150f, 600f), spawnValues.y, Random.Range(671f,811f));//生成位置随机
Quaternion q = Quaternion.identity;//物体无旋转诞生
Instantiate(o, p, q);
}
public void AddPower()
{
power++;
powerText.text = "Power: " + power;
if (power == 10)
{
bigShip.LevelUp();
}
if (power == 30)
{
bigShip.LevelUp();
}
}
//显示分数和增加分数
public void AddScore(int v)
{
score += v;
scoreText.text = "Score: " + score;
}
//显示结束界面
public void GameOver()
{
gameOver = true ;
enemyObject.GetComponent().EnemyOver();//敌机射击结束
gameOverText.text = "Game Over!";
helpText.text = "Press 'R' to Restart";
}
}
敌人的移动、攻击、死亡等脚本,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class enemyController : MonoBehaviour {
private GameUIController gameUIController;//UI控制器
private GameObject gameUIObject; //UI界面
public GameObject bolt;//敌机子弹
public Transform boltPos;//敌机子弹位置
public GameObject playerObject;//玩家物体
public float startWait;//等待敌机发射时间
public float shootWait;//敌机射击间隔
private Rigidbody rigidbody;//敌机刚体
private Transform transform;//敌机Transform
public float speed=15f;//敌机速度
private Vector3 vector;//控制敌机左右方向的三维向量
private float perSub=5f;//控制敌机左右方向的每帧移动距离
private AudioSource m_AudioSource;//声音源
public AudioClip fire;//敌机射击声音
private bool enemyOver;//敌机子弹发射结束标志位 (一定要是private)
void Start ()
{
gameUIObject = GameObject.FindWithTag("MainCamera");
gameUIController = gameUIObject.GetComponent();
m_AudioSource = gameObject.GetComponent();
StartCoroutine("CreateEnemyBolt");
transform=gameObject.GetComponent();
rigidbody = gameObject.GetComponent();
rigidbody.velocity = speed * transform.forward;
transform.Rotate(new Vector3(0, 180, 0));
}
///
/// 协同程序延时产生敌机子弹直至敌机被摧毁或离开场地
///
///
IEnumerator CreateEnemyBolt()
{
yield return new WaitForSeconds(startWait);
while(true)
{
//实例化敌机子弹
EnemyBolt();
//发射子弹声音
//判断是否静音状态
if (!gameUIController.flag)
m_AudioSource.PlayOneShot(fire);
m_AudioSource.volume = gameUIController.soundValume;
yield return new WaitForSeconds(shootWait);
if (enemyOver)
break;
}
}
///
/// 产生敌机子弹
///
public void EnemyBolt()
{
GameObject o = Instantiate(bolt, boltPos.position, boltPos.rotation) as GameObject;
}
void FixedUpdate()
{
vector=new Vector3(transform.position.x+perSub,transform.position.y,transform.position.z);
if (transform.position.x <= 50)
{
perSub = 5f;
}
if (transform.position.x >= 600)
{
perSub = -5f;
}
//敌机左右移动代码 //设置沿着y轴旋转1度
transform.SetPositionAndRotation(vector,new Quaternion(0,1,0,0));
}
void Update () {
//使敌机时刻朝向玩家
transform.LookAt(playerObject.GetComponent());
// Debug.Log(playerObject.GetComponent());
//Debug.Log(this.gameObject.transform.position);
//t += 1f / 10 * Time.deltaTime;
//targetObject.position = Vector3.Lerp(targetObject.position, transform.position, t);
}
///
/// 敌机销毁
///
public void EnemyOver()
{
enemyOver = true;
}
}
还有一个代码就是关于跟踪子弹的脚本实现如下,这个比较高级:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowEnemy : MonoBehaviour
{
public GameObject followObject;//跟踪目标父物体(父物体下有若干个敌人物体)
public GameObject bolt3Parent;//跟踪子弹父物体(父物体下有若干个跟踪子弹物体)
private Vector3 target;
private float speed = 420f;
private bool flag = true;
private Transform transform;
private Rigidbody rigidbody;
///
// 实现一个跟踪子弹跟踪一个敌人效果
///
void Update()
{
//遍历敌人(followObject是敌人的父物体,childCount是敌人总数)
for (int i = 0; i < followObject.transform.childCount; i++)
{
//当敌机下标i超出子弹下标最大值时退出循环(避免子弹数组索引溢出bug)
if (i >= bolt3Parent.transform.childCount)
{
break;
}
Transform enemyTransform, bolt3Transform;
//获取第i个敌人transform信息
enemyTransform = followObject.transform.GetChild(i);
//获取第i个子弹transform信息
bolt3Transform = bolt3Parent.transform.GetChild(i);
//计算出子弹到敌人的单位化向量target(即目标朝向)
target = (enemyTransform.position - bolt3Transform.position).normalized;
//经测试a会在(0~正数无穷大)之间徘徊,当子弹自身z轴向量与目标朝向的夹角越大,a值就越小,
//而当a的值小于1 /3时,说明夹角大于15°(测试得出),当大于15°时,朝向会每帧偏转(夹角* (5/夹角)=5)°朝着目标朝向偏转,也就是每帧偏转5°(这句话可能会有点难理解下面说明)
//下面的这行代码的5.0f决定是5°,而夹角*(5/夹角)这个公式是因为Vector3.Slerp这个方法,它具体是怎么个原理可百度,下面我简单说下,
//Slerp是会将第一个参数源向量根据第三个参数权值,目标向量是第二个参数向量,进行一次插值运算得出一个新的向量,
//例如:第三个参数是1/2的话,新生成的向量就是第一个参数向量和第二个参数向量之间的中线向量,若是1的话,返回的新向量就是第二个参数向量,若为0就是第一个参数向量(也就是不变)
//所以上面这些总结成数学公式可以差不多地理解为:为了生成新的向量而第一个参数向量要偏转的角度 = (第一参数和第二参数之间的)夹角 * a
float a = 5.0f / (Vector3.Angle(bolt3Transform.forward, target));// a = 5/夹角
//a<1/3就是夹角大于15°的情况(这是我测试得出的,必然正确!!!相信我!!!你也可以自己测试下)
if (a < 1.0f / 3.0f)
{
//在上方Slerp的说明可理解,下面的bolt3Transform.forward = Vector3.Slerp(bolt3Transform.forward, target, a).normalized;就相当于
//已知 a = 5 / 夹角 , 那么第一个参数向量要偏转的角度 = 夹角 * a = 夹角 * (5/夹角) = 5
//最后结论是:子弹朝向blot3Transform.forward会每帧朝着目标向量target转动5度°,从而实现不断地趋向于目标朝向
bolt3Transform.forward = Vector3.Slerp(bolt3Transform.forward, target, a).normalized;
}
else//夹角小于等于15°
{
//直接朝着目标点跑,也就是子弹朝向等于target(目标朝向),这是因为第三参数是1(很明显)
bolt3Transform.forward = Vector3.Slerp(bolt3Transform.forward, target, 1).normalized;
}
//这一步不用说了吧.
bolt3Transform.position += bolt3Transform.forward * speed * Time.deltaTime;
}
//上面已经把能用的跟踪子弹都用了,但是可能还会有剩余的跟踪子弹,下面就是处理剩余的跟踪子弹怎么飞的问题
//当子弹超出敌机数量时,保证额外的子弹能够正常向前移动
if (bolt3Parent.transform.childCount > followObject.transform.childCount)
{
//处理完没处理跟踪功能的子弹:默认向前移动
for (int i = followObject.transform.childCount; i < bolt3Parent.transform.childCount; i++)
{
transform = bolt3Parent.transform.GetChild(i);
transform.position += transform.forward * speed * Time.deltaTime;
}
}
}
//总结:实现子弹与目标点角度小于等于15°时直接朝着目标点飞,而大于15°时子弹会每帧5°来调整朝向目标点飞的这么一个跟踪效果
//后期会进一步完善,模拟更加现实的子弹跟踪效果,即考虑当夹角大于15°时,角度越大其每帧偏移角度也就越大,当角度越接近15°时,会不断趋向一个固定偏转角度,而不是每帧都一样的偏转角度
//其实实现方法我也想到了,将夹角转变为弧度来看待这个问题,就非常简单了,算夹角的弧度公式如下:
//弧度= 夹角 * 2π/360 = 夹角 * Mathf.Rad2Deg,因为夹角范围[0,180]那么弧度范围[0,π],π就是3.14
//假设简单点,我设置一个偏移最大角度为10°和偏移最小角度5°,那么上面 a = (弧度+π)/π * 5.0f / 夹角 ,当弧度极限为π时候,偏移角度为10°,当弧度为15度时,也就是刚好达成进入if的条件,偏移角度会比5度大一点
}
总之以上就是3D雷霆战机的核心部分的代码。