项目场景:
Unity API查询:Unity - Scripting API:
素材 : 百度搜图片:地面纹理,找到喜欢的贴图导入Import资源即可
自制一个坦克大战:需要的GameObjects有:Plane,Bullets,EnemyTank,PlaytTank,Wall空气墙
Main Camera,Directional Light
由于音频视频导入和UI制作和3D建模都不会,只能搞搞基础的了
以下是实机展示:
空气墙的制作:先在Plane边缘建立一个墙,尽量将墙边缘贴近Plane后用v键使重合,然后Crtl+D复制三份,通过移动和旋转角度和Plane重合,最后关闭Inspector面板的Mesh Mender组件,游戏运行时就会生成
己方坦克的要实现的功能有;移动,旋转,发射炮弹,创建一个PlayerControl.cs的脚本
移动,旋转通过虚拟轴GexAxis()获取正负方向Horizontal,Vertical
创建一个炮弹发射点的空对象
发射炮弹用transform.Find("目录")找到这个空对象
同时创建一个球体即炮弹的预设体
用圆柱体和长方体创建一个坦克并给它脚本和刚体,把坦克的GameObject全放在空对象中
再创建一个Bullets.cs的组件,负责控制炮弹的速度和方向
**创建敌方坦克**
因为要生成多个敌方坦克,需要将敌方坦克设置为一个预设体,颜色要和玩家的有所区分,还有坦克生成的最大数量,生成的时间间隔。首先他得是个刚体,其次得有计数器counter,计时器timer和时间间隔interval控制它生成的时间
需要创建一个TankControl.cs的脚本负责坦克的基本生成,移动等等。。。同时还需要刚体,计时器
最重要的还是随机生成,使用Random.Range()来随机生成的位置,角度等等。。
Example:对于旋转的角度来说,生成的是欧拉角,但要转成四元数,因为你要使用
Instantiate(GameObject,Vector3,Quaternion)这个方法;这时候我们就要使用Quaternion.Euler()这个方法来转化为四元数了。
既然生成了坦克,就不得不考虑模型重叠的问题了,添加刚体的情况下坦克容易被搞飞。
这时候就要用Physcis.CheckSphere(Vector3,float,int layerMask),球形检测,参数分别代表的意思是中心位置,半径,检测的Layer层,
第三个参数:layerMask涉及位或运算,由于我不会就简单介绍一下。(1 << 8),前面的“1 <<”是固定的,后面的8是只检测第八层,如果反过来只想检测除了八层的其它层就要加括号前加“~”号。
**设置敌方坦克参数**
综上我们已经设计好了坦克的生成,需要设置敌方坦克AI了。
对于敌方坦克,我们需要创建他们三种状态的方法,开火Fire(),朝玩家移动Move(),巡逻Patrol()
敌方坦克要攻击就得有炮弹,发射前先瞄准玩家的坦克,但首先判断自己前方的多少米是玩家还是自己的友军,用物理射线判断标签,给敌军加一个“Enemy”标签,玩家"Player"标签
private bool CheckForwardFriend(float dis)
{
//发射物理射线
if(Physics.Raycast(firePoint.position,
transform.forward ,out hit,dis))
{
if(hit.collider.transform.root.tag == "Enemy")
{
return true;
}
}
return false;
}
返回的是true,则不可以开火return,false则可以
同样,发射炮弹也需要时间间隔和计时器(计时器用完要归零)
if (timer > fireInterval)
{
//生成炮弹
GameObject blt =
Instantiate(bullerPrefab,
firePoint.position, Quaternion.identity);
//给炮弹一个速度
blt.GetComponent<Rigidbody>().velocity =
transform.forward * bltSpeed;
blt.GetComponent<Bullets>().moveDir = transform.forward;
Destroy(blt, 5f);
//计时器归零
timer = 0;
**小扩展**摄像机跟随
[Header("要跟随的目标")]
public GameObject followTarget;
[Header("摄像机跟随的速度")]
public float moveSpeed = 0.1f;
private Vector3 dir;
private void Start()
{
//计算方向向量
dir = followTarget.transform.position - transform.position ;
}
private void Update()
{
//计算摄像机跟随移动的向量
transform.position = Vector3.Lerp(transform.position,followTarget.transform.position -dir,moveSpeed) ;
}
**小扩展2:炮弹的方向**
[HideInInspector]
//炮弹的飞行方向
public Vector3 moveDir;
[Header("炮弹飞行速度")]
public float moveSpeed = 3f;
private void Update()
{
transform.position += moveDir * moveSpeed * Time.deltaTime;
}
以下是代码展示
一丶敌方AI坦克
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemysTankControll : MonoBehaviour
{
//实现坦克的三大功能:开火,转向敌人,巡逻
[Header("炮弹预设体")]
public GameObject bulletPrefab;
[Header("敌方坦克移动速度")]
public float moveSpeed = 3f;
[Header("敌方坦克转身速度")]
public float turnSpeed = 3f;
[Header("炮弹发生点")]
private Transform firePoint;
//炮弹发射时间
private float timer = 0;
[Header("时间间隔")]
public float timeInterval = 3f;
//敌方坦克与玩家之间的向量
private Vector3 dir;
//敌方坦克与玩家之间的距离
private float distance;
//巡逻监视的时候Partol的y坐标
private float yPartol = 0;
[Header("炮弹飞行速度")]
public float bltSpeed = 3f;
private Transform playerTank;
//射线碰撞检测器
private RaycastHit hit;
//创建的方法有:Awake():负责找到炮弹发射点,以及玩家坦克的组件
/*Update():负责加上计时器,以及判断玩家和敌人的距离
* 当达到某种距离时会执行相应的方法
*
* Rotate(Quaternion quaTarget):负责执行Lerp()插值函数从而旋转
* RotateTo(Vector3 Pos) :负责将向量转化为四元数从而执行Rotate(Quaternion quaTarget)方法
* RotateToPlayer() :负责计算玩家坐标减去本坐标的向量,将向量传给RotateTo()
*
* CheckForwardFriend(float distance):负责判断前方是否是友军,用射线和标签来判断
*
* Fire():开火,负责生成炮弹并速度,销毁
* Move():负责转向玩家,并朝着玩家的方向移动,移动到某某米时就会转为Fire()
* Partol():巡逻,负责先朝着一个方向移动,并在某时间范围内随机转向
* Rotate(Quaternion.Euler(Vector3.up * yPartol));
*/
private void Awake()
{
firePoint = transform.Find("Top/Gun/FirePoint");
//找到玩家坦克的组件
playerTank = GameObject.FindWithTag("Player").transform;
}
private void Update()
{
distance = Vector3.Distance(transform.position, playerTank.position);
timer += Time.deltaTime;
if (distance < 15)
{
Fire();
}
if ( distance < 25)
{
Move();
}
else
{
Partol();
}
}
/// <summary>
/// 开火
/// </summary>
private void Fire()
{
if (!CheckForwardFriend(20))
{
//生成炮弹
if (timer > timeInterval)
{
GameObject blt = Instantiate(bulletPrefab,
firePoint.position, Quaternion.identity);
blt.GetComponent<MyBullets>().moveDir = transform.forward;
blt.GetComponent<Rigidbody>().velocity = transform.forward * bltSpeed * Time.deltaTime;
Destroy(blt, 4f);
//计时器归零
timer = 0;
}
}
}
private void Move()
{
//先转向玩家
RotatoPlayer();
//判断前方有没有友军
if (CheckForwardFriend(10))
{
transform.position +=
Vector3.forward * moveSpeed * Time.deltaTime;
}
}
private bool CheckForwardFriend(float dis)
{
//是否有物体
if (Physics.Raycast(firePoint.position, transform.forward, out hit, dis))
{
//是敌军还是友军
if (hit.collider.transform.root.tag == "Enemy")
{
//说明是友军
return true;
}
}
return false;
}
/// <summary>
/// 转向玩家
/// </summary>
private void RotatoPlayer()
{
dir = playerTank.position - transform.position;
RotateTo(dir);
}
/// <summary>
/// 巡逻
/// </summary>
private void Partol()
{
transform.position +=
transform.forward * moveSpeed * Time.deltaTime;
if (timer > timeInterval)
{
yPartol = Random.Range(0, 360);
//计时器归零
timer = 0;
}
//新生成一个角度来旋转
Rotate(Quaternion.Euler(Vector3.up * yPartol));
//Quaternion quaRotation = Quaternion.LookRotation
// (new Vector3(0, yPartol, 0));
// transform.rotation = Quaternion.Lerp(transform.rotation,
// quaRotation, turnSpeed * Time.deltaTime);
}
private void Rotate(Quaternion quaTarget)
{
//再用插值函数
transform.rotation = Quaternion.Lerp(transform.rotation,
quaTarget, turnSpeed * Time.deltaTime);
}
private void RotateTo(Vector3 pos)
{
//先将方向向量转化为四元数
Quaternion quaTarget = Quaternion.LookRotation(pos);
Rotate(quaTarget);
}
}
其次是敌方坦克管理(如生成)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyEnemyTankManager : MonoBehaviour
{
//创建敌方坦克的生成
//以及敌方坦克的基本属性:随机生成的位置,旋转的角度
//还需要一些限制:最大生成数量,定时生产,生成的时候不能和其它坦克重合
[Header("敌方坦克的预设体")]
public GameObject enemyTankPrefab;
private float counter;
[Range(30, 100)]
public int maxEnemy = 50;
private float timer = 0;
[Header("生成的时间间隔")]
public float timeInterval = 5f;
private float radius = 5f;
private void Start()
{
}
private void Update()
{
timer += Time.deltaTime;
if (timer > timeInterval)
{
if(counter<maxEnemy)
{
CreateTank();
}
timer = 0;
}
}
/// <summary>
/// 创建坦克
/// </summary>
private void CreateTank()
{
float x = 0, z = 0;
int y = 0;
Vector3 pos = Vector3.zero;
do
{
x = Random.Range(-40f, 40f);
z = Random.Range(-40f, 40f);
pos = new Vector3(x, 0, z);
y = Random.Range(0, 360);
} while (!CanCreateTank(pos));
//将欧拉角转化为四元数
Quaternion quaTarget = Quaternion.Euler(new Vector3(0, y, 0));
Instantiate(enemyTankPrefab, pos,quaTarget);
counter++;
}
/// <summary>
/// 用来表示这个位置可不可用的
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
private bool CanCreateTank(Vector3 pos)
{
//检测除了第八层以外其它碰撞体,如果有则返回false
return !Physics.CheckSphere(pos, radius, ~(1 << 8));
}
}
然后是:玩家的坦克(移动旋转发射)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyTankMove : MonoBehaviour
{
//实现坦克的旋转,移动,炮弹的发生
[Header("玩家坦克移动速度")]
public float moveSpeed = 3f;
[Header("玩家坦克旋转速度")]
public float turnSpeed = 3f;
[Header("炮弹的预设体")]
public GameObject bulletprefab;
[Header("炮弹的飞行速度")]
public float bltSpeed = 5f;
private Transform firePoint;
float hor, ver; //虚拟x,y轴
bool fire; //是否开火
private void Awake()
{
firePoint = transform.Find("Top/Gun/FirePoint");
}
private void Update()
{
//获取虚拟x,y轴
hor = Input.GetAxis("Horizontal");
ver = Input.GetAxis("Vertical");
transform.position += transform.forward
* ver * moveSpeed *Time.deltaTime;
transform.eulerAngles += transform.up * hor * turnSpeed;
fire = Input.GetButtonDown("Fire1");
if (fire)
{
//生成炮弹
GameObject blt = Instantiate(bulletprefab,
firePoint.position, Quaternion.identity);
//给炮弹一个速度
blt.GetComponent<MyBullets>().moveDir = transform.forward;
blt.GetComponent<Rigidbody>().velocity = transform.forward * bltSpeed;
//定期销毁炮弹
Destroy(blt, 4f);
}
}
}
以及炮弹方向设置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyBullets : MonoBehaviour
{
//用来控制子弹的飞行方向的.cs
public Vector3 moveDir;
public float moveSpeed =1f;
private void Update()
{
transform.position += moveDir * Time.deltaTime * moveSpeed;
}
}
控制镜头移动:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyCameraMove : MonoBehaviour
{
public Vector3 dir;
public Transform followTarget;
[Header("摄像机的移动速度")]
public float moveSpeed = 0.1f;
private void Start()
{
//方向向量
dir = followTarget.position - transform.position;
}
private void Update()
{
//摄像机的跟随移动
transform.position = Vector3.Lerp(transform.position,
followTarget.position - dir, moveSpeed);
}
}
当代码写完时,还需要将相应的脚本挂在相应的对象上
以及贴标签和设置层Layer,如下所示
设置空对象PlayerTank将坦克的所有对象放进去,空气墙四个Wall,以及空对象EnemyTankManager可以控制生成地方坦克
注意要给坦克添加子弹的预设体
给空对象添加敌方坦克的预设体
(不然999+个Error)Unity直接死机
!!注意!!
一定要给Plane对象加入Layer层Plane(第八层,要自己去设置)
不然也是999+个Error死机了
介绍下插值函数Lerp,生成函数Instantiate():
百度上的定义:在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点。 [1]
插值是离散函数逼近的重要方法,利用它可通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值。
插值:用来填充图像变换时像素之间的空隙。
作用是可以使场景的,物体的移动平滑,常用两个类的插值函数有
①Vector3.Lerp()
public static Vector3 Lerp(Vector3 a, Vector3 b, float t);
②Quaternion.Lerp()
public static Quaternion Lerp(Quaternion a, Quaternion b, float t);
最后一个参数是控制变化速度的,
对于Instantiate()
只介绍一种重载:即(GameObject,Vector3,Quaternion);
先尝试如何移动自己的坦克!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyTankMove : MonoBehaviour
{
//实现坦克的旋转,移动,炮弹的发生
[Header("玩家坦克移动速度")]
public float moveSpeed = 3f;
[Header("玩家坦克旋转速度")]
public float turnSpeed = 3f;
[Header("炮弹的预设体")]
public GameObject bulletprefab;
[Header("炮弹的飞行速度")]
public float bltSpeed = 5f;
private Transform firePoint;
float hor, ver; //虚拟x,y轴
bool fire; //是否开火
private void Awake()
{
firePoint = transform.Find("Top/Gun/FirePoint");
}
private void Update()
{
//获取虚拟x,y轴
hor = Input.GetAxis("Horizontal");
ver = Input.GetAxis("Vertical");
transform.position += transform.forward
* ver * moveSpeed *Time.deltaTime;
transform.eulerAngles += transform.up * hor * turnSpeed;
fire = Input.GetButtonDown("Fire1");
if (fire)
{
//生成炮弹
GameObject blt = Instantiate(bulletprefab,
firePoint.position, Quaternion.identity);
//给炮弹一个速度
blt.GetComponent<MyBullets>().moveDir = transform.forward;
blt.GetComponent<Rigidbody>().velocity = transform.forward * bltSpeed;
//定期销毁炮弹
Destroy(blt, 4f);
}
}
}
常见的方法补充
把方向向量转化为四元数
Quaternion targetQua = Quaternion.LookRotation(dir)
多少秒后销毁该游戏对象
Destroy(blt, 5f);
实现坦克的旋转
int y = Random.Range(0, 360);
将欧拉角转化为四元数
//Quaternion.Euler(欧拉角)
Quaternion qua =Quaternion.Euler( new Vector3(0, y, 0));
检测第八层(1 << 8)前面的~号相当于!号
//检测除了8,9层以外的其它层~(1 << 8|1 <<9)
//~(1 << 8 | 1 << 9);
//~(1 << 8)表示抛出第八层
//如果检测到其它膨胀体表示该点不能用
//如果没检测到则返回true表示能用
Physics.CheckSphere(pos, 5, ~(1 << 8))
Ending: