项目场景:
? ?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:
?
|