3D坦克大战

初学unity之3D坦克大战

1、导入资源

从unity store中下载所需资源内含坦克模型、弹药模型、地图模型…然后将资源导入unity
内涵所需坦克模型、弹药、地图

2、 创建属于自己的文件夹

主要包括三个文件夹
1、Scripts
2、Prefabs
3、Scenes

项目文件夹

3、创建场景

  1. 在Scenes文件夹新建场景3DTanksBattle
  2. 将资源中的地图拖入3DTB
  3. 将坦克和子弹模型拖入3DTB

4、制作炮弹预制件

  1. 添加Rigidbody刚体
    模拟炮弹物理作用,有质量和重力。选中炮弹模型

  2. Add Component->Physics->RigidBody
    添加刚体

  3. 添加Collider碰撞体
    Add Component->Physics->Capsule Collider(胶囊碰撞体)
    调整胶囊的轴向、高度、半径以贴合炮弹模型
    调整碰撞体

  4. 添加炮弹爆炸粒子(particles)特效
    从资源中找到爆炸特效拖入shell对象中
    爆炸粒子特效

5、取消我自己的预制件与资源的关联

1、右键Hierarchy中的预制件->Prefabs->unpack
2、成功(Hierarchy中字体由蓝变白),这样对物体的操作就不会有干扰
3、将Hierarchy中处理好的预制件拖入Project中自己创建的文件夹Prefabs中	

6、制作坦克预制件

  1. 添加RigidBody刚体

  2. 添加Box Collider碰撞体
    坦克分上下两部分,添加两个碰撞体以贴合坦克形状

  3. 添加坦克粒子爆炸特效

  4. 添加坦克血条
    1、添加UI控件:右键Tank->UI->Slider(滑动块)
    2、 Fill Area的Fill Rect改为full,360°,Fill移入Slider并删除Fill Area
    血条
    3、Background和Fill图片都设为圆环
    Background和Fill坐标全部置零使二者重合
    坐标
    调色
    血条
    Slider设为长宽4x4大小
    血条

  5. 做成预制件拖入Prefabs文件夹

7、调整光线

1、Window->Rendering->Lighting进入光源设置
2、在Environment Lighting将Source的SkyBox改为Color
3、在Scene->Lighting Settings-->new Lighting Settings
4、LightingMapping Seethings将LightingMapper设为Enlighten
5、Lighting Mode设为Subtractive
6、删除原先场景光照

8、使用脚本控制坦克行为

  1. 在Scripts文件夹新建脚本,命名为TankControl
    使用Input控制器结合RigidBody让坦克移动
 //对象声明过后要实例化才能使用
    //变量名不要使用rigidbody,这个命名在基类中存在所以会被隐藏继承,除非加new--用下划线区分开
    public  Rigidbody _rigidbody;
    // Start is called before the first frame update
    void Start()
    {
        //this指这个脚本,此脚本的游戏对象(挂载到Tank),获取此游戏对象身上的RigidBody组件
        _rigidbody =  gameObject.GetComponent<Rigidbody>();//新版本不必加this
    }
  1. Input管理器
    1、打开Input Manager
    2、edit->Project Settings->Input Manager
    3、Horizontal(水平)、Vertical(垂直)—
    水平
    垂直
  public float speed = 2;//速度
    public float rotateSpeed = 2;//转速
 void Update()
    {
        //结合Input控制器
        h_Value = Input.GetAxis("Horizontal1");//a,d--转弯
        v_Value = Input.GetAxis("Vertical1");//s,w--前进后退

        if (v_Value != 0)//为正,前进,为负,后退
        {
            _rigidbody.MovePosition(this.transform.position + v_Value*this.transform.forward * speed * Time.deltaTime);//Time.deltaTime--上一帧执行的时间/每帧的间隔时间
        }
        //转弯
        if(h_Value!=0)
        {
            if(v_Value<0)
            {
                h_Value = -h_Value;//转弯逻辑
            }
            this.gameObject.transform.Rotate(Vector3.up * h_Value * rotateSpeed * Time.deltaTime);
        }
    }
  1. 避免坦克翻车
    改变x轴和z轴
    在RigidBody约束(Constraints)轴变化
    约束
  2. 两个坦克
    枚举两个坦克
public enum TankType
{
    Tank_One=1,
    Tank_Two=2,
    Tank_Enemy=3,
}
 public TankType tankType = TankType.Tank_One;//坦克1
 public string inputHorizontalStr;
 public string inputVerticalStr;//使输入方式可变,wasd和上下左右
void Start()
    {
        //this指这个脚本,此脚本的游戏对象(挂载到Tank),获取此游戏对象身上的RigidBody组件
        _rigidbody =  gameObject.GetComponent<Rigidbody>();//新版本不必加this
        inputHorizontalStr = inputHorizontalStr + (int)tankType;
        //在unity面板中输入Horizontal和Vertical,自动根据坦克1或2来赋值Horizontal1/2和Vertical1/2
        inputVerticalStr = inputVerticalStr + (int)tankType;
    }
    void Update()
    {
        //结合Input控制器
        h_Value = Input.GetAxis(inputHorizontalStr;//a,d--转弯
        v_Value = Input.GetAxis(inputVerticalStr);//s,w--前进后退
     }

两个坦克
两个输入模式

  1. 炮弹发射
    InputManager中Fire,FIre1指向空格键
    开火
	public GameObject shell;//炮弹
    public Transform shellPos;//炮弹发射位置
     void Start()
    {
        inputFireStr = inputFireStr + (int)tankType;
    }
    void Update()
    {
        //取得键盘的空格键输入---开炮,多种写法
        //if (Input.GetKeyDown(KeyCode.Space)) { }
        if(Input.GetButtonDown(inputFireStr))
        {
            //写开炮方法
            OpenFire();
        }
    }
    

在unity为Tank添加一个游戏对象shellPos作为炮弹的发射位置
炮弹发射位置
赋值
由RigidBody给炮弹速度

void OpenFire()
    {
        //1、先克隆一个炮弹
        //2、给炮弹一个速度
        if (shell != null)
        {
            GameObject shellObj = Instantiate(shell, shellPos.position, shellPos.rotation);//克隆一个原始对象并返回这个克隆体                                                                       
            //shell克隆到shellPos的位置
            Rigidbody shellRigidbody = shellObj.GetComponent<Rigidbody>();//克隆体shell获取shell身上的RigidBody
       if(shellRigidbody!=null)
            {
                shellRigidbody.velocity = shellPos.forward * shellSpeed;//沿shellSpeed速度向前---矢量速度
            }
        }
    }
  1. 炮弹发射力度(优化)
    — —键盘按下的状态
    定义炮弹的最小速度、当前速度、最大速度
 //按下的当时
        if(Input.GetButtonDown(inputFireStr))
        {
            //写开炮方法
            //OpenFire();
            IsFire = true;//按下,发射
            currentSpeed = MinSpeed;
        }
        //按下的状态
        if(Input.GetButton(inputFireStr)&&IsFire)
        {
            currentSpeed += speedChange + Time.deltaTime;
            if(currentSpeed>=MaxSpeed)
            {
                currentSpeed = MaxSpeed;
                OpenFire(currentSpeed);
                currentSpeed = MinSpeed;
                IsFire = false;
            }
            if (Input.GetButtonUp(inputFireStr) && IsFire)
            {

                OpenFire(currentSpeed);
                currentSpeed = MinSpeed;
                IsFire = false;
            }
        }

9、脚本控制炮弹行为

  1. 给shell添加脚本ShellControl
  2. 物理碰撞:碰撞双方都需要挂载Collider组件
 private void OnCollisionEnter(Collision collision)//当此刚体接触到另一刚体,就调用
    {
        if (shellExplosion != null)
        {
            shellExplosion.Play();//播放爆炸
                                  //在unity编辑器拖拽初始化shellExplosion
        }
        Destroy(this.gameObject);//碰撞完销毁
    }

缺陷:粒子特效是炮弹的子物体,销毁炮弹同时也销毁粒子特效,所以无法表现出效果,改进

private void OnCollisionEnter(Collision collision)//当此刚体接触到另一刚体,就调用
    {
        shellExplosion.transform.parent = null;//父物体设为null
        if (shellExplosion != null)
        {
            shellExplosion.Play();//播放爆炸
            //在unity编辑器拖拽初始化shellExplosion
            Destroy(shellExplosion.gameObject, shellExplosion.main.duration);
        }
        Destroy(this.gameObject);//碰撞完销毁
        //print("shabi");
    }
  1. 坦克被击中的爆炸力
    Layer图层:将一组行为类似的对象放在一起,按照某种方式处理 常见用法: 1、在Scene中用Layer选择哪些图层可出现在Scene视图中; 2、排除不被灯光照亮的对象 ; 3、告诉unity哪些对象之间可以进行物理交互; 4、使用图层定义摄像机可以看到什么以及不能看到什么;
  private void OnCollisionEnter(Collision collision)//当此刚体接触到另一刚体,就调用
    {
 //中心点,围绕半径,获取范围内Collider图层
        Collider[] tankCollider = Physics.OverlapSphere(this.transform.position,explosionRadius,tankMask);//返回一个数组,碰撞范围
        for(int i=0;i<tankCollider.Length;i++)
        {
           var tankRigidBody= tankCollider[i].gameObject.GetComponent<Rigidbody>();
            if(tankRigidBody!=null)
            {
                tankRigidBody.AddExplosionForce(explosionForce,this.transform.position,explosionRadius);//添加爆炸力:爆炸力,爆炸中心点,爆炸半径
            }
        }
     }
  1. 伤害
    计算血量
 private void OnCollisionEnter(Collision collision)//当此刚体接触到另一刚体,就调用
    {
     float distance = (this.transform.position - tankCollider[i].gameObject.transform.position).magnitude;//三元数的模,中心坐标减去爆炸体坐标
     float currentDamage = distance / explosionRadius * MaxDanage;
    }

在坦克脚本添加坦克的PH值

  public float PH = 15;//坦克血量
  //炮弹伤害
    public void ShellDamage(float damage)
    {
        if(PH>0)
        {
            PH -= damage;//血量减伤害
        }
        if(PH<=0)
        {
            //死掉
        }
    }
    //炮弹脚本
     private void OnCollisionEnter(Collision collision)//当此刚体接触到另一刚体,就调用
   	 {
     float distance = (this.transform.position - tankRigidBody.position).magnitude;//三元数的模,中心坐标减去爆炸体坐标
            float currentDamage = distance / explosionRadius * MaxDanage;

            var tankControl=tankCollider[i].gameObject.GetComponent<TankControl>();
            if(tankControl!=null)
            {
                tankControl.ShellDamage(currentDamage);//造成伤害
            }
           }

坦克身上有两个碰撞体,爆炸会产生两端伤害
所以,去掉一个碰撞体(Box Collider)
删掉
然后在Tank新建子游戏对象作为碰撞体(Collider),并且与坦克不在同一图层(Layer)
新碰撞体
同步到预制件

  1. 血条(Slider)同步
    控制Slider(UI空间) 的value值
    UI控件
    public Slider phSlider;//实例化后就可获得value的值
    void Start()
    {
        //血量初始化
        phSlider.maxValue = PH;
        phSlider.value = PH;
    }
 public void ShellDamage(float damage)
    {
        if(PH>0)
        {
            PH -= damage;//血量减伤害
            phSlider.value = PH;//血量减少
        }
        if(PH<=0)
        {
            //死掉
            //坦克爆炸
        }
    }

坦克死亡爆炸

 public ParticleSystem tankExplosion;//实例化
 if(PH<=0)
        {
            //死掉
            //坦克爆炸
            if(tankExplosion!=null)
            {
                tankExplosion.transform.parent = null;
                tankExplosion.Play();//播放粒子特效
                Destroy(tankExplosion.gameObject, tankExplosion.main.duration);
            }
            this.gameObject.SetActive(false);//隐藏坦克,直接销毁影响后面再次使用
        }

实例化

10、坦克的产生和初始化

  1. 在不同位置加载两个坦克
    新建脚本GameManager以及相应游戏对象
    新建两个游戏对象作为坦克刷新坐标,初始化
void TankSpwan()
    {
        GameObject tankOne = Instantiate(tankPrefab, PosOne.position, PosOne.transform.rotation);//克隆坦克作为坦克一号,位置在PosOne
        var tankOneControl = tankOne.GetComponent<TankControl>();//获取TankControl的组件,为了坦克类型
        if(tankOneControl!=null)
        {
            tankOneControl.tankType = TankType.Tank_One;
        }
        GameObject tankTwo = Instantiate(tankPrefab, PosTwo.position, PosTwo.transform.rotation);
        var tankTwoControl = tankTwo.GetComponent<TankControl>();//获取TankControl的组件,为了坦克类型
        if (tankTwoControl != null)
        {
            tankTwoControl.tankType = TankType.Tank_Two;
        }
    }
  1. 设置坦克颜色
    坦克颜色材质
    材质
void TankSpwan()
    {
        print("shabi");
        GameObject tankOne = Instantiate(tankPrefab, PosOne.position, PosOne.transform.rotation);//克隆坦克作为坦克一号,位置在PosOne
        var tankOneControl = tankOne.GetComponent<TankControl>();//获取TankControl的组件,为了坦克类型
        if(tankOneControl!=null)
        {
            tankOneControl.tankType = TankType.Tank_One;
            MeshRenderer tankOneRenderers = tankOne.GetComponentInChildren<MeshRenderer>();//获取材质
            tankOneRenderers.material.color = tankOneColor;
        }
        GameObject tankTwo = Instantiate(tankPrefab, PosTwo.position, PosTwo.transform.rotation);
        var tankTwoControl = tankTwo.GetComponent<TankControl>();//获取TankControl的组件,为了坦克类型
        if (tankTwoControl != null)
        {
            tankTwoControl.tankType = TankType.Tank_Two;
            MeshRenderer tankTwoRenderers = tankTwo.GetComponentInChildren<MeshRenderer>();//获取材质
            tankTwoRenderers.material.color = tankTwoColor;
        }
    }
}

PS:未能解决获取坦克下所有子对象的材质进行颜色修改....后续在研究
PSS:当游戏运行后,会出现“ArgumentException: Input Axis Horizontal12 is not setup. To change the input settings use: Edit -> Settings -> Input”的报错,因为坦克类型在声明时被初始化为坦克1,后面调用是在Horizontal1/Vertical1/Fire1的条件下再进行坦克类型判断,去掉初始化即可。

11、相机位置跟随

  1. 主摄像机改为正交模式,使视野变为平行
    摄像机模式
  2. 为主摄像机创建脚本
    让相机跟随两个坦克的中心点移动
    中心点
   public GameObject[] tanks;//两个坦克的数组
    public Vector3 targetCameraPos = Vector3.zero;
    public Camera mainCamera;
    public Vector3 currentVelocity = Vector3.zero;//初始化
    public float smoothTime = 0.1f;//平滑时间
    public float maxSmoothSpeed = 2;
    // Start is called before the first frame update
    void Start()
    {
        mainCamera = Camera.main;//初始化
    }
     void Update()
    {
        ResetCameraPos();
    }
    void ResetCameraPos()
    {
        //1、计算相机移动的位置
        //2、平滑移动相机
        Vector3 sumPos=Vector3.zero;//初始化
        foreach(var tank in tanks)//tanks中的每一辆坦克
        {
            sumPos += tank.transform.position;//坦克的位置全部相加
        }
        if(tanks.Length>0)
        {
            targetCameraPos= sumPos / tanks.Length;//算出相机移动目标位置
            targetCameraPos.y = mainCamera.transform.position.y;//避免y坐标偏差
            mainCamera.transform.position = Vector3.SmoothDamp(mainCamera.transform.position, targetCameraPos, ref currentVelocity, smoothTime, maxSmoothSpeed);
            //当前位置,目标位置,速度
        }
    }
}

初始化坦克数组,在初始化坦克处赋值
Tank设置Tag为Player
在这里插入图片描述

 public RyumnCameraControl cameraControl;//使运行后才出现的Tank(Clone)赋值给坦克数组
    // Start is called before the first frame update
    void Start()
    {
        TankSpwan();//初始化
        if(cameraControl!=null)
        {
            cameraControl.tanks = GameObject.FindGameObjectsWithTag("Player");//查找复制后坦克的tag
        }
        
    }

先初始化生成坦克,再为坦克赋值

  1. 相机尺寸修改
   void ResetCameraSize()
    {
        //通过计算相机与坦克中心的位置的x和z差值,来计算size值
        //勾股
        float size = 0;
        foreach(var tank in tanks)
        {
            Vector3 offSetPos = tank.transform.position - targetCameraPos;
            //坦克位置减去两坦克中心位置
            float z_Value = Mathf.Abs(offSetPos.z);//绝对值
            size = Mathf.Max(size, z_Value);//z方向取最大值
            float x_Value = Mathf.Abs(offSetPos.x);
            size = Mathf.Max(size, x_Value/mainCamera.aspect);

        }
        mainCamera.orthographicSize = size;//正交模式下相机的size
    }

yunxing
运行发现两个坦克一直处于画面边缘
修改 size += sizeOffset;

  1. 优化相机位置
    给主相机创建父物体,通过控制父物体来控制主相机
    将代码中的mainCamera对象修改为父物体

    12、游戏音效

    1. 新建游戏对象AudioGroup,添加子对象并添加Audio Source组件用来挂载音乐
      音频
      对每个游戏对象挂载相应音频,创建脚本声明相应变量并初始化
 	public AudioSource bgm;
    public AudioSource tankExplosion;
    public AudioSource shellExplosion;
    public AudioSource tankFire;

在这里插入图片描述

  1. 游戏开始时播放bgm
    在坦克初始化脚本里
void Update()
    {
        audioManager = GameObject.FindWithTag("AudioManager").GetComponent<RyumnAudioManager>();
       //为音频管理添加Tag,使用Tag查找
        if(audioManager!=null)
        {
            audioManager.bgmAudioPlay();
        }
    }
  1. 坦克发射炮弹音效
    //爆炸音效
    public AudioSource shellFireAudio;
    public AudioSource shellExplosionAudio;
        void Start()
    {
        //坦克开火音效
        if (shellFireAudio != null)
        {
            if (!shellFireAudio.isPlaying)
            {
                shellFireAudio.Play();
            }
        }
    }
            //炮弹爆炸音效

        if (shellExplosionAudio != null)
        {
            shellExplosionAudio.gameObject.transform.parent = null;//父物体设为null
            if (!shellExplosionAudio.isPlaying)
            {
                shellExplosionAudio.Play();
                Destroy(shellExplosionAudio.gameObject, 1);
            }
        }

炮弹爆炸音效会跟随炮弹被销毁,因此听不见
将炮弹爆炸音效做粒子特效同样处理

13、给屏幕添加光晕效果

  1. 主相机添加组件:Post-process Layer,Layer设为Postprocessing
    新组件
  2. 新建空游戏对象,添加Post-Processing Volume组件

14、重新加载

 public void ShellDamage(float damage)
    {
       
        if(PH<=0)
        {
            
            Invoke("ReloadTankBattleScene", 2);
        }
    }
    void ReloadTankBattleScene()
    {
        SceneManager.LoadScene("3DTanksBattle");
    }

15、游戏发布

  1. File->Build Setting
    在这里插入图片描述
    在这里插入图片描述
    一系列设置
这里是一个简单的Unity3D坦克大战游戏的C#代码示例: ```csharp using UnityEngine; public class TankController : MonoBehaviour { public float moveSpeed = 5f; // 坦克移动速度 public float rotateSpeed = 20f; // 坦克转向速度 public GameObject bulletPrefab; // 子弹预制体 public Transform firePoint; // 开火点 public float bulletSpeed = 10f; // 子弹速度 public float bulletInterval = 0.5f; // 发射子弹的时间间隔 private float lastBulletTime = 0f; // 上次发射子弹的时间 private Rigidbody tankRigidbody; // 坦克刚体 void Start() { tankRigidbody = GetComponent<Rigidbody>(); } void Update() { // 获取玩家输入 float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); // 移动坦克 Vector3 moveDir = new Vector3(h, 0, v).normalized; tankRigidbody.MovePosition(transform.position + moveDir * moveSpeed * Time.deltaTime); // 转向坦克 Vector3 rotateDir = new Vector3(0, h, 0); Quaternion deltaRotation = Quaternion.Euler(rotateDir * rotateSpeed * Time.deltaTime); tankRigidbody.MoveRotation(tankRigidbody.rotation * deltaRotation); // 发射子弹 if (Input.GetKeyDown(KeyCode.Space) && Time.time - lastBulletTime > bulletInterval) { Fire(); lastBulletTime = Time.time; } } void Fire() { GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); Rigidbody bulletRigidbody = bullet.GetComponent<Rigidbody>(); bulletRigidbody.velocity = firePoint.forward * bulletSpeed; } } ``` 这个脚本可以挂载在坦克上,控制坦克的移动、转向和发射子弹。需要注意的是,这个代码示例只是一个基础的框架,还需要根据实际需求进行修改和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值