【学Unity】尝试自制一个坦克大战


项目场景:

   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:

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>