Unity教学 项目3 3D坦克大战

视频教程:

https://www.bilibili.com/video/BV11D5QzgEpw?spm_id_from=333.788.videopod.sections&vd_source=25b783f5f945c4507229e9dec657b5bb

1. 场景搭建

  1. 创建工程文件
  2. 素材导入
  3. 将游戏场景预制体实例化
  4. 设置场景光颜色为(29, 26, 00)
  1. 设置天空颜色为(128, 110, 36)
  1. 设置 camera 位置为(-31, 25, -20)
  2. 设置 camera 旋转角度为(37, 53, 6)
  3. 设置 camera 为正交视野,Size 为 8

正交投影:常用于 2D 游戏开发、UI 设计、建筑图纸绘制等,这些场景更关注物体实际尺寸和相对位置,不需要模拟真实 3D 空间深度感。比如 2D 横版过关游戏,正交投影能保证角色和场景元素大小一致,便于玩家把握距离和位置 。

透视投影:广泛用于 3D 游戏、虚拟现实(VR)和增强现实(AR)等场景,能营造逼真空间感和深度感,让玩家有身临其境的体验。如第一人称射击游戏,通过透视投影呈现真实远近效果,增强沉浸感。

2. 移动旋转

  1. 创建坦克实例

  2. 将烟拖动到坦克上,设置位置为(0.6, 0, -0.94)和(-0.5, 0, -0.94)

  3. 坦克添加刚体组件

  4. 坦克添加碰撞盒子,设置位置为(0, 0.95, 0),大小为(1.51, 1.71, 1.62)

    1. 注意:碰撞盒子不能紧挨地面,容易检测坦克与地面发生碰撞导致坦克无法移动。
  5. 将坦克做成预制体

  6. 创建脚本文件 Tank.cs

  7. 添加脚本,实现坦克的前后移动功能

  8. 约束刚体部分轴,位置冻结 Y 轴,旋转冻结 X 和 Z 轴

  1. 添加脚本,实现坦克旋转功能

2.1. Tank.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tank : MonoBehaviour
{
    public float moveSpeed;
    public float angularSpeed;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移动
    /// </summary>
    void Move()
    {
        float v = Input.GetAxis("Vertical");
        float h = Input.GetAxis("Horizontal");
        rb.velocity = transform.forward * v * moveSpeed;
        rb.angularVelocity = transform.up * h * angularSpeed;
    } 
}

3. 两个玩家控制

进入输入设置

  1. 复制 Horizontal 轴
  2. 修改 Horizontal 控制按键
  3. 复制 Vertical 轴
  4. 修改 Vertical 控制按键
  1. 修改代码,实现通过编号区分不同的控制

3.1. Tank.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tank : MonoBehaviour
{
    public float moveSpeed;
    public float angularSpeed;
    public int playerNum;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移动
    /// </summary>
    void Move()
    {
        float v = Input.GetAxis("VerticalP" + playerNum);
        float h = Input.GetAxis("HorizontalP" + playerNum);
        rb.velocity = transform.forward * v * moveSpeed;
        rb.angularVelocity = transform.up * h * angularSpeed;
    } 
}

4. 发射炮弹

子弹、爆炸预设体,发射位置,脚本

子弹预设体

爆炸预设体

4.1. PreShell.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PreShell : MonoBehaviour
{
    public float speed = 2f;
    public GameObject preShellExplosionGo;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.velocity = transform.forward * speed;
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnCollisionEnter(Collision collision)
    {
        GameObject shellExplosionGo = Instantiate(preShellExplosionGo, transform.position, transform.rotation);
        Destroy(gameObject);
        Destroy(shellExplosionGo.gameObject, 1.5f);
    }
}

4.2. Tank.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tank : MonoBehaviour
{
    public float moveSpeed;
    public float angularSpeed;
    public int playerNum;
    public KeyCode attackKey;
    public Transform shootPointTr;
    public GameObject preShellGo;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        attackKey = KeyCode.Space;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(attackKey))
        {
            Shoot();
        }
    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移动
    /// </summary>
    void Move()
    {
        float v = Input.GetAxis("VerticalP" + playerNum);
        float h = Input.GetAxis("HorizontalP" + playerNum);
        rb.velocity = transform.forward * v * moveSpeed;
        rb.angularVelocity = transform.up * h * angularSpeed;
    } 

    /// <summary>
    /// 攻击
    /// </summary>
    void Shoot()
    {
        GameObject shellGo = Instantiate(preShellGo, shootPointTr.position, shootPointTr.rotation);
        //shellGo.GetComponent<Rigidbody>().velocity = shellGo.transform.forward * 5;
    }
}

5. 产生伤害

5.1. PreShell.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PreShell : MonoBehaviour
{
    public float speed = 2f;
    public GameObject preShellExplosionGo;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.velocity = transform.forward * speed;
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnCollisionEnter(Collision collision)
    {
        GameObject shellExplosionGo = Instantiate(preShellExplosionGo, transform.position, transform.rotation);
        Destroy(gameObject);
        Destroy(shellExplosionGo.gameObject, 1.5f);

        //造成伤害
        Tank tank = collision.gameObject.GetComponent<Tank>();
        if (tank == null) return;
        tank.currentBlood -= 10;
        //死亡
        if(tank.currentBlood < 0)
        {
            tank.currentBlood = 0;
            Destroy(collision.gameObject);
        }
    }
}

5.2. Tank.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tank : MonoBehaviour
{
    public float moveSpeed;
    public float angularSpeed;
    public int playerNum;
    public int currentBlood;
    public int maxBlood = 100;
    public KeyCode attackKey;
    public Transform shootPointTr;
    public GameObject preShellGo;
    private Rigidbody rb;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        attackKey = KeyCode.Space;
        currentBlood = maxBlood;
    }

    // Update is called once per frame
    void Update()
    {
        if (playerNum == 1 && Input.GetKeyDown(attackKey))
        {
            Shoot();
        }
        else if (playerNum == 2 && Input.GetKeyDown(KeyCode.Keypad9))
        {
            Shoot();
        }
    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移动
    /// </summary>
    void Move()
    {
        float v = Input.GetAxis("VerticalP" + playerNum);
        float h = Input.GetAxis("HorizontalP" + playerNum);
        rb.velocity = transform.forward * v * moveSpeed;
        rb.angularVelocity = transform.up * h * angularSpeed;
    } 

    /// <summary>
    /// 攻击
    /// </summary>
    void Shoot()
    {
        GameObject shellGo = Instantiate(preShellGo, shootPointTr.position, shootPointTr.rotation);
        //shellGo.GetComponent<Rigidbody>().velocity = shellGo.transform.forward * 5;
    }
}

6. 摄像机跟随坦克

6.1. MainCamera.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainCamera : MonoBehaviour
{
    public Transform tank1; // 第一辆坦克的Transform
    public Transform tank2; // 第二辆坦克的Transform
    private Vector3 offset; // 摄像机相对于两辆坦克中心的偏移量
    private Camera mainCamera; // 主摄像机组件
    private float distance; // 两辆坦克之间的距离
    private float cameraSize; // 正交摄像机的尺寸

    // Start is called before the first frame update
    void Start()
    {
        // 检查是否正确赋值了两个坦克的Transform
        if (tank1 == null || tank2 == null) return;

        // 计算摄像机的初始偏移量
        Vector3 tanksCenter = (tank1.position + tank2.position) / 2;
        offset = transform.position - tanksCenter;

        // 获取主摄像机组件
        mainCamera = GetComponent<Camera>();
    }

    // Update is called once per frame
    void Update()
    {
        // 如果任意一个坦克不存在,则直接返回
        if (tank1 == null || tank2 == null) return;

        // 更新摄像机的位置
        Vector3 tanksCenter = (tank1.position + tank2.position) / 2;
        transform.position = tanksCenter + offset;

        // 计算两辆坦克之间的距离
        distance = Vector3.Distance(tank1.position, tank2.position);

        // 根据距离调整摄像机的正交尺寸
        cameraSize = distance * 0.875f;
        mainCamera.orthographicSize = cameraSize;
    }
}

7. 添加血条

7.1. 设置血条物体

新增血条物体

设置 Canvas

设置 Slider

设置 Background

设置 Fill

7.2. 编写代码

7.2.1. PreShell.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PreShell : MonoBehaviour
{
    public float speed = 5f;//速度
    public int damage = 10;//伤害值
    public GameObject preShellExplosionGo;//爆炸预设体对象
    private Rigidbody rb;//刚体

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.velocity = transform.forward * speed;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    /// <summary>
    /// 碰撞则产生爆炸并自我销毁
    /// </summary>
    /// <param name="collision"></param>
    private void OnCollisionEnter(Collision collision)
    {
        GameObject shellExplosionGo = Instantiate(preShellExplosionGo, transform.position, transform.rotation);//生成爆炸效果
        Destroy(gameObject);//销毁炮弹
        Destroy(shellExplosionGo, 1.5f);//销毁爆炸效果

        Tank tank = collision.gameObject.GetComponent<Tank>();
        if (tank != null) {
            tank.currentBlood -= damage;//造成了伤害
            tank.hpSlider.value = (float)tank.currentBlood / tank.maxBlood;//更新血量滑动条
            if (tank.currentBlood <= 0) Destroy(collision.gameObject);//销毁坦克
        }
    }
}

7.2.2. Tank.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Tank : MonoBehaviour
{
    public float moveSpeed = 5f;//移动速度
    public float angularSpeed = 2f;//旋转角度
    public int playerNum = 1;//玩家编号
    public int currentBlood;//当前血量
    public int maxBlood = 100;//最大血量
    public GameObject preShellGo;//炮弹预设游戏对象
    public Slider hpSlider;//血量滑动条
    private Transform shootPoint;//发射位置
    private Rigidbody rb;//刚体

    // Start is called before the first frame update
    void Start()
    {
        currentBlood = maxBlood;
        rb = GetComponent<Rigidbody>();
        shootPoint = transform.Find("ShootPoint");
    }

    // Update is called once per frame
    void Update()
    {
        //如果按下空格键则发射炮弹
        if (playerNum == 1 && Input.GetKeyDown(KeyCode.Space))
        {
            Shoot();
        }
        else if (playerNum == 2 && Input.GetKeyDown(KeyCode.P))
        {
            Shoot();
        }
    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移动
    /// </summary>
    private void Move()
    {
        float v = Input.GetAxis("VerticalP" + playerNum);
        float h = Input.GetAxis("HorizontalP" + playerNum);
        rb.velocity = transform.forward * v * moveSpeed;//前后移动
        rb.angularVelocity = transform.up * h * angularSpeed;//左右旋转
    }

    /// <summary>
    /// 发射炮弹
    /// </summary>
    private void Shoot()
    {
        Instantiate(preShellGo, shootPoint.position, shootPoint.rotation);
    }
}

7.2.3. MainCamera.cs

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainCamera : MonoBehaviour
{
    public Transform tank1; // 第一辆坦克的Transform
    public Transform tank2; // 第二辆坦克的Transform
    private Vector3 offset; // 摄像机相对于两辆坦克中心的偏移量
    private Camera mainCamera; // 主摄像机组件
    private float distance; // 两辆坦克之间的距离
    private float cameraSize; // 正交摄像机的尺寸

    // Start is called before the first frame update
    void Start()
    {
        // 检查是否正确赋值了两个坦克的Transform
        if (tank1 == null || tank2 == null) return;

        // 计算摄像机的初始偏移量
        Vector3 tanksCenter = (tank1.position + tank2.position) / 2;
        offset = transform.position - tanksCenter;

        // 获取主摄像机组件
        mainCamera = GetComponent<Camera>();
    }

    // Update is called once per frame
    void Update()
    {
        // 如果任意一个坦克不存在,则直接返回
        if (tank1 == null || tank2 == null) return;

        // 更新摄像机的位置
        Vector3 tanksCenter = (tank1.position + tank2.position) / 2;
        transform.position = tanksCenter + offset;

        // 计算两辆坦克之间的距离
        distance = Vector3.Distance(tank1.position, tank2.position);

        // 根据距离调整摄像机的正交尺寸
        cameraSize = distance * 0.875f;
        mainCamera.orthographicSize = cameraSize;
    }
}

7.3. 少量细节

8. 音效

8.1. 坦克静止、行驶音效

调正摄像机位置靠近坦克,使摄像机录制到坦克音效

8.2. 坦克销毁音效

8.3. 炮弹发射音效

8.4. 炮弹爆炸音效

8.5. 背景音乐

相关推荐
苏州邦恩精密4 小时前
蔡司3D扫描仪怎么选?从企业检测需求看供应商实力
人工智能·机器学习·3d·自动化·制造
3D小将12 小时前
3D格式转换之GLB转GLB(模型优化与标准化重打包)
3d·solidworks模型·ug模型·sketchup模型·igs模型
真鬼12317 小时前
【Unity WebGL】内嵌网页与双向通信
unity·游戏引擎·webgl
某林21218 小时前
从硬件解耦、3D公差设计到ROS 2柔性导航
人工智能·3d·机器人·ros2·技术复盘
zyh______1 天前
C#语法糖(按照实用性排序)
unity·c#
avi91111 天前
[AI教做人]CinemachineCamera 各项调整配置参数入门 + Profiler
unity·游戏开发·camera·cinemachine·visual camera·镜头配置
逸模1 天前
逸模智造・BIM 效能专栏一|极速土建翻模,2D 一键跨越 3D
笔记·3d·bim·土建·翻模
陈天伟教授2 天前
eDrawings 2026 x64 Edition-免费 3D 模型和 2D 工程图查看器
3d
郝学胜-神的一滴2 天前
[简化版 GAMES 101] 计算机图形学 16:纹理走样、Mipmap、三线性插值、各向异性过滤原理全解
unity·游戏引擎·godot·图形渲染·three.js·opengl·unreal
2301_767113983 天前
Superpowers 游戏引擎从零开发实战指南
游戏引擎