黑洞大作战游戏架构设计与实现

版权申明:

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
  • 更多学习资源请加QQ:1517069595获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)

设计思路:

  1. 玩家的移动、吃物体和敌人、被敌人吃掉、用遥感移动、玩家升级变大
  2. 敌人的移动。被玩家吃掉、吃掉场景中的物体、敌人寻找可以吃掉的物体,在范围内敌人等级大于玩家时追击玩家
  3. 物体被吃掉掉落、物体过一段时间刷新、物体不能被玩家吃掉与玩家碰撞修改透明度
  4. UI,得分、时间、开场动画、结束动画、再来一局按钮
  5. 相机跟随玩家移动、玩家升级是拉远
  6. 得分的上升以及淡出

数据架构

  1. 单件类:存放两个数组一个是得分达到多少可以升级的用于判断的数组,另一个是玩家或者敌人变化的Scale值得数组
  2. 基类:
    • 等级
    • 速度
    • 缩放值
    • 得分
    • 玩家或者敌人派生类继承于基类
  3. 玩家派生类:
    • 调用单件类里的数组
    • 继承基类数据
    • 旋转值
    • 判断是否可以改变scale的bool值
    • 定义一个可以被吃物体的分数变量int值
    • 声音组件
    • 相机组件
    • 坐标组件
  4. 敌人派生类:
    • 继承基类数据
    • 调用单件类里的数组
    • 怪物坐标组件
    • 玩家坐标组件
    • OBJ坐标组件
    • 寻找的OBJ对象
    • 音效组件
    • 是否需要看到玩家转向的bool值、吃掉物体加分的量int值、scale发生改变bool值、平滑mathf需要的0参数值。

功能实现

  1. 玩家
    (1)碰撞吃掉OBJ或者怪物
    (2)在不能吃掉的OBJ发生碰撞使得OBJ变得透明
    (3)吃掉OBJ或者怪物得分,得分达到一定值时升级体型变大

  2. 敌人
    (1)找到玩家,判断玩家等级,高于自己躲避,低于自己追玩家。
    (2)视野内没有玩家是找可以吃掉的OBJ
    (3)碰撞吃掉OBJ,吃掉玩家游戏结束
    (4)和玩家一样得分到达一定值时升级体型变大

  3. 相机
    (1)相机随玩家变大缓慢拉远

  4. UI
    (1)游戏刚开始是缓慢淡出logo和游戏名字
    (2)游戏开始时启动游戏倒计时
    (3)根据不同模式,判断玩家是否复活,或者死亡结束本局游戏,显示排行榜

  5. 对象池
    (1)用于生成敌人,敌人死亡时,重新生成

  6. OBJ
    (1)判断是否可以被敌人或者玩家吃掉,可以被吃掉掉落
    (2)过一段时间重新生成在初始位置

  7. 添加游戏中警车巡逻
    (1)警车巡逻按照路点移动,是游戏画面更加美观。

  8. TTUI制作UI框架

  9. 接入广告,实现看广告得到新皮肤

  10. 异步加载场景。玩家的数据读写,用于解锁新皮肤,记录等级

  11. 添加陷阱,白色黑洞,会根据时间不断变大,玩家或者敌人都会被其吃掉

难点解决

  1. 玩家碰撞到吃不掉的OBJ时,使得OBJ变得透明
    解决方法: 更换被碰撞物体的材质

  2. 通过什么方法判断玩家和敌人变大
    解决方法: 在GameMessage脚本下创建两个数组,一个数组用来判断玩家和敌人的得分,得分大于数组里的一个等级时可以升级。另一个数组用来存玩家和敌人Scale的大小用来升级后进行改变。

  3. 敌人寻找玩家和可以吃掉的OBJ
    解决方法:创建一个数组存下所有OBJ,找到所有OBJ,遍历所有OBJ求得距离敌人最近可以吃的OBJ,敌人向得到的OBJ移动吃掉它。
    判断敌人一定范围内是否可以寻找到玩家,先算得敌人和玩家距离,敌人和玩家距离大于敌人的判定范围并且敌人等级大于玩家,敌人就去追击玩家。

  4. 更换皮肤,实现皮肤解锁功能

  5. 排行榜以及TTUI制作使用

注意事项

  1. 被吃物体挂载的obj脚本要设置obj等级

  2. 要记得选择按钮触发事件(再玩一局)
    在这里插入图片描述

  3. 分数数组要记得填写分数,以及scale数组
    在这里插入图片描述

  4. 敌人以及玩家的碰撞体设置好,防止碰撞检测判断不出来

具体实现

  1. 场景搭建
    (1)在scene文件夹下打开game场景,将里面的模型导入到自己的场景
    (2)确定场景搭建完毕,确定都有碰撞体,物体有可能会掉落,可以给地面添加很大的一个碰撞体,防止物体掉落
    在这里插入图片描述

  2. 创建文件夹导入插件
    (1)把物体下的脚本去除,在Assets下面创建两个文件夹Script、Scenes。Script用来存放脚本,Scenes用来存放场景。导入EasyTouch组件(Easy Touch 4 Touchscreen Virtual Controls v4.1.0)。

  3. 设置摇杆
    (1)找到EasyTouchBundle文件夹下EasyTouchControls下Examples打开一个场景挑选合适的摇杆。把摇杆导入至自己的场景。一定要到如在UI下面。按照下图进行设置,把摇杆的对象选为游戏玩家,Action设置为Translate,Affected axis设置好X,Z轴在这里插入图片描述

  4. 调试玩家和敌人预制体
    (1)在Assets下Resources下找到玩家和敌人的预制体拖拽到自己场景中进行调试。把不需要的碰撞体删除掉。玩家和怪物各自拥有一个Sphere Collider碰撞体就可以。大小自己调试好。把玩家放在一个合适的位置。

  5. 数据架构脚本
    (1)在Script文件夹下创建脚本MovableObject、GameMgr。
    (2)GameMgr脚本下创建两个数组,用来存放升级需要得分数组,scale变大的scale值数组。把GameMgr单利出来做成单件类。

    public static GameMgr instance;
    public int[] ScoreLevelup;//升级需要得分数组
    public int[] ScaleNum;    //scale变大的scale值数组
    //private GameObject player;
    // Start is called before the first frame update
     private void Awake()
    {
        instance = this;
    }

(2)MoveableObject脚本做成一个基类,用来存放玩家和敌人共用的数据

public class MovableObject : MonoBehaviour
{
    //基类

    //多个类共用的数据放在基类,多个对象共用的数据放单件类
    public int lvl;//等级
    public float speed;//速度
    public float OBJLocalScale;//缩放值
    public int OBJscore;//得分
    // Start is called before the first frame update
}
  1. 玩家脚本
    (1)玩家脚本需要包括碰撞检测(敌人或者OBJ),增加得分,升级变大。
    (2)首先玩家脚本继承基类MovableObject,设置玩家脚本自己需要的一些数据。
		public float veclityScale;     //scale的
		public bool CanchangeScores;   //是否改变分数
		public float angle;          //旋转值
		private bool CanChangeScale; //是否可以改变Scale
		public int score;            //定义一个可以被吃物体的分数变量
		public GameObject WgCamera;    //找到相机
		public AudioSource Coin;     //音效组件
		public AudioClip[] music;    //音效数组
		private Camera camera;       //相机 
		private Transform player;    //玩家坐标

(3)将玩家脚本单利出来,用来备用

	 public static Player instance;//单利出来
	 private void Awake()
    {
        instance = this;
    }

(4)在玩家脚本Start()中设置一些初始值

		   private void Start()
		{
			WgCamera= GameObject.FindGameObjectWithTag("MainCamera");//找到相机
			OBJLocalScale = this.transform.localScale.x;//初始化scale
			lvl = 1;//初始化等级
			player = this.transform;//初始化坐标
			veclityScale = 0;//初始化值
			CanchangeScores = false;//不可以改变分数
			Coin = GetComponent<AudioSource>();//找到音效组件
			angle = 3;//初始化旋转角度
    }

(5)在FixedUpdate()中添加玩家旋转、判断游戏是否结束、以及启用玩家加分携程。

			private void FixedUpdate()
		{
			transform.Rotate(0, angle, 0);
			if (UI.instance.IsGameOver == true)//判断是否结束游戏
			return;
			StartCoroutine(Addlevel());//启用携程
		}

(6)玩家的碰撞检测,包含吃掉OBJ或者敌人的。以及玩家碰撞到自己等级不够无法吃掉的OBJ是OBJ会变得透明化(修改材质实现此功能)。吃掉怪物或者OBJ时要添加音效。

	 	private void OnTriggerEnter(Collider other)//判断碰撞吃掉物体
		{

		if (other.tag == "obj")//碰到障碍物吃掉障碍物 
		{
			if (other.gameObject.GetComponent<Obj>().ObjLevel <= lvl)//判断玩家是否可以吃掉物体
			{
					Coin.PlayOneShot(music[0]);
					score = other.gameObject.GetComponent<Obj>().ObjLevel;//把碰撞的物体等级赋值给score
					StartCoroutine(AddScore());//调用携程
				}
			if (other.gameObject.GetComponent<Obj>().ObjLevel >lvl)//判断物体等级大于玩家
			{
			Texture tex2 = other.gameObject.GetComponent<MeshRenderer>().material.mainTexture;//取得meshrenderer组件为了修改透明度
			Material m2 = other.gameObject .GetComponent<MeshRenderer>().material;
			m2.CopyPropertiesFromMaterial(Resources.Load("touming") as Material);//加载材质
			m2.mainTexture = tex2;//更换透明度
				}
			}


			if (other.tag == "enemy")//是怪物
			{
				if (other.gameObject.GetComponent<Enemy>().lvl < lvl)//怪物等级小于玩家
			{
				Coin.PlayOneShot(music[1]);//播放音乐数组里的第一个音效
				score = other.gameObject.GetComponent<Enemy>().lvl;//得分等于怪物等级
				StartCoroutine(AddScore());//调用加分携程
				Destroy(other.gameObject, 1f);//删除敌人

				}
			}
		}
		public void  OnTriggerExit(Collider other)//判断碰撞离开修改材质
		{
			if (other.gameObject.tag == "obj")
			{
				Texture tex1 = other.gameObject.GetComponent<MeshRenderer>().material.mainTexture;//找到材质
				Material m1 = other.gameObject.GetComponent<MeshRenderer>().material;
				m1.CopyPropertiesFromMaterial(Resources.Load("toumingOff") as Material);//找到材质
				m1.mainTexture = tex1;//修改材质
			}
		}

(7)在吃掉OBJ或者怪物是会调用脚本内的加分携程,加分携程包括加分,判断是否可以升级,升级了的话玩家体型变大。同时触发相机拉高判断条件。

	IEnumerator Addlevel()//升级携程
		{
			if (OBJscore > GameMgr.instance.ScoreLevelup[lvl - 1])//判断条件得分大于需要得分的数组里的得分时升级
			{
				lvl += 1;
				CanChangeScale = true;//修改scale
			}
			ChangeScale();
			yield return new WaitForSeconds(1);
		}
		IEnumerator AddScore()//吃物体加分携程
		{
			OBJscore = score + OBJscore;//增加玩家分数
			CanchangeScores = true;
			UI.instance.ScoreText.GetComponent().text = string.Format("SCORE:{0}", OBJscore);//重写玩家分数
			yield return new WaitForSeconds(0.5f);//延迟半秒执行
		}
		public void ChangeScale()//玩家升级变换scale
		{
			if (CanChangeScale == true)
			{
			   OBJLocalScale = Mathf.SmoothDamp(OBJLocalScale, GameMgr.instance.ScaleNum[lvl-1], ref veclityScale, 0.6f);//+++++平缓修改scale
				player.localScale = new Vector3(OBJLocalScale,OBJLocalScale,OBJLocalScale);//+++++
				if (Mathf.Abs(OBJLocalScale - GameMgr.instance.ScaleNum[lvl-1]) < 0.1f)//计算scale和对应等级的scale值
					CanChangeScale = false;
				WgCamera.GetComponent().canchange = true;//相机移动
			}
		}
  1. 敌人脚本
    (1)首先继承MovableObject基类,添加敌人脚本自己需要的数据
		public bool CaneatPlayer;        //玩家是否可以被吃掉
		public float dis;                 //玩家和怪物的距离
		public Transform EnemyObj;        //怪物坐标
		private Transform player;         //查找玩家
		public GameObject Findobj;        //找到的obj
		private AudioSource kill;           //音效
		private Transform ObjToGet;       //obj的transform
		private float veclityScale = 0.0f;//平滑mathf需要的0参数
		private bool CanSee;              //是否需要看到转向
		private int Escore;               //吃掉物体加分的量
		private bool CanchangeEnemy;      //scale发生改变

(2)设置数据的初始值

	 private void Start()
		{
		lvl = 1; 
		player = GameObject.FindGameObjectWithTag("Player").transform;//找到玩家位置
		OBJLocalScale = this.transform.localScale.x;//取得缩放值
		EnemyObj = this.transform;//取得当前transform
		speed = Random.Range(2, 4);//设置随机速度
		CanSee = false;
		kill = GetComponent<AudioSource>();
		}

(3)添加循环FixedUpdate()里面需要寻找OBJ,判断是否可以吃掉OBJ,移动过去吃掉,寻找玩家,是追击玩家还是远离玩家,调用加分携程。

			private void FixedUpdate()//提前执行,里面有
		{
			if (UI.instance.IsGameOver == true)
				return;
			if (ObjToGet == null)
			{
				if (FindOBJ())//如果找到不为空返回Findobj,
					ObjToGet = FindOBJ().transform;
			}
			else
			{
					EnemySee();
			}
			StartCoroutine(ADDlevel());//升级携程
			CanSeePlayer();//看到玩家
			EatPlayer();//吃掉玩家
    }

(4)找到OBJ,创建数组找到所有OBJ遍历数组里的OBJ找到距离敌人最近可以吃掉的那一个,敌人转向移动过去吃掉OBJ

	 	public GameObject FindOBJ()//找到obj
		{
			GameObject[] objj;//创建数组
			objj = GameObject.FindGameObjectsWithTag("obj");//找到所有tag为obj的gameobject
			float EnemySeedis = Mathf.Infinity;
			Vector3 position = transform.position;//赋值position

			foreach(GameObject goobj in objj)
			{
			Vector3 distance = goobj.transform.position - position;//算得距离向量
			float reallydis = distance.sqrMagnitude;//取得vector3平方值
			var o = goobj.GetComponent<Obj>();
			if (o.ObjLevel <= lvl && reallydis < EnemySeedis)
			{
				Findobj = goobj;
				EnemySeedis = reallydis;//计算得到的绝对值赋值给判断距离
			}
			}
			return Findobj;
		}
	  	public void EnemySee()//移动
		{
			if (CaneatPlayer == false) {
			Vector3 pos = new Vector3(ObjToGet.transform.position.x, 0, ObjToGet.transform.position.z);//赋值向量计算旋转用
			transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(pos - transform.position), 5 * Time.deltaTime);//Slerp(从哪,到哪,时间)LookRotation(看向点减去自身坐标得到向量进行转换)
			transform.Translate(Vector3.forward * speed*Time.deltaTime);//移动
			if (ObjToGet.tag != "obj")
			{
				ObjToGet = null;
			}
			}
		}

(5)启用升级携程判断敌人自身是否可以升级变大

		IEnumerator ADDlevel()
		{
			if (OBJscore > GameMgr.instance.ScoreLevelup[lvl - 1])//数组判断得分是否达到升级要求
			{
				lvl += 1;
				CanchangeEnemy = true;
			}
			ChangeScale();
			yield return new WaitForSeconds(1);
		}
		 private void ChangeScale()
		{
			if (CanchangeEnemy == true)//判断可以修改scale缓慢修改scale
			{
				OBJLocalScale = Mathf.SmoothDamp(OBJLocalScale, GameMgr.instance.ScaleNum[lvl - 1], ref veclityScale, 0.6f);
				EnemyObj.localScale = new Vector3(OBJLocalScale, OBJLocalScale, OBJLocalScale);
				if (Mathf.Abs(OBJLocalScale - GameMgr.instance.ScaleNum[lvl - 1]) < 0.1f)
					CanchangeEnemy = false;
			}
		}

(6)寻找玩家判断是否可以吃掉玩家(可以吃掉追击玩家不可以远离寻找OBJ去吃)。

	 public void CanSeePlayer()
		{

		   dis = Vector3.Distance(transform.position, player.position);//定义敌人和玩家算距离
			if (dis < 15)
			{
				if (Player.instance.lvl < lvl)
				{
					CaneatPlayer = true;
				}
				if (Player.instance.lvl >= lvl)
				{
					CaneatPlayer = false;
					return;
				}
			}
			if (dis >= 15)
			{
				CaneatPlayer = false;
			}
		}
		public  void EatPlayer()
		{
			if (CaneatPlayer == true)
			{
				Vector3 pos = new Vector3(player.transform.position.x, 0, player.transform.position.z);//赋值向量计算旋转用
				transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(pos - transform.position), 5 * Time.deltaTime);//Slerp(从哪,到哪,时间)LookRotation(看向点减去自身坐标得到向量进行转换)
				transform.Translate(Vector3.forward * 4 * Time.deltaTime);//移动
			}
		}

(7)敌人碰撞检测(碰撞玩家检测和OBJ检测,是否可以吃掉,吃掉OBJ加分,吃掉玩家游戏结束)

	 private void OnTriggerEnter(Collider other)
		{
			if (other.tag == "Player")
			{
				if (lvl > other.gameObject.GetComponent<Player>().lvl)
				{
					kill.Play();
					UI.instance.IsGameOver = true;//游戏结束
				}
			}
			if (other.tag == "obj")
			{
				if (lvl >= other.gameObject.GetComponent<Obj>().ObjLevel)
				{
					Escore =other.gameObject.GetComponent<Obj>().ObjLevel;//赋值得分
				   OBJscore = Escore + OBJscore;//得分增加
				}
			}
		}
  1. OBJ脚本**(代码请加Q:1517069595)**
    (1)创建OBJ脚本基本数据,单利出来用于玩家敌人脚本调用
    (2)设置OBJ脚本基本数据
    (3)碰撞检测(等级低于玩家或者得人等级被吃掉掉落)
    (4)过一段时间还原初始位置并可以被重新吃掉(刷新,记录了初始位置旋转欧拉角)

  2. 对象池创建敌人**(代码请加Q:1517069595)**
    (1)对象池创建敌人,并可以是敌人保持在一定人数,死亡后重新生成敌人
    (2)创建设置基本数据
    (3)利用循环生成敌人(入队,出队)
    先将敌人入队创建好,不显示。在将敌人出队,显示。

  3. 摄像机脚本**(代码请加Q:1517069595)**
    (1)相机更随玩家移动,玩家升级拉远摄像机
    (2)创建设置相机数据
    (3)循环调用相机移动(实现更随玩家效果),玩家升级时相机拉升Bool值会变为true,相机拉升结束后改为False。相机因为循环实时更随玩家。

  4. 玩家加分淡入淡出效果**(代码请加Q:1517069595)**
    (1)创建设置基本数据
    (2)淡入淡出函数(通过时间一定时间后分数淡出,并且分数有一个上升的过程,需要在循环下执行)
    原理生成一个数字,由玩家脚本得分了生成数字(数字作为预制体),该数字绑定这个脚本,时间一到数字和脚本都删除。

  5. UI脚本**(代码请加Q:1517069595)**
    (1)创建设置UI基本数据
    (2)淡出背景图(一定时间消失,隐藏)
    (3)倒计时的计时器(游戏时间)
    (4)判断游戏是否结束,玩家吃东西得分是玩家脚本的CanchangeScores为true,生成得分text(淡出效果,上一个脚本有解释)。设置好位置把CanchangeScores改为false。
    (5)设置在玩一局的按钮触发事件(按钮点击触发,重新加载场景)
    在这里插入图片描述

  6. 警车巡逻**(代码请加Q:1517069595)**
    (1)创建警车预制体,在已有的模型中用Ctrl+D复制几个警车预制体
    (2)把警车预制体放在起始点,创建几个空节点将警车放在空节点下以便转向使用。
    (3)给空节点添加BoxCollider和Rigidbody组件。并且把Rigidbody组件里的Use Gravity勾选去掉,不使用重力。把Is Kinematic勾选上,不受重力影响。
    (4)在Script文件夹下创建一个叫MoveCar的脚本。
    (5)打开脚本,编辑脚本。脚本包括:车子向路点移动,车子向下一个点转向,碰到敌人或者玩家等于大于自己时掉落销毁。
    (6)

  7. 白色陷阱黑洞制作**(代码请加Q:1517069595)**
    (1)拷贝一个玩家或敌人的模型,把模型的颜色修改为白色,创建脚本Trap挂在陷阱的预制体上
    (2)白色黑洞要实现自己移动,经过一段时间变大,再经过一段时间消失
    (3)白色黑洞不会迟吃掉OBJ但会吃掉敌人和玩家。
    (4)脚本如下

UI框架的接入**(代码请加Q:1517069595)**

  1. 创建一个新场景叫做UIFramework,在场景主界面可以导入一些场景,当做背景,用PS制作一个艺术字图片,贴在背景上面

  2. TopBar制作**(代码请加Q:1517069595)**
    (1)主界面制作包括,左上角的经验条,记录玩家等级以及经验进度的Slider
    (2)右上角制作两个按钮分别是点击看广告的和调整震动的的按钮
    (3)右下角制作一个游戏开始按钮,点击会进行异步加载随机三个场景
    (4)添加两个按钮做成透明的样子,一上一下,用于做返回按钮
    (5)打开UITopBar脚本修改脚本
    (6)左上角的slider添加脚本实现异步加载

  3. UIMain制作**(代码请加Q:1517069595)**
    (1)UIMainPage实现点击出现换肤窗口以及点击出现选择模式窗口
    (2)脚本实现

  4. UINotice制作**(代码请加Q:1517069595)**
    (1)弹窗可以更改是否震动
    (2)脚本实现

  5. UISkill2关卡模式选择**(代码请加Q:1517069595)**
    (1)点击不同按钮实现关卡模式切换,添加按钮触发事件,点击按钮时按钮存下两个值,玩家是否可以复活,本关卡是否生成敌人。数据是通过配置配置出来的。再去读取配置的数据通过按钮事件把数据存下来。玩家以及敌人生成器在start运行时会读取数据判断是否可以复活,以及生成敌人
    (2)脚本实现

  6. MVC框架利用制作皮肤读取更换**(代码请加Q:1517069595)**
    (1)导入MVC框架插件
    (2)创建两个脚本SkinPlayer,Datamanger利用MVC框架用来读写数据
    (3)Datamanger脚本实现
    (4)SkinPlayer脚本实现

  7. UISkillPage制作(接上面的MVC框架皮肤读取切换)(代码请加Q:1517069595)
    (1)制作弹窗的皮肤
    (2)创建一个skinplayer读取皮肤需要从skinplayer的脚本进行读取
    (3)第一个和第二个皮肤在刚开始时可以切换,第三个皮肤要达到等级5自动解锁,第四个皮肤需要看广告后解锁
    (4)脚本实现

接广告**(代码请加Q:1517069595)**

  1. 在Unity里面按Ctrl+0,把ADS打开,进行到网页设置界面
  2. 点击左边的Projects选择要添加广告的项目
    3.选择左边的Monetization下面的Placements,记录下显示的ID号用于接广告用
  3. 脚本实现
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值