版权申明:
- 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
- 更多学习资源请加QQ:1517069595获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
设计思路:
- 玩家的移动、吃物体和敌人、被敌人吃掉、用遥感移动、玩家升级变大
- 敌人的移动。被玩家吃掉、吃掉场景中的物体、敌人寻找可以吃掉的物体,在范围内敌人等级大于玩家时追击玩家
- 物体被吃掉掉落、物体过一段时间刷新、物体不能被玩家吃掉与玩家碰撞修改透明度
- UI,得分、时间、开场动画、结束动画、再来一局按钮
- 相机跟随玩家移动、玩家升级是拉远
- 得分的上升以及淡出
数据架构
- 单件类:存放两个数组一个是得分达到多少可以升级的用于判断的数组,另一个是玩家或者敌人变化的Scale值得数组
- 基类:
- 等级
- 速度
- 缩放值
- 得分
- 玩家或者敌人派生类继承于基类
- 玩家派生类:
- 调用单件类里的数组
- 继承基类数据
- 旋转值
- 判断是否可以改变scale的bool值
- 定义一个可以被吃物体的分数变量int值
- 声音组件
- 相机组件
- 坐标组件
- 敌人派生类:
- 继承基类数据
- 调用单件类里的数组
- 怪物坐标组件
- 玩家坐标组件
- OBJ坐标组件
- 寻找的OBJ对象
- 音效组件
- 是否需要看到玩家转向的bool值、吃掉物体加分的量int值、scale发生改变bool值、平滑mathf需要的0参数值。
功能实现
-
玩家
(1)碰撞吃掉OBJ或者怪物
(2)在不能吃掉的OBJ发生碰撞使得OBJ变得透明
(3)吃掉OBJ或者怪物得分,得分达到一定值时升级体型变大 -
敌人
(1)找到玩家,判断玩家等级,高于自己躲避,低于自己追玩家。
(2)视野内没有玩家是找可以吃掉的OBJ
(3)碰撞吃掉OBJ,吃掉玩家游戏结束
(4)和玩家一样得分到达一定值时升级体型变大 -
相机
(1)相机随玩家变大缓慢拉远 -
UI
(1)游戏刚开始是缓慢淡出logo和游戏名字
(2)游戏开始时启动游戏倒计时
(3)根据不同模式,判断玩家是否复活,或者死亡结束本局游戏,显示排行榜 -
对象池
(1)用于生成敌人,敌人死亡时,重新生成 -
OBJ
(1)判断是否可以被敌人或者玩家吃掉,可以被吃掉掉落
(2)过一段时间重新生成在初始位置 -
添加游戏中警车巡逻
(1)警车巡逻按照路点移动,是游戏画面更加美观。 -
TTUI制作UI框架
-
接入广告,实现看广告得到新皮肤
-
异步加载场景。玩家的数据读写,用于解锁新皮肤,记录等级
-
添加陷阱,白色黑洞,会根据时间不断变大,玩家或者敌人都会被其吃掉
难点解决
-
玩家碰撞到吃不掉的OBJ时,使得OBJ变得透明
解决方法: 更换被碰撞物体的材质 -
通过什么方法判断玩家和敌人变大
解决方法: 在GameMessage脚本下创建两个数组,一个数组用来判断玩家和敌人的得分,得分大于数组里的一个等级时可以升级。另一个数组用来存玩家和敌人Scale的大小用来升级后进行改变。 -
敌人寻找玩家和可以吃掉的OBJ
解决方法:创建一个数组存下所有OBJ,找到所有OBJ,遍历所有OBJ求得距离敌人最近可以吃的OBJ,敌人向得到的OBJ移动吃掉它。
判断敌人一定范围内是否可以寻找到玩家,先算得敌人和玩家距离,敌人和玩家距离大于敌人的判定范围并且敌人等级大于玩家,敌人就去追击玩家。 -
更换皮肤,实现皮肤解锁功能
-
排行榜以及TTUI制作使用
注意事项
-
被吃物体挂载的obj脚本要设置obj等级
-
要记得选择按钮触发事件(再玩一局)
-
分数数组要记得填写分数,以及scale数组
-
敌人以及玩家的碰撞体设置好,防止碰撞检测判断不出来
具体实现
-
场景搭建
(1)在scene文件夹下打开game场景,将里面的模型导入到自己的场景
(2)确定场景搭建完毕,确定都有碰撞体,物体有可能会掉落,可以给地面添加很大的一个碰撞体,防止物体掉落
-
创建文件夹导入插件
(1)把物体下的脚本去除,在Assets下面创建两个文件夹Script、Scenes。Script用来存放脚本,Scenes用来存放场景。导入EasyTouch组件(Easy Touch 4 Touchscreen Virtual Controls v4.1.0)。 -
设置摇杆
(1)找到EasyTouchBundle文件夹下EasyTouchControls下Examples打开一个场景挑选合适的摇杆。把摇杆导入至自己的场景。一定要到如在UI下面。按照下图进行设置,把摇杆的对象选为游戏玩家,Action设置为Translate,Affected axis设置好X,Z轴 -
调试玩家和敌人预制体
(1)在Assets下Resources下找到玩家和敌人的预制体拖拽到自己场景中进行调试。把不需要的碰撞体删除掉。玩家和怪物各自拥有一个Sphere Collider碰撞体就可以。大小自己调试好。把玩家放在一个合适的位置。 -
数据架构脚本
(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)玩家脚本需要包括碰撞检测(敌人或者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)首先继承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;//得分增加
}
}
}
-
OBJ脚本**(代码请加Q:1517069595)**
(1)创建OBJ脚本基本数据,单利出来用于玩家敌人脚本调用
(2)设置OBJ脚本基本数据
(3)碰撞检测(等级低于玩家或者得人等级被吃掉掉落)
(4)过一段时间还原初始位置并可以被重新吃掉(刷新,记录了初始位置旋转欧拉角) -
对象池创建敌人**(代码请加Q:1517069595)**
(1)对象池创建敌人,并可以是敌人保持在一定人数,死亡后重新生成敌人
(2)创建设置基本数据
(3)利用循环生成敌人(入队,出队)
先将敌人入队创建好,不显示。在将敌人出队,显示。 -
摄像机脚本**(代码请加Q:1517069595)**
(1)相机更随玩家移动,玩家升级拉远摄像机
(2)创建设置相机数据
(3)循环调用相机移动(实现更随玩家效果),玩家升级时相机拉升Bool值会变为true,相机拉升结束后改为False。相机因为循环实时更随玩家。 -
玩家加分淡入淡出效果**(代码请加Q:1517069595)**
(1)创建设置基本数据
(2)淡入淡出函数(通过时间一定时间后分数淡出,并且分数有一个上升的过程,需要在循环下执行)
原理生成一个数字,由玩家脚本得分了生成数字(数字作为预制体),该数字绑定这个脚本,时间一到数字和脚本都删除。 -
UI脚本**(代码请加Q:1517069595)**
(1)创建设置UI基本数据
(2)淡出背景图(一定时间消失,隐藏)
(3)倒计时的计时器(游戏时间)
(4)判断游戏是否结束,玩家吃东西得分是玩家脚本的CanchangeScores为true,生成得分text(淡出效果,上一个脚本有解释)。设置好位置把CanchangeScores改为false。
(5)设置在玩一局的按钮触发事件(按钮点击触发,重新加载场景)
-
警车巡逻**(代码请加Q:1517069595)**
(1)创建警车预制体,在已有的模型中用Ctrl+D复制几个警车预制体
(2)把警车预制体放在起始点,创建几个空节点将警车放在空节点下以便转向使用。
(3)给空节点添加BoxCollider和Rigidbody组件。并且把Rigidbody组件里的Use Gravity勾选去掉,不使用重力。把Is Kinematic勾选上,不受重力影响。
(4)在Script文件夹下创建一个叫MoveCar的脚本。
(5)打开脚本,编辑脚本。脚本包括:车子向路点移动,车子向下一个点转向,碰到敌人或者玩家等于大于自己时掉落销毁。
(6) -
白色陷阱黑洞制作**(代码请加Q:1517069595)**
(1)拷贝一个玩家或敌人的模型,把模型的颜色修改为白色,创建脚本Trap挂在陷阱的预制体上
(2)白色黑洞要实现自己移动,经过一段时间变大,再经过一段时间消失
(3)白色黑洞不会迟吃掉OBJ但会吃掉敌人和玩家。
(4)脚本如下
UI框架的接入**(代码请加Q:1517069595)**
-
创建一个新场景叫做UIFramework,在场景主界面可以导入一些场景,当做背景,用PS制作一个艺术字图片,贴在背景上面
-
TopBar制作**(代码请加Q:1517069595)**
(1)主界面制作包括,左上角的经验条,记录玩家等级以及经验进度的Slider
(2)右上角制作两个按钮分别是点击看广告的和调整震动的的按钮
(3)右下角制作一个游戏开始按钮,点击会进行异步加载随机三个场景
(4)添加两个按钮做成透明的样子,一上一下,用于做返回按钮
(5)打开UITopBar脚本修改脚本
(6)左上角的slider添加脚本实现异步加载 -
UIMain制作**(代码请加Q:1517069595)**
(1)UIMainPage实现点击出现换肤窗口以及点击出现选择模式窗口
(2)脚本实现 -
UINotice制作**(代码请加Q:1517069595)**
(1)弹窗可以更改是否震动
(2)脚本实现 -
UISkill2关卡模式选择**(代码请加Q:1517069595)**
(1)点击不同按钮实现关卡模式切换,添加按钮触发事件,点击按钮时按钮存下两个值,玩家是否可以复活,本关卡是否生成敌人。数据是通过配置配置出来的。再去读取配置的数据通过按钮事件把数据存下来。玩家以及敌人生成器在start运行时会读取数据判断是否可以复活,以及生成敌人
(2)脚本实现 -
MVC框架利用制作皮肤读取更换**(代码请加Q:1517069595)**
(1)导入MVC框架插件
(2)创建两个脚本SkinPlayer,Datamanger利用MVC框架用来读写数据
(3)Datamanger脚本实现
(4)SkinPlayer脚本实现 -
UISkillPage制作(接上面的MVC框架皮肤读取切换)(代码请加Q:1517069595)
(1)制作弹窗的皮肤
(2)创建一个skinplayer读取皮肤需要从skinplayer的脚本进行读取
(3)第一个和第二个皮肤在刚开始时可以切换,第三个皮肤要达到等级5自动解锁,第四个皮肤需要看广告后解锁
(4)脚本实现
接广告**(代码请加Q:1517069595)**
- 在Unity里面按Ctrl+0,把ADS打开,进行到网页设置界面
- 点击左边的Projects选择要添加广告的项目
3.选择左边的Monetization下面的Placements,记录下显示的ID号用于接广告用 - 脚本实现