|
1 需求实现
项目代码见→坦克大战1.1.0
1)人机交互
玩家通过 ↑ ↓ ← → 键(或 W、S、A、D)键控制己方坦克平移;玩家通过滑动鼠标右键控制己方坦克左右旋转;玩家通过鼠标左键(或空格键)控制己方坦克发射炮弹;玩家通过 ESC 键控制窗口全屏和恢复;玩家通过 Q 键控制退出游戏;
2)相机
主相机跟随:主相机始终在玩家后上方的位置,并保持与玩家的相对位置不变;次相机实现小地图:次相机俯拍战场,并将影像在屏幕右上角显示;单击小地图,小地图全屏,再单击,小地图恢复,在全屏和恢复的过程中有缩放动效。
3)坦克属性
坦克属性:血量、移动速度、旋转速度、颜色、初始位置、初始方位。
4)炮弹属性
5)敌方坦克生成策略
敌方坦克根据能力值分三个级别,对应比例为 3:2:1,颜色分别为灰、浅蓝、黄;敌方坦克初始生成 10 个,之后每隔 2 秒生成一个,总共 50 个坦克;敌方坦克生成位置和朝向都是随机的,并且能够保证坦克位置不会重叠。
6)敌方坦克作战策略
转向再开炮:玩家在敌方坦克 0.5 倍射程范围内,敌方坦克转向玩家,再向玩家开炮;转向再靠近并开炮:玩家在敌方坦克 1 倍射程范围内,敌方坦克转向玩家,再向玩家移动,同时向玩家开炮转向再靠近:玩家在敌方坦克 1.5 倍射程范围内,敌方坦克转向玩家,再向玩家移动;随机巡逻:玩家在敌方坦克在 1.5 倍射程范围外,敌方坦克随机巡逻:将其 10 ~ 30米外的随机一处位置作为目标位置,转向目标位置,再向目标位置移动;如果到中途满足以上条件,则执行相应操作;如果不满足,继续向目标位置移动;如果中途发现前方 5 米范围内有友军,重新换一个目标位置继续巡逻;如果达到目标位置仍不满足以上条件,再换一个目标位置继续巡逻。
7)血条
玩家血条显示在屏幕左上角,玩家被命中时,扣除相应血量,血量进度条向左滑动,血量比例更新;敌方坦克血条显示在坦克的上方,随坦克一起运动,坦克被命中时,扣除相应血量,血量进度条向左滑动;敌方坦克在运动时,其血条始终朝向主相机。
8)杀敌计数
在屏幕左上角、玩家血条下方显示杀敌计数,每杀掉一个敌人,杀敌计数加 1,进度条向右滑动。
9)摇杆
摇杆显示在屏幕左下角,拖拽摇杆可以控制己方坦克平移;↑ ↓ ← → 键(或 W、S、A、D 键)除了能控制己方坦克平移,还能控制摇杆移动。
10)死亡策略
坦克在死亡时,停止运动并停止发射炮弹,渐变为透明;己方坦克死亡时,生成一个空对象替换己方坦克,使得玩家可以通过 ↑ ↓ ← → 键(或 W、S、A、D 键)及鼠标右键,继续查看战场。
2 相关技术栈
Transform组件人机交互刚体组件Rigidbody碰撞体组件Collider物理射线相机跟随Canvas渲染模式与锚点UGUI之TextUGUI之Image血条UGUI回调函数摇杆退出全屏、退出游戏相机
3 游戏对象
1)游戏界面
2)游戏对象层级结构
Hierarchy 窗口游戏对象:
预设体游戏对象:
3)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 预设体。
玩家 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。
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。
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 |
敌人 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。
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 图层(不渲染敌人血条)。
4 游戏框架
1)类图
2)代码目录
5 代码实现
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 游戏对象上。
6 运行效果
声明:本文转自【Unity3D】Tank大战 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|