如何制作Unity项目3中的3D坦克大战游戏?

摘要:视频教程: https:www.bilibili.comvideoBV11D5QzgEpw?spm_id_from=333.788.videopod.sections&vd_source=25b783f5f9
视频教程: https://www.bilibili.com/video/BV11D5QzgEpw?spm_id_from=333.788.videopod.sections&vd_source=25b783f5f945c4507229e9dec657b5bb 1. 场景搭建 创建工程文件 素材导入 将游戏场景预制体实例化 设置场景光颜色为(29, 26, 00) 设置天空颜色为(128, 110, 36) 设置 camera 位置为(-31, 25, -20) 设置 camera 旋转角度为(37, 53, 6) 设置 camera 为正交视野,Size 为 8 正交投影:常用于 2D 游戏开发、UI 设计、建筑图纸绘制等,这些场景更关注物体实际尺寸和相对位置,不需要模拟真实 3D 空间深度感。比如 2D 横版过关游戏,正交投影能保证角色和场景元素大小一致,便于玩家把握距离和位置 。 透视投影:广泛用于 3D 游戏、虚拟现实(VR)和增强现实(AR)等场景,能营造逼真空间感和深度感,让玩家有身临其境的体验。如第一人称射击游戏,通过透视投影呈现真实远近效果,增强沉浸感。 2. 移动旋转 创建坦克实例 将烟拖动到坦克上,设置位置为(0.6, 0, -0.94)和(-0.5, 0, -0.94) 坦克添加刚体组件 坦克添加碰撞盒子,设置位置为(0, 0.95, 0),大小为(1.51, 1.71, 1.62) 注意:碰撞盒子不能紧挨地面,容易检测坦克与地面发生碰撞导致坦克无法移动。 将坦克做成预制体 创建脚本文件 Tank.cs 添加脚本,实现坦克的前后移动功能 约束刚体部分轴,位置冻结 Y 轴,旋转冻结 X 和 Z 轴 添加脚本,实现坦克旋转功能 2.1. Tank.cs 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. 两个玩家控制 进入输入设置 复制 Horizontal 轴 修改 Horizontal 控制按键 复制 Vertical 轴 修改 Vertical 控制按键 修改代码,实现通过编号区分不同的控制 3.1. Tank.cs 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 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 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 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 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 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 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 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 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. 背景音乐