首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Unity教学 项目3 3D坦克大战

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

作者头像
90后小陈老师
发布2025-12-18 20:54:55
发布2025-12-18 20:54:55
2180
举报
文章被收录于专栏:杂谈杂谈

视频教程:

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

场景搭建

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

正交投影:常用于 2D 游戏开发、UI 设计、建筑图纸绘制等,这些场景更关注物体实际尺寸和相对位置,不需要模拟真实 3D 空间深度感。比如 2D 横版过关游戏,正交投影能保证角色和场景元素大小一致,便于玩家把握距离和位置 。 透视投影:广泛用于 3D 游戏、虚拟现实(VR)和增强现实(AR)等场景,能营造逼真空间感和深度感,让玩家有身临其境的体验。如第一人称射击游戏,通过透视投影呈现真实远近效果,增强沉浸感。

移动旋转

  1. 1. 创建坦克实例
  2. 2. 将烟拖动到坦克上,设置位置为(0.6, 0, -0.94)和(-0.5, 0, -0.94)
  3. 3. 坦克添加刚体组件
  4. 4. 坦克添加碰撞盒子,设置位置为(0, 0.95, 0),大小为(1.51, 1.71, 1.62)
    1. 1. 注意:碰撞盒子不能紧挨地面,容易检测坦克与地面发生碰撞导致坦克无法移动。
  5. 5. 将坦克做成预制体
  6. 6. 创建脚本文件Tank.cs
  7. 7. 添加脚本,实现坦克的前后移动功能
  8. 8. 约束刚体部分轴,位置冻结 Y 轴,旋转冻结 X 和 Z 轴
  1. 9. 添加脚本,实现坦克旋转功能

Tank.cs

代码语言:javascript
复制
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;
    } 
}

两个玩家控制

进入输入设置

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

Tank.cs

代码语言:javascript
复制
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;
    } 
}

发射炮弹

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

子弹预设体

爆炸预设体

PreShell.cs

代码语言:javascript
复制
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.cs

代码语言:javascript
复制
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;
    }
}

产生伤害

PreShell.cs

代码语言:javascript
复制
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);
        }
    }
}

Tank.cs

代码语言:javascript
复制
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;
    }
}

摄像机跟随坦克

MainCamera.cs

代码语言:javascript
复制
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;
    }
}

添加血条

设置血条物体

新增血条物体

设置Canvas

设置Slider

设置Background

设置Fill

编写代码

PreShell.cs

代码语言:javascript
复制
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);//销毁坦克
        }
    }
}

Tank.cs

代码语言:javascript
复制
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);
    }
}

MainCamera.cs

代码语言:javascript
复制
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;
    }
}

少量细节

音效

坦克静止、行驶音效

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

坦克销毁音效

炮弹发射音效

炮弹爆炸音效

背景音乐

往期推荐:

【文章合集】Unity游戏引擎开发

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 90后小陈老师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景搭建
  • 移动旋转
    • Tank.cs
  • 两个玩家控制
    • Tank.cs
  • 发射炮弹
    • PreShell.cs
    • Tank.cs
  • 产生伤害
    • PreShell.cs
    • Tank.cs
  • 摄像机跟随坦克
    • MainCamera.cs
  • 添加血条
    • 设置血条物体
    • 编写代码
      • PreShell.cs
      • Tank.cs
      • MainCamera.cs
    • 少量细节
  • 音效
    • 坦克静止、行驶音效
    • 坦克销毁音效
    • 炮弹发射音效
    • 炮弹爆炸音效
    • 背景音乐
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档