项目代码见→坦克大战1.1.0
1)人机交互
2)相机
3)坦克属性
4)炮弹属性
5)敌方坦克生成策略
6)敌方坦克作战策略
7)血条
8)杀敌计数
9)摇杆
10)死亡策略
1)游戏界面

2)游戏对象层级结构
Hierarchy 窗口游戏对象:

预设体游戏对象:

3)Transform组件参数
1. Tank Transform 组件参数
| Name | Type | Position | Rotation | Scale |
| Tank | Empty | (0, 0.25, 0) | (0, 0, 0) | (1, 1, 1) |
| Botton | Cube | (0, 0, 0) | (0, 0, 0) | (2, 0.5, 2) |
| Top | Cube | (0, 0.5, 0) | (0, 0, 0) | (1, 0.5, 1) |
| Gun | Cylinder | (0, 0, 1.5) | (90, 0, 0) | (0.2, 1, 0.4) |
| FirePoint | Empty | (0, 1.15, 0) | (0, 0, 0) | (1, 1, 1) |
补充:Tank 游戏对象添加了刚体组件,并修改 Mass = 100,Drag = 1,AngularDrag = 0.5,Tank 作为预设体拖拽到 Assets/Resources/Prefabs 目录下,敌方坦克和己方坦克共用 Tank 预设体。
2. 玩家 Blood RectTransform 组件参数
| Name | Type | Pos | Width/Height | Anchors | Pivot | Color/Texture |
| Blood | Empty | (25, -30, 0) | (0, 0) | (0, 1), (0, 1) | (0.5, 0.5) | —— |
| Progress | Empty | (45, 0, 0) | (0, 0) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
| Background | Image | (0, 0, 0) | (200, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | #FFFFFFFF |
| Value | Image | (0, 0, 0) | (200, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | #F63C3CFF |
| Rate | Text | (5, 0, 0) | (60, 30) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
| Description | Text | (0, 0, 0) | (50, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
补充:Value 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal,Fill Origin 设置为 Left。
3. Kill RectTransform 组件参数
| Name | Type | Pos | Width/Height | Anchors | Pivot | Color/Texture |
| Kill | Empty | (25, -60, 0) | (0, 0) | (0, 1), (0, 1) | (0.5, 0.5) | —— |
| Progress | Empty | (45, 0, 0) | (0, 0) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
| Background | Image | (0, 0, 0) | (200, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | #FFFFFFFF |
| Value | Image | (0, 0, 0) | (200, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | #3CB8F6FF |
| Rate | Text | (5, 0, 0) | (60, 30) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
| Description | Text | (0, 0, 0) | (50, 20) | (0, 0.5), (0, 0.5) | (0, 0.5) | —— |
补充:Value 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal,Fill Origin 设置为 Left。
4. Stick RectTransform 组件参数
| Name | Type | Pos | Width/Height | Anchors | Pivot | Sprite |
| Stick | Empty | (75, 70, 0) | (0, 0) | (0, 0), (0, 0) | (0.5, 0.5) | —— |
| Background | Image | (0, 0, 0) | (100, 100) | (0.5, 0.5), (0.5, 0.5) | (0.5, 0.5) | stick_bg |
| Ball | Image | (0, 0, 0) | (40, 40) | (0.5, 0.5), (0.5, 0.5) | (0.5, 0.5) | stick_ball |
5. 敌人 Blood RectTransform 组件参数
| Name | Type | Pos | Width/Height | Color/Texture |
| Blood | Canvas | (0, 0.85, 0) | (2, 0.2) | —— |
| Background | Image | (0, 0, 0) | (2, 0.2) | #FFFFFFFF |
| Value | Image | (0, 0, 0) | (2, 0.2) | #F63C3CFF |
补充: 敌人 Blood 的 Canvas 渲染模式是 World Space,Value 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal,Fill Origin 设置为 Right。
6. Plane、Bullet、MinimapCamera Transform 组件参数
| Name | Type | Position | Rotation | Scale | Color/Texture |
| 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) | #228439FF |
| MinimapCamera | Camera | (0, 12, 0) | (90, 0, 0) | (1, 1, 1) | —— |
补充:Bullet 作为预设体拖拽到 Assets/Resources/Prefabs 目录下,并且添加了刚体组件,己方坦克和敌方坦克使用的炮弹预设体都是 Bullet;MinimapCamera 是次相机,用于渲染小地图,其 Viewport Rect 为 (0.8, 0.7, 0.2, 0.3),Culling Mask 中去掉勾选 UI 图层(不渲染敌人血条)。
1)类图

2)代码目录

BaseCreater.cs
using System;
using UnityEngine;
public class BaseCreater : MonoBehaviour {
protected GameObject tankPrefab; // 坦克预设体
protected Action dyingAction; // 死亡活动
protected void LoadPrefab() { // 加载预设体
tankPrefab = (GameObject) Resources.Load("Prefabs/Tank");
}
protected GameObject CreateTank(TankInfo info) { // 创建坦克
GameObject tank = Instantiate(tankPrefab, info.tankInitPosition, Quaternion.Euler(info.tankInitRotation));
tank.name = info.GetName();
UpdateColor(tank, info.tankColor);
return tank;
}
private void UpdateColor(GameObject tank, Color color) { // 更新坦克颜色
ForAllChildren(tank, obj => {
MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
if (renderer != null) {
renderer.material.color = color;
}
});
}
private void ForAllChildren(GameObject obj, Action<GameObject> action) { // 每个子孙对象(包含自己)执行委托动作
if (obj == null) {
return;
}
foreach(Transform child in obj.GetComponentsInChildren<Transform>()) {
action(child.gameObject);
}
}
}
PlayerCreater.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class PlayerCreater : BaseCreater {
[Header("坦克信息")]
public TankInfo tankInfo;
private void Awake() {
LoadPrefab();
tankInfo = TankParamsManager.GetInstance().GetPlayerTankInfo();
GameObject tank = CreateTank(tankInfo);
dyingAction += () => {
Camera.main.GetComponent<CameraController>().PlayerDying(); // 玩家死亡, 相机重新跟随一个空对象
};
tank.AddComponent<PlayerController>()
.SetDiedAction(dyingAction)
.SetTankInfo(tankInfo);
}
}
说明:PlayerCreater 脚本组件挂在 PlayerCreater 游戏对象上。
EnemyCreater.cs
using System;
using UnityEngine;
public class EnemyCreater : BaseCreater {
protected GameObject bloodPrefab; // 血条预设体
public EnemiesInfo enemiesInfo = null;
private Transform player; // 玩家
private KillHelper killHelper; // 杀敌助手
private float waitTime = 0; // 距离上次坦克生成已等待的时间
private void Awake() {
LoadPrefab();
enemiesInfo = new EnemiesInfo();
killHelper = new KillHelper(enemiesInfo.GetTotalNum());
dyingAction += () => {
enemiesInfo.CurrDes();
killHelper.KillEnemy();
};
}
private void Start() {
player = GameObject.Find("Player/Top").transform;
CreateInitEnemies();
}
private void Update() {
if (waitTime > EnemiesInfo.ENTER_INTERVAL && enemiesInfo.GetAppeardNum() < EnemiesInfo.TOTAL_NUM) {
CreateTank();
waitTime = 0;
}
waitTime += Time.deltaTime;
}
protected new void LoadPrefab() { // 加载预设体
base.LoadPrefab();
bloodPrefab = (GameObject) Resources.Load("Prefabs/Blood");
}
private void CreateInitEnemies() {
for (int i = 0; i < EnemiesInfo.INIT_NUM; i++) {
CreateTank();
}
}
private GameObject CreateTank() { // 创建坦克
int level = enemiesInfo.GetRandomLevel();
TankInfo tankInfo = TankParamsManager.GetInstance().GetEnemyTankInfo(level);
Vector3 position = GetEnemyPosition();
Vector3 rotation = GetEnemyRotation();
tankInfo.tankInitPosition = position;
tankInfo.tankInitRotation = rotation;
GameObject tank = CreateTank(tankInfo);
CreateHealth(tank);
tank.AddComponent<EnemyController>()
.SetPlayer(player)
.SetDiedAction(dyingAction)
.SetTankInfo(tankInfo);
enemiesInfo.CurrAdd();
return tank;
}
private Vector3 GetEnemyPosition() { // 获取敌人随机位置
Vector3 position = new Vector3(0f, 0.25f, 0f);
do {
position.x = UnityEngine.Random.Range(-49f, 49f);
position.z = UnityEngine.Random.Range(-49f, 49f);
} while (IsOccupied(position));
return position;
}
private Vector3 GetEnemyRotation() { // 获取敌人随机方向
int y = UnityEngine.Random.Range(0, 360);
return new Vector3(0, y, 0);
}
private bool IsOccupied(Vector3 pos) { // 检测指定位置是否被占用
Vector3 halfExtents = new Vector3(2, 0.1f, 2);
return Physics.CheckBox(pos, halfExtents);
}
private void CreateHealth(GameObject tank) { // 创建血条
Vector3 pos = tank.transform.position;
Vector3 realPos = new Vector3(pos.x, pos.y + 1.5f, pos.z);
GameObject blood = Instantiate(bloodPrefab, realPos, Quaternion.identity, tank.transform);
blood.name = "Blood";
}
}
说明:EnemyCreater 脚本组件挂在 EnemyCreater 游戏对象上。
TankController.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class TankController : MonoBehaviour {
protected TankInfo tankInfo; // 坦克信息
protected GameObject bulletPrefab; // 炮弹预设体
protected Transform firePoint; // 开火点
protected BloodHelper bloodHelper; // 血量助手
protected float fireWaitTime = float.MaxValue; // 距离上次开火已等待的时间
protected volatile bool isDying = false; // 死亡中
protected Action diedAction; // 敌人死亡活动
protected void LoadBullet() {
bulletPrefab = (GameObject) Resources.Load("Prefabs/Bullet");
firePoint = transform.Find("Top/Gun/FirePoint");
}
public TankController SetTankInfo(TankInfo info) {
tankInfo = info;
return this;
}
public TankController SetDiedAction(Action action) {
diedAction = action;
return this;
}
protected void Fire() { // 开炮
if (fireWaitTime > tankInfo.bullet.bulletCoolTime) {
BulletInfo info = tankInfo.bullet.Clone() as BulletInfo;
info.SetFlyDir(transform.forward);
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity);
bullet.AddComponent<BulletController>().SetBulletInfo(info);
fireWaitTime = 0f;
}
}
public void BuckleBlood(int damage) { // 扣血
isDying = bloodHelper.BuckleBlood(damage);
}
public bool IsDying() {
return isDying;
}
protected void Dying() { // 坦克死亡中, 渐变为透明
ForAllChildren(gameObject, obj => {
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer != null) {
Color color = renderer.material.color;
// 材质球的 Rendering Mode 需要设置为 Fade 或 Transparent, 渐变才会生效
renderer.material.color = new Color(color.r, color.g, color.b, color.a - Time.deltaTime / 3);
}
Image image = obj.GetComponent<Image>();
if (image != null) {
Color color = image.color;
image.color = new Color(color.r, color.g, color.b, color.a - Time.deltaTime / 3);
}
});
}
protected bool CheckFallDeath() { // 检验摔死
if (!isDying && transform.position.y < -5) {
isDying = true;
Destroy(gameObject, 3);
diedAction.Invoke();
return true;
}
return false;
}
protected void ForAllChildren(GameObject obj, Action<GameObject> action) { // 每个子孙对象(包含自己)执行委托动作
if (obj == null) {
return;
}
foreach(Transform child in obj.GetComponentsInChildren<Transform>()) {
action(child.gameObject);
}
}
}
PlayerController.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class PlayerController : TankController {
private MoveHelper moveHelper; // 交互助手
private StickController stick; // 摇杆控制器
private void Awake() {
LoadBullet();
stick = GameObject.Find("UI/Stick/Ball").GetComponent<StickController>();
moveHelper = new MoveHelper(transform);
}
private void Start() {
diedAction += () => {
stick.PlayerDying();
};
moveHelper.Init(tankInfo.tankMoveSpeed, tankInfo.tankRotateSpeed);
LoadBlood();
}
private void Update() {
if (isDying || CheckFallDeath()) {
Dying();
return;
}
fireWaitTime += Time.deltaTime;
moveHelper.Move();
moveHelper.Rotate();
if (Input.GetMouseButtonDown(0) && !stick.IsMouseInStickArea()
|| Input.GetKeyDown(KeyCode.Space)) {
Fire();
}
}
private void LoadBlood() {
Image bloodValueImg = GameObject.Find("UI/Blood/Progress/Value").GetComponent<Image>();
Text bloodRateTxt = GameObject.Find("UI/Blood/Progress/Rate").GetComponent<Text>();
bloodHelper = new BloodHelper(bloodValueImg, bloodRateTxt, tankInfo, diedAction);
}
public void Move(float hor, float ver) { // 坦克移动
moveHelper.Move(hor, ver);
}
}
说明:PlayerController 脚本组件挂在 Player 游戏对象上。
EnemyController.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class EnemyController : TankController {
private Transform player; // 玩家
private Transform top; // 炮头
private Vector3 tempTarget; // 临时目标点,巡逻时使用
private bool isFoundPlayer = false; // 是否发现了玩家
private float findTargetTime = 0; // 巡逻时寻找目标花费的时间
private void Awake() {
LoadBullet();
top = transform.Find("Top");
Vector3 pos = top.position;
tempTarget = new Vector3(pos.x, pos.y, pos.z);
}
private void Start(){
LoadBlood();
}
private void Update() {
if (isDying || CheckFallDeath()) {
Dying();
return;
}
bloodHelper.BloodLookAtCamera();
float distance = GetDistance();
if (distance < 1.5f * tankInfo.bullet.bulletFireRange) { // 发现了玩家
if (distance < tankInfo.bullet.bulletFireRange / 2 || distance < 5) {
Fire();
} else if (distance < tankInfo.bullet.bulletFireRange) {
MoveToTarget(player.position);
Fire();
} else {
MoveToTarget(player.position);
}
isFoundPlayer = true;
findTargetTime = 0;
} else {
Patrol();
isFoundPlayer = false;
}
fireWaitTime += Time.deltaTime;
}
public EnemyController SetPlayer(Transform transform) {
player = transform;
return this;
}
protected new void Fire() { // 开炮
if (LookAtTarget(player.position)) {
base.Fire();
}
}
private bool LookAtTarget(Vector3 target) { // 转向目标
Vector3 dir = target - 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 * tankInfo.tankRotateSpeed;
return false;
}
GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
return true;
}
private void MoveToTarget(Vector3 target) { // 向目标移动
if (LookAtTarget(target)) {
Vector3 dir = (target - top.position).normalized;
GetComponent<Rigidbody>().velocity = tankInfo.tankMoveSpeed * dir;
findTargetTime += Time.deltaTime;
}
}
private void Patrol() { // 巡逻
GetNextPatrolTarget();
MoveToTarget(tempTarget);
}
private float GetDistance() { // 获取坦克距离玩家的距离
if (player == null) {
return float.MaxValue;
}
return Vector3.Distance(player.position, top.position);
}
private void GetNextPatrolTarget() { // 获取下一个巡逻目标点
if (isFoundPlayer || Vector3.Distance(tempTarget, top.position) < 0.1f
|| ForwardHasFriends() && findTargetTime > 1) {
float angle = top.eulerAngles.y + UnityEngine.Random.Range(-120f, 120f);
angle = angle * Mathf.PI / 180f;
float radius = UnityEngine.Random.Range(10f, 30f);
tempTarget.x = Mathf.Clamp(top.position.x + radius * Mathf.Cos(angle), -49, 49);
tempTarget.z = Mathf.Clamp(top.position.z + radius * Mathf.Sin(angle), -49, 49);
findTargetTime = 0;
}
}
private bool ForwardHasFriends() { // 前方有友军
return Physics.BoxCast(top.position, new Vector3(1, 0.5f, 1), top.forward, top.rotation, 5, ~(1 << 8));
}
private void LoadBlood() {
Transform blood = transform.Find("Blood");
Image bloodValueImg = blood.Find("Value").GetComponent<Image>();
bloodHelper = new BloodHelper(bloodValueImg, null, tankInfo, diedAction);
bloodHelper.setTransform(blood);
}
}
说明:EnemyController 脚本组件挂在 Enemy 游戏对象上。
BulletController.cs
using UnityEngine;
public class BulletController : MonoBehaviour {
private BulletInfo bulletInfo; // 炮弹信息
private bool isDying = false;
private void Start () {
GetComponent<MeshRenderer>().material.color = bulletInfo.bulletColor;
float lifeTime = bulletInfo.bulletFireRange / bulletInfo.bulletSpeed; // 存活时间
Destroy(gameObject, lifeTime);
}
private void Update () {
transform.GetComponent<Rigidbody>().velocity = bulletInfo.GetFlyDir() * bulletInfo.bulletSpeed;
}
private void OnCollisionEnter(Collision other) {
TankController tankController = other.gameObject.GetComponent<TankController>();
if (isDying || tankController == null || tankController.IsDying()) {
return;
}
if (isHitEnemy(bulletInfo.GetName(), other.gameObject.name)
|| isHitPlayer(bulletInfo.GetName(), other.gameObject.name)) {
tankController.BuckleBlood(bulletInfo.bulletDamage);
if (tankController.IsDying()) {
Destroy(other.gameObject, 3f);
}
}
isDying = true;
Destroy(gameObject, 0.3f);
}
public void SetBulletInfo(BulletInfo info) {
bulletInfo = info;
}
private bool isHitEnemy(string bulletName, string roleName) { // 击中敌军
return "PlayerBullet".Equals(bulletName) && "Enemy".Equals(roleName);
}
private bool isHitPlayer(string bulletName, string roleName) { // 击中玩家
return "EnemyBullet".Equals(bulletName) && "Player".Equals(roleName);
}
}
说明:BulletController 脚本组件挂在 Bullet 游戏对象上。
CameraController.cs
using UnityEngine;
public class CameraController : MonoBehaviour {
protected Transform player; // 玩家
protected Transform visitor; // 游客
protected Vector3 relaPlayerPos; // 相机在玩家坐标系中的位置
protected void InitPlayer() {
GameObject obj = GameObject.Find("Player/Top");
if (obj != null) {
player = obj.transform;
CompCameraPos(player);
}
}
public void PlayerDying() { // 玩家死亡时, 游客替换玩家的位置, 使相机跟随游客
GameObject visitorObj = new GameObject("Visitor");
visitorObj.AddComponent<VisitorController>();
visitor = visitorObj.transform;
visitor.position = player.position;
visitor.rotation = player.rotation;
player = null;
}
protected Transform getVisitor() { // 给小地图相机使用
GameObject visitorObj = GameObject.Find("Visitor");
if (visitorObj != null) {
return visitorObj.transform;
}
return visitor;
}
// 求以origin为原点, locX, locY, locZ 为坐标轴的本地坐标系中的向量 vec 在世界坐标系中对应的向量
protected Vector3 transformVecter(Vector3 vec, Vector3 origin, Vector3 locX, Vector3 locY, Vector3 locZ) {
return vec.x * locX + vec.y * locY + vec.z * locZ + origin;
}
protected void CompCameraPos(Transform target) { // 计算相机位置
}
}
MainCameraController.cs
using UnityEngine;
public class MainCameraController : CameraController {
private float targetDistance = 30f; // 相机看向玩家前方的位置
private void Start() {
relaPlayerPos = new Vector3(0, 8, -12);
InitPlayer();
}
private void LateUpdate() {
if (player != null) {
CompCameraPos(player);
} else if (visitor == null) {
visitor = getVisitor();
} else {
CompCameraPos(visitor);
}
}
protected new void CompCameraPos(Transform target) { // 计算相机坐标
transform.position = transformVecter(relaPlayerPos, target.position, target.right, target.up, target.forward);
if (transform.position.y < 0) { // 避免坦克翻面时相机朝天
transform.position = new Vector3(transform.position.x, -transform.position.y, transform.position.z);
}
Vector3 lookPos = target.position + target.forward * targetDistance;
if (lookPos .y > 1) { // 避免坦克扬起时相机朝天
lookPos = new Vector3(lookPos.x, 1, lookPos.z);
}
transform.rotation = Quaternion.LookRotation(lookPos - transform.position);
}
}
说明:MainCameraController 脚本组件挂在 MainCamera 游戏对象上。
MinimapCameraController.cs
using UnityEngine;
public class MinimapCameraController : CameraController {
private CameraController mainCamera;
private bool isFullscreen = false; // 小地图相机是否全屏
private Rect minViewport; // 小地图视口位置和大小(相对值)
private Rect fullViewport; // 全屏时视口位置和大小(相对值)
private Rect targetViewport; // 目标视口大小, 用于动效
private float switchRectInternal = 0.3f; // 切换小地图尺寸时间间隔
private float switchWaitTime = float.MaxValue; // 切换小地图等待时间
private bool isSwitching = false; // 切换中, 用于标记执行切换动效中
private void Start() {
mainCamera = Camera.main.GetComponent<CameraController>();
minViewport = GetComponent<Camera>().rect;
fullViewport = new Rect(0, 0, 1, 1);
relaPlayerPos = new Vector3(0, 50, 0);
InitPlayer();
}
private void Update() {
switchWaitTime += Time.deltaTime;
if (switchWaitTime > switchRectInternal && !isSwitching) {
SwitchRect();
}
if (isSwitching) {
SwitchAnimation();
}
}
private void LateUpdate() {
if (player != null) {
CompCameraPos(player);
} else if (visitor == null) {
visitor = getVisitor();
} else {
CompCameraPos(visitor);
}
}
private void SwitchRect() { // 切换小地图尺寸
if (Input.GetMouseButtonDown(0) && IsClickMinimap()) {
if (isFullscreen) { // 小地图缩小
targetViewport = minViewport;
} else { // 小地图放大
targetViewport = fullViewport;
}
isFullscreen = !isFullscreen;
isSwitching = true;
switchWaitTime = 0;
}
}
private void SwitchAnimation() { // 切换动效
Rect rect = GetComponent<Camera>().rect;
rect.position = Vector2.Lerp(rect.position, targetViewport.position, 0.5f);
rect.size = Vector2.Lerp(rect.size, targetViewport.size, 0.5f);
GetComponent<Camera>().rect = rect;
if (Vector2.Distance(rect.position, targetViewport.position) < 0.01f) {
GetComponent<Camera>().rect = targetViewport;
isSwitching = false;
}
}
protected new void CompCameraPos(Transform target) { // 计算相机坐标
transform.position = target.position + relaPlayerPos;
}
public bool IsClickMinimap() { // 是否单击到小地图区域
Vector3 pos = Input.mousePosition;
if (isFullscreen || isSwitching) {
return true;
}
if (pos.x / Screen.width > minViewport.xMin && pos.y / Screen.height > minViewport.yMin) {
return true;
}
return false;
}
}
说明:MinimapCameraController 脚本组件挂在 MinimapCamera 游戏对象上。
VisitorController.cs
using UnityEngine;
public class VisitorController : MonoBehaviour {
private MoveHelper moveHelper; // 交互助手
private void Awake() {
moveHelper = new MoveHelper(transform);
moveHelper.Init(6, 10);
}
private void Update() {
moveHelper.Move();
moveHelper.Rotate();
}
}
说明:VisitorController 脚本组件挂在 Visitor 游戏对象上。
StickController.cs
using UnityEngine;
using UnityEngine.EventSystems;
public class StickController : MonoBehaviour, IDragHandler, IEndDragHandler {
private Vector3 originPos; // 鼠标开始拖拽时位置
private Vector3 currPos; // 鼠标当前位置
private float radius; // 遥杆半径
private PlayerController player; // 玩家控制器
private Vector3 dire = Vector3.zero; // 摇杆球的方位
private void Start () {
originPos = transform.position;
radius = 50;
player = GameObject.Find("Player").GetComponent<PlayerController>();
}
private void Update () {
if (player != null && !Vector3.zero.Equals(dire)) {
player.Move(dire.x, dire.y);
}
}
public void OnDrag(PointerEventData eventData) {
Vector3 vec = Input.mousePosition - originPos;
dire = vec.normalized * Mathf.Min(vec.magnitude / radius, 1);
UpdateStick(dire);
}
public void OnEndDrag(PointerEventData eventData) {
transform.position = originPos;
dire = Vector3.zero;
}
public void UpdateStick(Vector3 dire) { // 更新摇杆位置
transform.position = originPos + dire * radius;
}
public void PlayerDying() {
player = null;
UpdateStick(Vector3.zero);
}
public bool IsMouseInStickArea() { // 鼠标在Stick区域
Vector3 pos = Input.mousePosition;
return pos.x < 150 && pos.y < 150;
}
}
说明:StickController 脚本组件挂在 Stick 游戏对象上。
BloodHelper.cs
using System;
using UnityEngine;
using UnityEngine.UI;
public class BloodHelper {
private Transform transform; // 血条Transform
private Image bloodValueImg; // 血条Image
private Text bloodRateTxt; // 血量比率Text
private TankInfo tankInfo; // 坦克信息
private Action diedAction; // 死亡活动
public BloodHelper(Image bloodValueImg, Text bloodRateTxt, TankInfo tankInfo, Action diedAction) {
this.bloodValueImg = bloodValueImg;
this.bloodRateTxt = bloodRateTxt;
this.tankInfo = tankInfo;
this.diedAction = diedAction;
bloodValueImg.fillAmount = 1;
if (bloodRateTxt != null) {
bloodRateTxt.text = "" + tankInfo.GetFullBlood() + " / " + tankInfo.GetFullBlood();
}
}
public bool BuckleBlood(int damage) { // 扣血
bloodValueImg.fillAmount = tankInfo.BuckleBlood(damage);
if (bloodRateTxt != null) {
bloodRateTxt.text = "" + tankInfo.GetCurrBlood() + " / " + tankInfo.GetFullBlood();
}
bool isDying = tankInfo.IsDying();
if (isDying) {
diedAction.Invoke();
}
return isDying;
}
public void setTransform(Transform transform) {
this.transform = transform;
}
public void BloodLookAtCamera() { // 血条朝向相机
Vector3 cameraPos = Camera.main.transform.position;
Vector3 target = new Vector3(cameraPos.x, transform.position.y, cameraPos.z);
transform.LookAt(target);
}
}
KillHelper.cs
using UnityEngine;
using UnityEngine.UI;
public class KillHelper {
private Image killValueImg; // 杀敌Image
private Text killRateTxt; // 杀敌比率Text
private int enemiesNum; // 敌人总数
private int currKillEnemies; // 当前杀敌数
public KillHelper(int enemiesNum) {
this.enemiesNum = enemiesNum;
currKillEnemies = 0;
killValueImg = GameObject.Find("UI/Kill/Progress/Value").GetComponent<Image>();
killRateTxt = GameObject.Find("UI/Kill/Progress/Rate").GetComponent<Text>();
UpdateUI();
}
public void KillEnemy() { // 杀敌
lock(this) {
currKillEnemies++;
UpdateUI();
}
}
private void UpdateUI() {
killValueImg.fillAmount = 1.0f * currKillEnemies / enemiesNum;
killRateTxt.text = "" + currKillEnemies + " / " + enemiesNum;
}
}
MoveHelper.cs
using UnityEngine;
public class MoveHelper {
private Transform transform; // 交互对象的变换组件
protected StickController stick; // 摇杆控制器
private Vector3 predownMousePoint; // 鼠标右键按下时的位置
private Vector3 currMousePoint; // 鼠标右键滑动过程中的位置
private float moveSpeed; // 移动速度
private float rotateSpeed; // 旋转速度
public MoveHelper(Transform transform) {
this.transform = transform;
stick = GameObject.Find("UI/Stick/Ball").GetComponent<StickController>();
}
public void Init(float moveSpeed, float rotateSpeed) {
this.moveSpeed = moveSpeed;
this.rotateSpeed = rotateSpeed;
}
public void Move() { // 移动
float hor = Input.GetAxis("Horizontal");
float ver = Input.GetAxis("Vertical");
Move(hor, ver);
if (Mathf.Abs(hor) > float.Epsilon || Mathf.Abs(ver) > float.Epsilon) {
updateStick(hor, ver);
}
}
public void Move(float hor, float ver) { // 移动
if (Mathf.Abs(hor) > float.Epsilon || Mathf.Abs(ver) > float.Epsilon) {
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
if (rigidbody != null) {
Vector3 vec = transform.forward * ver + transform.right * hor;
rigidbody.velocity = vec * moveSpeed;
} else {
transform.Translate(new Vector3(hor, 0, ver) * Time.deltaTime * moveSpeed);
}
RestrictBoundary();
}
}
public void Rotate() { // 旋转
if (Input.GetMouseButtonDown(1)) {
predownMousePoint = Input.mousePosition;
} else if (Input.GetMouseButton(1)) {
currMousePoint = Input.mousePosition;
Vector3 vec = currMousePoint - predownMousePoint;
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
if (rigidbody != null) {
rigidbody.angularVelocity = Vector3.up * rotateSpeed * vec.x;
} else {
transform.Rotate(Vector3.up * vec.x * Time.deltaTime * rotateSpeed);
}
predownMousePoint = currMousePoint;
}
}
private void updateStick(float hor, float ver) { // 更新摇杆
Vector3 dire = new Vector3(hor, ver, 0);
dire = dire.normalized * Mathf.Min(dire.magnitude, 1);
stick.UpdateStick(dire);
}
private void RestrictBoundary() { // 限制边界
Vector3 pos = transform.position;
if (Mathf.Abs(pos.x) > 49) {
transform.position = new Vector3(Mathf.Sign(pos.x) * 49, pos.y, pos.z);
} else if (Mathf.Abs(pos.z) > 49) {
transform.position = new Vector3(pos.x, pos.y, Mathf.Sign(pos.z) * 49);
}
}
}
TankInfo.cs
using System;
using UnityEngine;
[Serializable]
public class TankInfo : ICloneable {
[Header("坦克满血量")]
[Range(30, 100)]
public int fullBlood = 30;
[Header("坦克移动速度")]
[Range(1, 5)]
public float tankMoveSpeed = 3f;
[Header("坦克旋转速度")]
[Range(1, 4)]
public float tankRotateSpeed = 2f;
[Header("坦克颜色")]
public Color tankColor = Color.green;
[Header("坦克初始位置")]
public Vector3 tankInitPosition = new Vector3(0f, 0.25f, 0f);
[Header("坦克初始方位")]
public Vector3 tankInitRotation = new Vector3(0, 0, 0);
[Header("炮弹信息")]
public BulletInfo bullet;
private String name = "Enemy"; // 坦克角色名
private int currBlood = 30; // 坦克当前血量
public TankInfo(String name) {
this.name = name;
bullet = new BulletInfo(name + "Bullet");
}
public object Clone() { // 克隆坦克
TankInfo info = new TankInfo(this.name);
info.fullBlood = this.fullBlood;
info.tankMoveSpeed = this.tankMoveSpeed;
info.tankRotateSpeed = this.tankRotateSpeed;
info.tankColor = this.tankColor;
info.tankInitPosition = this.tankInitPosition;
info.tankInitRotation = this.tankInitRotation;
info.bullet = this.bullet.Clone() as BulletInfo;
info.SetCurrBlood(this.currBlood);
return info;
}
public void SetCurrBlood(int blood) { // 设置当前血量
currBlood = blood;
}
public int GetCurrBlood() { // 获取当前血量
return currBlood;
}
public int GetFullBlood() { // 获取满血量
return fullBlood;
}
public float BuckleBlood(int damage) { // 扣血, 返回剩余血量比例
currBlood = Math.Max(currBlood - damage, 0);
return 1.0f * currBlood / fullBlood;
}
public bool IsDying() {
return currBlood == 0;
}
public String GetName() {
return name;
}
}
BulletInfo.cs
using System;
using UnityEngine;
[Serializable]
public class BulletInfo : ICloneable {
[Header("炮弹伤害")]
[Range(2, 10)]
public int bulletDamage = 2;
[Header("炮弹飞行速度")]
[Range(5, 20)]
public int bulletSpeed = 10;
[Header("炮弹冷却时间")]
[Range(0.1f, 1.5f)]
public float bulletCoolTime = 0.3f;
[Header("炮弹射程")]
[Range(5, 15)]
public float bulletFireRange = 10;
[Header("炮弹颜色")]
public Color bulletColor = Color.red;
private String name = "EnemyBullet"; // 炮弹名
private Vector3 flyDir; // 炮弹飞行方向
public BulletInfo(String name) {
this.name = name;
}
public void SetFlyDir(Vector3 flyDir) {
this.flyDir = flyDir;
}
public Vector3 GetFlyDir() {
return flyDir;
}
public String GetName() {
return name;
}
public object Clone() { // 克隆炮弹
BulletInfo info = new BulletInfo(this.name);
info.bulletDamage = this.bulletDamage;
info.bulletSpeed = this.bulletSpeed;
info.bulletCoolTime = this.bulletCoolTime;
info.bulletFireRange = this.bulletFireRange;
info.bulletColor = this.bulletColor;
return info;
}
}
EnemiesInfo.cs
using UnityEngine;
public class EnemiesInfo {
public const int TOTAL_NUM = 50; // 敌人总数
public const int INIT_NUM = 10; // 敌人初始入场个数
public const int ENTER_INTERVAL = 2; // 敌人入场时间间隔
public const int LEVEL_1_WEIGHT = 3; // 一级敌人个数权重
public const int LEVEL_2_WEIGHT = 2; // 二级敌人个数权重
public const int LEVEL_3_WEIGHT = 1; // 三级敌人个数权重
private volatile int appeardNum = 0; // 已入场的敌人数
private volatile int currentNum = 0; // 当前敌人数
private int totalWeight; // 总权重
public EnemiesInfo() {
totalWeight = LEVEL_1_WEIGHT + LEVEL_2_WEIGHT + LEVEL_3_WEIGHT;
}
public void CurrAdd() {
lock(this) {
appeardNum ++;
currentNum ++;
}
}
public void CurrDes() {
lock(this) {
currentNum --;
}
}
public int GetRandomLevel() {
float random = Random.Range(0f, totalWeight);
if (random < LEVEL_1_WEIGHT) {
return 1;
}
if (random < LEVEL_1_WEIGHT + LEVEL_2_WEIGHT) {
return 2;
}
return 3;
}
public int GetAppeardNum() {
return appeardNum;
}
public int GetTotalNum() {
return TOTAL_NUM;
}
}
TankParamsManager.cs
using UnityEngine;
public class TankParamsManager {
private const int MIN_ENEMY_LEVEL = 1;
private const int MAX_ENEMY_LEVEL = 3;
private static TankParamsManager tankParamsManager;
private TankInfo LEVEL_0 = new TankInfo("Player"); // 玩家
private TankInfo LEVEL_1 = new TankInfo("Enemy"); // 敌军1级
private TankInfo LEVEL_2 = new TankInfo("Enemy"); // 敌军2级
private TankInfo LEVEL_3 = new TankInfo("Enemy"); // 敌军3级
private TankParamsManager() {
Level0Params();
Level1Params();
Level2Params();
Level3Params();
}
public static TankParamsManager GetInstance() {
if (tankParamsManager == null) {
tankParamsManager = new TankParamsManager();
}
return tankParamsManager;
}
public TankInfo GetPlayerTankInfo() {
return LEVEL_0.Clone() as TankInfo;
}
public TankInfo GetEnemyTankInfo(int level) {
level = Camp(level, MIN_ENEMY_LEVEL, MAX_ENEMY_LEVEL);
switch (level) {
case 1:
return LEVEL_1.Clone() as TankInfo;
case 2:
return LEVEL_2.Clone() as TankInfo;
case 3:
return LEVEL_3.Clone() as TankInfo;
}
return LEVEL_1;
}
private void Level0Params() {
LEVEL_0.fullBlood = 500;
// LEVEL_0.fullBlood = 2;
LEVEL_0.tankColor = Color.green;
LEVEL_0.tankMoveSpeed = 4;
LEVEL_0.tankRotateSpeed = 2;
LEVEL_0.SetCurrBlood(LEVEL_0.fullBlood);
LEVEL_0.bullet.bulletDamage = 10;
LEVEL_0.bullet.bulletColor = Color.red;
LEVEL_0.bullet.bulletCoolTime = 0.15f;
LEVEL_0.bullet.bulletFireRange = 15;
LEVEL_0.bullet.bulletSpeed = 10;
}
private void Level1Params() {
LEVEL_1.fullBlood = 30;
LEVEL_1.tankColor = Color.gray;
LEVEL_1.tankMoveSpeed = 1;
LEVEL_1.tankRotateSpeed = 0.85f;
LEVEL_1.SetCurrBlood(LEVEL_1.fullBlood);
LEVEL_1.bullet.bulletDamage = 2;
LEVEL_1.bullet.bulletColor = Color.gray;
LEVEL_1.bullet.bulletCoolTime = 2f;
LEVEL_1.bullet.bulletFireRange = 10;
LEVEL_1.bullet.bulletSpeed = 5;
}
private void Level2Params() {
LEVEL_2.fullBlood = 40;
LEVEL_2.tankColor = Color.cyan;
LEVEL_2.tankMoveSpeed = 1.3f;
LEVEL_2.tankRotateSpeed = 0.9f;
LEVEL_2.SetCurrBlood(LEVEL_2.fullBlood);
LEVEL_2.bullet.bulletDamage = 3;
LEVEL_2.bullet.bulletColor = Color.gray;
LEVEL_2.bullet.bulletCoolTime = 1.7f;
LEVEL_2.bullet.bulletFireRange = 11;
LEVEL_2.bullet.bulletSpeed = 6;
}
private void Level3Params() {
LEVEL_3.fullBlood = 50;
LEVEL_3.tankColor = Color.yellow;
LEVEL_3.tankMoveSpeed = 1.6f;
LEVEL_3.tankRotateSpeed = 1f;
LEVEL_3.SetCurrBlood(LEVEL_3.fullBlood);
LEVEL_3.bullet.bulletDamage = 4;
LEVEL_3.bullet.bulletColor = Color.yellow;
LEVEL_3.bullet.bulletCoolTime = 1.4f;
LEVEL_3.bullet.bulletFireRange = 12;
LEVEL_3.bullet.bulletSpeed = 7;
}
private int Camp(int value, int min, int max) {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
}
WindowController.cs
using UnityEngine;
public class WindowController : MonoBehaviour {
private bool fullscreen = true; // 全屏
private void Update () {
if (Input.GetKeyDown(KeyCode.Escape)) {
fullscreen = !fullscreen;
Screen.fullScreen = fullscreen;
}
if (Input.GetKeyDown(KeyCode.Q)) {
Application.Quit();
}
}
}
说明:WindowController 脚本组件挂在 Window 游戏对象上。


无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c
三分钟集成Tap防沉迷SDK(Unity版)一、SDK介绍基于国家对上线所有游戏必须增加防沉迷功能的政策下,TapTap推出防沉迷SDK,供游戏开发者进行接入;允许未成年用户在周五、六、日以及法定节假日晚上8:00-9:00进行游戏,防沉谜时间段进入游戏会弹窗进行提示!开发环境要求:Unity2019.4或更高版本iOS10或更高版本Android5.0(APIlevel21)或更高版本🔗Unity集成Demo参考链接🔗UnityTapSDK功能体验APK下载链接二、集成前准备1.创建应用进入开发者后台,按照提示开始创建应用;2.开通服务在使用TDS实名认证和防沉迷服务之前,需要在上面创建的应
写在前面前两天学习并整理的大气散射基础知识:【Unity大气渲染】关于单次大气散射的理论知识,收获了很多,但不得不承认的是,这其实已经是最早的、90年代的非常古老的方法了,后来也出现了一些优化性的计算思路和方法。因此,我打算先不急着跟各种教程在Unity中实现大气散射,而是再花时间来看看最近的游戏是如何去实现大气渲染的:06.游戏中地形大气和云的渲染(下)|GAMES104-现代游戏引擎:从入门到实践接下来就跟着GAMES104讲地形大气和云渲染的部分学习并做简单的记录,涉及到之前没提到的Mie散射也只选择直接截图PPT的方式记录啦!毕竟对于做作品来说,之后实现出来才是重要的~当然,May佬的