0808
视频:21障碍动态管理 道具
-
答疑 开始——00:05:00
- 光照改变场景美观性 微信专栏文章阅读
- 插件 post processing
-
解决工程遗留问题00:05:00——00:21:10
- 发射不准 00:05:00——00:07:30
-
因为由一个小球改为多个球发射后 发射是在发射管理中实现的 之前一个小球时是在单个Ball脚本中 造成的区别是小球发射的方向会不准 因为多个小球的发射点固定在一个地方 而之前单个小球是球本身所在的点的位置与光标位置的差 把transform.position 改成ShootPoint.position
Vector2 _direction = mousePos - ShootPoint.position;
-
除此之外 还要记得将小球的初始速度和角度归零
- 小球在发射上方发生弹跳00:07:30——00:21:10
-
定义两个物理材质类型的变量
public PhysicsMaterial2D NoBounce; public PhysicsMaterial2D Bounce;
-
对应场景里将不同的材质拖入空槽
-
给承接物的墙的collider也设置成没有弹性的 拖入无弹性的物理材质
-
在Ball脚本里 定义一个碰撞类型的变量 用于后面使用
Collider2D _collider2D;
-
在start方法里把小球的碰撞器赋值给这个变量
_collider2D= GetComponent<Collider2D>();
-
在重置小球的方法里 把没有材质的变量赋给小球
GetComponent<Collider2D>().sharedMaterial= NoBounce;
-
写一个发射前的方法 将有弹性的物理材质赋给小球 以便在不同的脚本中调用
public void BeforeShoot() { GetComponent<Collider2D>().sharedMaterial = Bounce; }
-
在BallManager脚本里的发射小球的方法里调用上面的方法 在发射前改变小球的物理材质
child.GetComponent<Ball>().BeforeShoot();
-
障碍物管理00:21:10——01:23:27
动态生成00:21:10
动态生成.png- 一个障碍物的自动创建 00:21:10——00:50:16
-
新建prefab目录 将之前已经可以实现扣血的单个障碍物拖进去 作为预制体 然后用脚本实现动态生成
-
作为预制体后会发现代码里做好的与UI连接的空槽的东西丢失了 这是因为预制体无法获取场景中的资源 它只能获取assets里的资源 手动创建的UI需要重新建立并关联
-
新建ObstacleManager的脚本 并拖给场景中的Obstacles
-
在这个脚本下自定义一个方法 用来自动创建障碍物
public void ObstacleBuild() { }
-
为了建造出障碍物 甚至是更多的障碍物 可以定义一个GameObject类型的数组 这样后面有多少个预制体就拖进去多少个 方便生成多个障碍物 类似模仿手动创建时Obstacle下面的多个sprite子物体 sprite就是一个2D的GameObject 所以用GameObject的类型 数组可以理解为同一类型的集合
public GameObject[] ObstaclePrefabs;
-
为了后面多个障碍物随机生成 先实现随机 可以在ObstacleBuild方法下定义一个随机指数的变量让它可以在0到预制体个数之间随机获取一个序号 然后定义一个prefab变量 可以调取到随机序号所对应的预制体
var index = Random.Range(0, ObstaclePrefabs.Length); var prefab = ObstaclePrefabs[index];
-
在AbstacleBuild();的方法里 用自动生成的语句生成上面所调取的随机的预制体 并用一个变量进行接收以便后续对这个对象的操作
var pf = Instantiate(prefab);
-
还记得手动新建sprite时为了方便管理 把这个circle的sprite放在一个空物体的目录下面 作为子物体 这个Instantiate的工具可以直接设置其生成的目录位置 那么这个新生成的pf的父物体是谁呢 就是所挂代码的transform
var pf = Instantiate(prefab,transform);
-
生成后首要地是控制生成地位置 根据原游戏设计 从最低的那行生成 如何告诉计算机哪里是最低位置呢 根据屏幕二维坐标有X Y轴 最低即 Y轴所对应的数值 这里先定义一个公有类型(方便调整)的浮点型变量(有小数点记得数值带f) 这个变量所赋值的是 小球在游戏场景的位置二维向量的Y值
public float LowestY = -3.1f;
-
在ObstacleBuild();方法下进行设置生成出来的预制体的位置 .position.y只能查看不能改变所以需要转换为一个new Vector2(,)在这里进行改变
pf.transform.position = new Vector2(0, LowestY);
-
在start方法里调用这个自定义方法后并把之前手动创建的circle障碍物隐藏掉
void Start () { ObstacleBuild(); }
-
此时可以按照一定位置生成一个小球了 但小球缺少血量的UI
-
同理 创建 首先将之前的Text也变成预制体 作为预制体是为了自动生成而不是手动放上去
-
在ObstacleManager中定义一个公有类型的GameObject 用来放入生成的Text预制体 场景中拖入空槽
public GameObject TextPrefab;
-
然后在脚本中定义一个自定义的方法 用来自动创建Text 这时就不是一个公有类型的自定义方法了 而是一个GameObject类型 因为这个方法会返回成一个游戏对象 便于调取相关工具包 实现跨代码绑定的功能
GameObject TextBuild() { }
-
还记得手动创建Text时 会自动带一个Canvas 不仅是为了方便管理更是为了Text可以显示在游戏界面当中 同创建之前的circle障碍物一样 在这个 TextBuild();里Instantiate方法下也要写上父物体
-
不同于上面的pf的父物体可以直接找到transform 这里需要定义一个Transform类型的公有变量 把目录上的Canvas拖进来
public Transform CanvasParent;
-
接下来就可以在自定义的TextBuild();方法里进行自动创建Text了
GameObject TextBuild() { var ui = Instantiate(TextPrefab,CanvasParent); return ui; }
-
返回成GameObject类型的方法是为了后面与前面自动创建的pf进行关联
-
前面已经提到过 之前单个障碍物会自带一个之前写好的Abstacle的脚本 但由于变成预制体后HpText会丢失 也就是脚本无法关联到UI 所以自动创建好UI后 需要将自动生成的障碍物和UI重新建立关联
-
我们回到Obstacle脚本中进行初始化 目的是与ObatacleMananger脚本建立关联 在Obstacle脚本中自定义一个初始化的方法
public void Initialize() { }
-
回到AbstacleManager脚本建立关联 先在AbstacleBuild();的自定义方法里调取前面写好的TextBuild方法 自动创建一个预制体一样的Text 因为这个方法是一个GameObject类型 并且返回值就是ui 可以将变量名再次命名为ui 因为变量内部的东西是一样的 相当于共用一个变量名
var ui = TextBuild();
-
此时可以获取到新创建的pf的Obstacle的脚本调用初始化的脚本 并把这里的ui传给初始化 让初始化可以使用这个ui
pf.GetComponent<Obstacle>().Initialize(ui);
-
在Obstacle脚本中的初始化方法中传入GameObject类型的 ui变量 相当于给当前这个新生成的Obstacle分配了一个ui 可以进行使用了
public void Initialize(GameObject ui){ }
-
获取到ui的text组件并赋值给前面丢失了场景资源的 TextHp
TextHp = ui.GetComponent<Text>();
-
接下来进行更新ui的位置 首先 将新生成的小球的的位置转化为屏幕空间
var pos = Camera.main.WorldToScreenPoint(transform.position);
-
此时就可以将新生成的障碍物的位置与ui的位置进行匹配
TextHp.transform.position = pos;
- 重新梳理逻辑00:46:22——00:50:16
- 生成多个障碍物 00:50:16——01:23:27
-
多个障碍物的动态生成是基于前面一个障碍物的动态生成的 首先需要知道界面最下面一排 最多可以摆放几个障碍物 这边根据障碍物自身的大小可以生成5个 手动创建好找每个障碍物坐标点的规律 发现x轴成一定的间隔排列 这里是0.95的公差
-
在ObstacleManager脚本中 定义一个公有 浮点型的数组用来存放每个障碍物X轴上的数值
public float[] HorizontalPos= { 0,0.95f,1.9f,-0,95f,-1.9f};
-
在ObstacleBuild();方法里写一个foreach循环 它和for循环的区别就是 可以依次在数组中抽取一个序号 参数相对for循环更简单
foreach (var item in HorizontalPos) { }
-
把之前的语句剪切到这个循环里 就实现了在最下面一行生成多个障碍物
foreach (var item in HorizontalPos) { var index = Random.Range(0, ObstaclePrefabs.Length); var prefab = ObstaclePrefabs[index]; var pf = Instantiate(prefab, transform); pf.transform.position = new Vector2(0, LowestY); var ui = TextBuild(); pf.GetComponent<Obstacle>().Initialize(ui); }
-
记得把对x轴数值的数组里的item赋给新生成障碍物的二维向量的X坐标
pf.transform.position = new Vector2(item, LowestY);
-
虽然实现了多个障碍物的动态生成 但可玩性还不够 可以将其他形状的sprite添加Abstacle的脚本组件 作为预制体拖入到存放预制体的数组里
-
为了每次生成的个数随机 这里运用概率的一些知识来实现随机性 可以通过一个只有n种可能性的结果事件 每个结果发生的可能性为n分之一 用一个if条件语句 假设其中一种结果发生就中止继续进行再次循环一次
var random = Random.Range(0, 5); if (random == 0) continue;
- 障碍物上移
1.在Ball脚本里的ReSetPos();方法中可以知道一局是否结束
-
给小球定义一个bool类型的变量 用来判断 小球是否在弹跳状态 bool默认为false
public bool isBouncing;
-
在ReSetPos();方法里把上面的bool设为false 相当于做了一个标记
isBouncing = false;
-
在BeforeShoot();方法里把上面的bool设为true 同理是一个标记
isBouncing = true;
-
在BallManager的脚本中新建一个bool类型的方法用来检测是否所有的小球都已经回收
public bool CheckIfAllBallsReset() { }
-
在Ball方法里 每次重置一个小球时就获取到BallsManager的脚本中的检测是否所有的小球都回来的脚本组件 单个小球和总的小球是父子的关系 可以进行调用
transform.GetComponentInParent<BallManager>().CheckIfAllBallsReset();
-
接下来去完善CheckIfAllBallsReset();方法里的内容 用foreach的方法循环查找BallManager 所挂代码的物体的子物体的Ball代码里的isBouncing的状态来检查是否都已经全部回收 如果isBouncing为true 说明小球还在弹跳 将这个方法返回false 否则 返回true 注意 foreach的条件中 要显著标识一下类型为Transform 因为默认为GameObject类型
foreach (Transform child in transform) { var ball = child.GetComponent<Ball>(); if (ball.isBouncing == true) return false; } return true;
-
小球的父子已经建立了联系 如何将小球和障碍物建立联系呢 因为当检查到所有小球都重置位置后的目的是通知到障碍物需要上移 因此在BallManager中再新建一个方法OnBallBack();并将上面的CheckIfAllBallsReset();的方法嵌入 在Ball的脚本中只用调用这个新的OnBallBack();的方法就可以实现之前的检查功能 并且还可以通过这个新的方法连接到障碍物
public void OnBallsBack() { var allballs = CheckIfAllBallsReset(); }
-
在Ball脚本里更改代码
transform.GetComponentInParent<BallManager>().OnBallsBack();
-
在OnBallsBack();的方法里 用一个if条件语句去做一个判断 当allballs 的变量为true时代表所有的小球都已经回收 那么就可以通知障碍物上移了 这里 首先要将ObstacleManager脚本变成静态成员 因为它们不属于一个类型 不像之前有父子关系 可以直接被访问到 其次就是在ObstacleManager的脚本里新建一个方法实现所剩的障碍物进行上移
public static ObstacleManager Instance;
-
在ObstacleManager脚本的start方法里将this赋值Instance 说明这个静态成员就是它本身 这样在其他脚本里就可以通过类名+属性进行索引 用“.”进行遍历
Instance = this;
-
在ObstacleManager的脚本里新建一个方法实现所剩的障碍物进行上移动
public void ObstaclesUp(){ }
-
在方法中用foreach的语句遍历所挂代码的物体的子物体
foreach (Transform item in transform) { }
-
通过调取item的位置并加等于一个三维向量 给y轴赋值为1
item.position += new Vector3(0, 1f, 0);
-
障碍物已经可以实现上升了 但在何时调取呢 在BallManager的类中的OnBallsBack()方法下调用
public void OnBallsBack() { var allballs = CheckIfAllBallsReset(); if (allballs == true) { //通知障碍物升高一层 ObstacleManager.Instance.ObstaclesUp(); } }
-
除了上移一行之外最底下的需要新生成一行障碍物 此时在ObstacleManager 的类中的ObstaclesUp();方法下加上新生成的方法的语句
public void ObstaclesUp() { foreach (Transform item in transform) { item.position += new Vector3(0, 1f, 0); } ObstacleBuild(); }
-
此时运行时会发现上移的障碍物的UI不见了 这是因为在上升完障碍物后没有更新ui 可以新建一个更新UI的方法在Obstacle的类里 在ObstacleManager类中的ObstacleUp的方法里的foreach循环里直接获取到子物体的代码中的更新UI的方法 从而最终实现UI的更新
public void ObstaclesUp() { foreach (Transform item in transform) { item.position += new Vector3(0, 1f, 0); item.GetComponent<Obstacle>().UpdateUI(); } ObstacleBuild(); }
-
因为还没有在Obstacle的类中新建这个新的方法 所以会报错 光标移上去会有一个解决方案 会自动生成一个新的方法
internal void UpdateUI() { }
-
直接复制之前跟新ui的语句放在这个方法里 在复制粘贴的同时就可以将上面的语句替换成这个新建立的方法
public void Initialize(GameObject ui) { TextHp = ui.GetComponent<Text>(); UpdateUI(); } internal void UpdateUI() { var pos = Camera.main.WorldToScreenPoint(transform.position); TextHp.transform.position = pos; }
-
此时会报一个错误 这是因为ObstacleManager所挂的物体下还有一些隐藏掉的物体 在直接调取它的子物体时会每个都遍历 遇到隐藏的会检测到空物体 要么在层级面板中删掉多余的物体 要么通过代码给一个判断
-
在ObstacleManager类下的ObstaclesUp();方法下写一个条件 只有它的子物体不为空的时候才进行调用更新UI
public void ObstaclesUp() { foreach (Transform item in transform) { item.position += new Vector3(0, 1f, 0); var obstacle= item.GetComponent<Obstacle>(); if(obstacle != null) obstacle.UpdateUI(); } ObstacleBuild(); }
? HpText数值随机
?为什么在foreach循环中的条件里为了循环所挂代码物体的子物体时不能用var作为类型 必须用Transform类型 除了因为在下面获取子物体的脚本组件时编译器会报错 更深层次的原因是什么
?为什么 (ball.isBouncing == true) 以及if (allballs == true) 中的== true可以省略掉 除了是因为整洁以外 还有其他的什么原因呢 isBouncing 和 allballs作为条件时默认是真吗省 略后没有影响吗
?与障碍物绑定的ui没有消失
答:因为有几个障碍物的预制体被隐藏掉了。
答疑01:23:27——01:26:05
道具01:26:05——01:43:26
道具.png- 手动创建分裂球
-
新建一个sprite拖到层级面板中 并加上collider 勾选上is trigger 因为是道具
-
先设置手动生成这个分裂球 之后动态生成同理其他障碍物 给这个sprite建立一个可以产生效果的脚本 在脚本中实现分裂的效果
-
在脚本中通过OnTriggerEnter2D的方法去实现碰撞后的效果
-
分裂一个球的意思就是再生成一个球 可以借助道具碰到的这个球来生成一个新的球
private void OnTriggerEnter2D(Collider2D collision) { var oldball = collision.gameObject; Instantiate(oldball, collision.transform.parent); Destroy(gameObject); }
- 手动创建双倍球
-
除了被碰到的小球面积变大以外 威力也增大
-
同分裂球一样 新建sprite 脚本中OnTriggerEnter2D中进行编辑 改变scale
private void OnTriggerEnter2D(Collider2D collision) { collision.transform.localScale *= 1.25f; }
-
于此同时 这个道具球消失
Destroy(gameObject);
-
此时在Ball类里定义一个伤害值
public int Damage=1;
-
回到ItemDouble的方法里在碰撞的方法里改变Damage的值
collision.GetComponent<Ball>().Damage = 2;
-
将Damage应用到相关掉血的代码里 在Obstacle的类里 将原来的Hp-=1;的语句和Damage联系起来
var ball = collision.gameObject.GetComponent<Ball>().Damage; Hp -= ball;
?双倍球碰到双倍球应该怎么办
网友评论