视频教程:
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
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. 两个玩家控制
进入输入设置

- 复制 Horizontal 轴
- 修改 Horizontal 控制按键
- 复制 Vertical 轴
- 修改 Vertical 控制按键



- 修改代码,实现通过编号区分不同的控制
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. 背景音乐
