草庐IT

【Unity3D】Tank大战

little_fat_sheep 2024-02-08 原文

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 相关技术栈

3 游戏对象

        1)游戏界面

        2)游戏对象层级结构

        Hierarchy 窗口游戏对象:

         预设体游戏对象:

        3)Transform组件参数

        1. Tank Transform 组件参数

NameTypePositionRotationScale
TankEmpty(0, 0.25, 0)(0, 0, 0)(1, 1, 1)
BottonCube(0, 0, 0)(0, 0, 0)(2, 0.5, 2)
TopCube(0, 0.5, 0)(0, 0, 0)(1, 0.5, 1)
GunCylinder(0, 0, 1.5)(90, 0, 0)(0.2, 1, 0.4)
FirePointEmpty(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 组件参数

NameTypePosWidth/HeightAnchorsPivotColor/Texture
BloodEmpty(25, -30, 0)(0, 0)(0, 1), (0, 1)(0.5, 0.5)——
ProgressEmpty(45, 0, 0)(0, 0)(0, 0.5), (0, 0.5)(0, 0.5)——
BackgroundImage(0, 0, 0)(200, 20)(0, 0.5), (0, 0.5)(0, 0.5)#FFFFFFFF
ValueImage(0, 0, 0)(200, 20)(0, 0.5), (0, 0.5)(0, 0.5)#F63C3CFF
RateText(5, 0, 0)(60, 30)(0, 0.5), (0, 0.5)(0, 0.5)——
DescriptionText(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 组件参数

NameTypePosWidth/HeightAnchorsPivotColor/Texture
KillEmpty(25, -60, 0)(0, 0)(0, 1), (0, 1)(0.5, 0.5)——
ProgressEmpty(45, 0, 0)(0, 0)(0, 0.5), (0, 0.5)(0, 0.5)——
BackgroundImage(0, 0, 0)(200, 20)(0, 0.5), (0, 0.5)(0, 0.5)#FFFFFFFF
ValueImage(0, 0, 0)(200, 20)(0, 0.5), (0, 0.5)(0, 0.5)#3CB8F6FF
RateText(5, 0, 0)(60, 30)(0, 0.5), (0, 0.5)(0, 0.5)——
DescriptionText(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 组件参数

NameTypePosWidth/HeightAnchorsPivotSprite
StickEmpty(75, 70, 0)(0, 0)(0, 0), (0, 0)(0.5, 0.5)——
BackgroundImage(0, 0, 0)(100, 100)(0.5, 0.5), (0.5, 0.5)(0.5, 0.5)stick_bg
BallImage(0, 0, 0)(40, 40)(0.5, 0.5), (0.5, 0.5)(0.5, 0.5)stick_ball

        5. 敌人 Blood RectTransform 组件参数

NameTypePosWidth/HeightColor/Texture
BloodCanvas(0, 0.85, 0)(2, 0.2)——
BackgroundImage(0, 0, 0)(2, 0.2)#FFFFFFFF
ValueImage(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 组件参数

NameTypePositionRotationScaleColor/Texture
PlanePlane(0, 0, 0)(0, 0, 0)(10, 10, 10)GrassRockyAlbedo
BulletSphere(0, 0.5, -5)(0, 0, 0)(0.3, 0.3, 0.3)#228439FF
MinimapCameraCamera(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大战的更多相关文章

  1. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  2. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  3. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  4. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  5. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  6. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  7. python - Ruby 或 Python 的 3d 游戏引擎? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?

  8. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  9. 三分钟集成 TapTap 防沉迷 SDK(Unity 版) - 2

    三分钟集成Tap防沉迷SDK(Unity版)一、SDK介绍基于国家对上线所有游戏必须增加防沉迷功能的政策下,TapTap推出防沉迷SDK,供游戏开发者进行接入;允许未成年用户在周五、六、日以及法定节假日晚上8:00-9:00进行游戏,防沉谜时间段进入游戏会弹窗进行提示!开发环境要求:Unity2019.4或更高版本iOS10或更高版本Android5.0(APIlevel21)或更高版本🔗Unity集成Demo参考链接🔗UnityTapSDK功能体验APK下载链接二、集成前准备1.创建应用进入开发者后台,按照提示开始创建应用;2.开通服务在使用TDS实名认证和防沉迷服务之前,需要在上面创建的应

  10. 【Unity大气散射】GAMES104:3A中如何实现大气散射 - 2

    写在前面前两天学习并整理的大气散射基础知识:【Unity大气渲染】关于单次大气散射的理论知识,收获了很多,但不得不承认的是,这其实已经是最早的、90年代的非常古老的方法了,后来也出现了一些优化性的计算思路和方法。因此,我打算先不急着跟各种教程在Unity中实现大气散射,而是再花时间来看看最近的游戏是如何去实现大气渲染的:06.游戏中地形大气和云的渲染(下)|GAMES104-现代游戏引擎:从入门到实践接下来就跟着GAMES104讲地形大气和云渲染的部分学习并做简单的记录,涉及到之前没提到的Mie散射也只选择直接截图PPT的方式记录啦!毕竟对于做作品来说,之后实现出来才是重要的~当然,May佬的

随机推荐