一、效果图演示
二、逻辑剖析
从界面上:
- 需要一个Canvas满屏对着用户,该Canvas上展示用户的游戏数据,比如血条。
- 需要一个Canvas放在蓝色坦克上方,也需要实时对着用户,显示敌人的血条信息
- 两个坦克
- 一个平面Plane放草地的纹理
从逻辑上:
- 前后箭头键控制玩家前进或后退
- 左右箭头键控制玩家左右转向
- 鼠标左键或空格键控制玩家发射炮弹
- 玩家血条希纳是在屏幕左上角
- 相机在玩家后上方的位置,始终跟随玩家,朝玩家正前方看
- 玩家移动时,敌人转向玩家,当偏离玩家的角度小于5度时,发射炮弹
- 敌人血条显示在其上方,并且始终看向相机
三、界面组件信息
(1)游戏对象层级结构
(2)组件参数信息
1.玩家Player组件参数
Name | Type | Position | Rotation | Scale | Color |
Player | Empty | (0, 0.25, -5) | (0, 0, 0) | (1, 1, 1) | #228439 |
Button | Cube | (0, 0, 0) | (0, 0, 0) | (2, 0.5, 2) | #228439 |
Top | Cube | (0, 0.5, 0) | (0, 0, 0) | (1, 0.5, 1) | #228439 |
Gun | Cylinder | (0, 0, 1.5) | (90, 0, 0) | (0.2, 1, 0.4) | #228439 |
FirePoint | Empty | (0, 1.15, 0) | (0, 0, 0) | (1, 1, 1) | -- |
Player 游戏对象添加了刚体组件,并修改 Mass = 100,Drag = 1,AngularDrag = 0.1,Freeze Rotation 中勾选 X 和 Z。
2.玩家HP组件参数
Name | Type | Position | Width/Height | Color |
PlayerHP | Canvas | (960, 540, 0) | 1920/1080 | -- |
Panel | Panel | 位置信息全是0 | #FFFFFF | |
HealthBG | Image | (-809,464,0) | 200/20 | #FFFFFF |
Health | Image | (-809,464,0) | 200/20 | #FF2230 |
玩家 PlayerHP 的 Canvas 渲染模式是 Screen Space - Overlay。
制作一个红色的图片放入Health的Source Image中,Health 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal。
3.敌人组件参数
Name | Type | Position | Rotation | Scale | Color |
Enemy | Empty | (0, 0.25, 5) | (0, 180, 0) | (1, 1, 1) | #15D3F9 |
Button | Cube | (0, 0, 0) | (0, 0, 0) | (2, 0.5, 2) | #15D3F9 |
Top | Cube | (0, 0.5, 0) | (0, 0, 0) | (1, 0.5, 1) | #15D3F9 |
Gun | Cylinder | (0, 0, 1.5) | (90, 0, 0) | (0.2, 1, 0.4) | #15D3F9 |
FirePoint | Empty | (0, 1.15, 0) | (0, 0, 0) | (1, 1, 1) | -- |
Enemy 游戏对象添加了刚体组件,并修改 Mass = 100,Drag = 0.5,AngularDrag = 0.1,Freeze Rotation 中勾选 X 和 Z。
4.敌人HP组件参数
Name | Type | Position | Width/Height | Color |
HP | Canvas | (0, 0.85, 0) | 2/0.2 | -- |
HealthBG | Image | (0,0,0) | 2/0.2 | #FFFFFF |
Health | Image | (0,0,0) | 2/0.2 | #FF2230 |
敌人 HP 的 Canvas 渲染模式是 World Space,将刚才的红色底图也放入Health的Source Image中,Health 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal。
5.地面和炮弹的组件参数
Name | Type | Position | Rotation | Scale | Color |
Plane | Plane | (0, 0, 0) | (0, 0, 0) | (10, 10, 10) | GrassRockyAlbedo |
Bullet | Sphere | (0, 0.5, -5) | (0, 0, 0) | (0.3, 0.3, 0.3) | #228439 |
炮弹作为预设体拖拽到 Assets/Resources/Prefabs 目录下,并且添加了刚体组件。
四、脚本代码:
1.CameraController
CameraController 脚本组件挂在 MainCamera 游戏对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
// Start is called before the first frame update
private Transform player; // 玩家
private Vector3 relaPlayerPos; // 相机在玩家坐标系中的位置
private float targetDistance = 15f; // 相机看向玩家前方的位置
void Start()
{
relaPlayerPos = new Vector3(0, 4, -8);
player = GameObject.Find("Player/Top").transform; // 世界坐标系位置
}
private void LateUpdate()
{
ComCameraPos();
}
// 计算相机坐标
private void ComCameraPos()
{
Vector3 target = player.position + player.forward * targetDistance;
transform.position = transformVecter(relaPlayerPos, player.position, player.right, player.up, player.forward);
transform.rotation = Quaternion.LookRotation(target - transform.position);
}
// 以origin为原点,已知vec在坐标轴locX/locY/locZ中的位置,将vec转为世界坐标系的位置
private Vector3 transformVecter(Vector3 vec, Vector3 origin, Vector3 locX, Vector3 locY, Vector3 locZ)
{
return vec.x * locX + vec.y * locY + vec.z * locZ + origin;
}
}
2.BulletInfo
using UnityEngine;
public class BulletInfo
{
public string name; // 炮弹名
public Color color; // 炮弹颜色
public Vector3 flyDir; // 炮弹飞出方向
public float speed; // 炮弹飞行速度
public float fireRange; // 炮弹射程
public BulletInfo(string name, Color color, Vector3 flyDir, float speed, float fireRange)
{
this.name = name;
this.color = color;
this.flyDir = flyDir;
this.speed = speed;
this.fireRange = fireRange;
}
}
3.BulletController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BulletController : MonoBehaviour
{
private BulletInfo bulletInfo; // 炮弹信息
private volatile bool isDying = false;
// Start is called before the first frame update
void Start()
{
gameObject.name = bulletInfo.name;
GetComponent<MeshRenderer>().material.color = bulletInfo.color;
float lifeTime = bulletInfo.fireRange / bulletInfo.speed; // 存活时间
Destroy(gameObject, lifeTime);
}
// Update is called once per frame
void Update()
{
transform.GetComponent<Rigidbody>().velocity = bulletInfo.flyDir * bulletInfo.speed;
}
public void SetBulletInfo(BulletInfo bulletInfo)
{
this.bulletInfo = bulletInfo;
}
private void OnCollisionEnter(Collision other)
{
if (isDying)
{
return;
}
if(IsHitEnemy(gameObject.name, other.gameObject.name))
{
other.transform.Find("HP/Health").GetComponent<Image>().fillAmount -= 0.1f;
isDying = true;
Destroy(gameObject, 0.1f);
}else if(IsHitPlayer(gameObject.name, other.gameObject.name))
{
GameObject.Find("PlayerHP/Panel/Health").GetComponent<Image>().fillAmount -= 0.1f;
isDying = true;
Destroy(gameObject, 0.1f);
}
}
private bool IsHitEnemy(string name, string otherName)
{
return name.Equals("PlayerBullet") && otherName.Equals("Enemy");
}
private bool IsHitPlayer(string name, string otherName)
{
return name.Equals("EnemyBullet") && otherName.Equals("Player");
}
}
4.PlayerController
PlayerController 脚本组件挂在 Player 游戏对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Transform firePoint; // 开火点
private GameObject bulletPrefab; // 炮弹预设体
private float tankMoveSpeed = 4f; // 坦克移动速度
private float tankRotateSpeed = 2f; // 坦克转向速度
private float fireWaitTime = float.MaxValue; // 距离上次开火已等待的时间
private float bulletCoolTime = 0.15f; // 炮弹冷却时间
void Start()
{
firePoint = transform.Find("Top/Gun/FirePoint");
bulletPrefab = (GameObject)Resources.Load("Prefabs/Bullet");
}
// Update is called once per frame
void Update()
{
fireWaitTime += Time.deltaTime;
float hor = Input.GetAxis("Horizontal");
float ver = Input.GetAxis("Vertical");
Move(hor, ver);
if(Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Space))
{
Fire();
}
}
// 坦克移动
private void Move(float hor, float ver)
{
if (Mathf.Abs(hor) > 0.1f || Mathf.Abs(ver) > 0.1f) {
GetComponent<Rigidbody>().velocity = transform.forward * tankMoveSpeed * ver;
GetComponent<Rigidbody>().angularVelocity = Vector3.up * tankRotateSpeed * hor;
}
}
// 开炮
private void Fire()
{
if (fireWaitTime > bulletCoolTime) {
BulletInfo bulletInfo = new BulletInfo("PlayerBullet", Color.red, transform.forward, 10f, 15f);
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity);
bullet.AddComponent<BulletController>().SetBulletInfo(bulletInfo);
fireWaitTime = 0f;
}
}
}
5.EnemyController
EnemyController 脚本组件挂在 Enemy 游戏对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyController : MonoBehaviour
{
private Transform target; // 目标
private Transform top; // 炮头
private Transform firePoint; // 开火点
private Transform hp; // 血条
private GameObject bulletPrefab; // 炮弹预设体
private float rotateSpeed = 0.4f; // 坦克转向速度
private float fireWaitTime = float.MaxValue; // 距离上次开火已等待时间
private float bulletCoolTime = 1f; // 炮弹冷却时间
// Start is called before the first frame update
void Start()
{
target = GameObject.Find("Player/Top").transform;
top = transform.Find("Top");
firePoint = transform.Find("Top/Gun/FirePoint");
hp = transform.Find("HP");
bulletPrefab = (GameObject)Resources.Load("Prefabs/Bullet");
}
// Update is called once per frame
void Update()
{
fireWaitTime += Time.deltaTime;
if (LookAtTarget()) {
Fire();
}
HPLookAtCamera();
}
private bool LookAtTarget()
{
Vector3 dir = target.position - top.position;
float angle = Vector3.Angle(dir, top.forward);
if(angle > 5)
{
int axis = Vector3.Dot(Vector3.Cross(dir, top.forward), Vector3.up) > 0 ? -1 : 1;
GetComponent<Rigidbody>().angularVelocity = axis * Vector3.up * rotateSpeed;
return false;
}
GetComponent<Rigidbody>().velocity = Vector3.zero;
return true;
}
private void HPLookAtCamera()
{
Vector3 cameraPos = Camera.main.transform.position;
Vector3 target = new Vector3(cameraPos.x, hp.position.y, cameraPos.z);
hp.LookAt(target);
}
private void Fire()
{
if(fireWaitTime > bulletCoolTime)
{
BulletInfo bulletInfo = new BulletInfo("EnemyBullet", Color.yellow, top.forward, 5f, 10f);
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity); // 通过预设体创建炮弹
bullet.AddComponent<BulletController>().SetBulletInfo(bulletInfo);
fireWaitTime = 0;
}
}
}