机关网站建设需求文档涪陵网站制作
news/
2025/9/27 14:58:05/
文章来源:
机关网站建设需求文档,涪陵网站制作,广东嵘通建设,Wordpress表单无法收到文章目录 【Unity】终极移动指南-注解#xff08;从移动、抓钩到贪吃蛇#xff09;观前提醒链接地址#xff1a; 内容一、 transform移动操作【1】transform.position变换位置【2】transform.Translate平移【3】transform.position 类似平移的操作【4】定向矢量【5】停在指定… 文章目录 【Unity】终极移动指南-注解从移动、抓钩到贪吃蛇观前提醒链接地址 内容一、 transform移动操作【1】transform.position变换位置【2】transform.Translate平移【3】transform.position 类似平移的操作【4】定向矢量【5】停在指定位置未旋转与已旋转的游戏对象不同操作下的区别 二、移动距离、方向和目的地【1】查找两个对象之间的距离【2】找到两个物体之间的方向【3】使用 MoveTowards 移动到目的地 三、 Lerp【1】使用 Mathf Lerp 移动【2】Lerp 缓入【3】 Lerp 缓出【4】Lerp 反弹 四、Rigidbody控制移动操作【1】如何使用键盘移动【2】使用 AddForce 移动刚体【3】使用AddForce 跳跃【4】恒定速度移动【5】使用 MovePosition 移动刚体 五、运动学平台【1】unity操作【2】Transform的写法【3】刚体的写法总结Rigidbody和transform的不同 六、四元数【1】使用四元数欧拉旋转【2】使用 LookRotation 旋转查看移动对象【3】使用 FromToRotation 旋转查看移动对象【4】使用四元数 AngleAxis 旋转【5】 RotateAround 围绕另一个对象旋转 七、Camera**复刻简单的cinemachine插件**【1】相机跟随玩家【2】使用 SmoothDamp 顺畅地跟随带有相机的玩家【3】设置相机边界 八、子弹发射与弹道轨迹射线提示【1】简单的子弹发射【2】使用物理模拟脚本的弹道轨迹【3】按键控制子弹发射Debug的过程【4】具有独立物理场景的射弹轨迹让子弹变得像弹夹一样【5】为对象添加弹性化为无情的网球发射机【6】带点的射弹轨迹线(03:56)【7】物体处的射弹轨迹停止线【8】给子弹添加渲染【9】弹道长度和射弹速度【10】用鼠标改变和长度速度 九、磁铁/抓钩【1】射出磁铁【2】取回磁铁【3】用磁铁拉住另一个物体【4】创建抓钩 十、贪吃蛇【1】拾起范围内的物体【2】跟随物体蛇形运动【3】吃东西时跟随物体蛇运动并长出蛇【4】使用电影机cinemachine跟踪蛇头【5】使用带边界的电影机跟踪对象【6】贪吃蛇游戏空格增加移动速度 【Unity】终极移动指南-注解从移动、抓钩到贪吃蛇
观前提醒
链接地址
https://www.bilibili.com/video/BV15L411y7a3
https://www.youtube.com/watch?vOHJS44fIDCU
完整项目|https://github.com/MemoryLeakHub/MovementUnity
感悟
为什么我打开项目什么都没有啊(¬д¬。) 弄这个项目时因为视频是机翻的看得很难受每次出现bug我就不得不去猜它unity的操作猜我是拆的不对还是项目本身的问题但是所幸我在看了很多文档ChatGpt的帮助这些问题都解决了。 内容
一、 transform移动操作 假设我们自己定好了一名游戏对象并搭建好了场地。 之后配上该有的组件已经设置好初始位置Position还有重力和精灵图等等而本章所涉及到的代码都在BoxMovement.cs脚本操作现在就让我们在unity中跟随着摄像机镜头一起学习unity中有关于移动的操作。 具体如下 【1】transform.position变换位置 transform.position表示当前物体的位置包含三维坐标轴因此我们下面这段脚本的操作其实就是修改X坐标轴出现的位置而由于我们目前在2D项目中操作所以就可以直接修改前两个值即可而不用管Z轴。 void Update(){float nowX 22.5f;transform.position new Vector3(10, transform.position.y);
}transform.position.y就表示当前y轴的值只会跟跟随当前游戏对象的受力发生改变因为我加了刚体组件而不同于X轴已经修改了值。
【2】transform.Translate平移 transform.Translate(X, 0, 0);表示只修改X轴上的移动X是指定的移动量。 void Update(){float X 0.01f;transform.Translate(X, 0, 0);
}它是以每秒移动0.01单位进行操作的也即transform.Translate(X/1.0f, 0, 0)只为方便理解秒不同于帧帧是每秒的渲染帧数比如我想游戏以60FPS运行就意味着每秒渲染60帧。 【3】transform.position 类似平移的操作 将当前物体沿X轴方向移动但这个移动是与时间Time.deltaTime有关的 则将当前物体的位置与右边的向量相加从而改变了物体的位置。 public float X 0.1f;
void Update(){transform.position new Vector3(X * Time.deltaTime, 0);
}该脚本就意味着我们目前是以每帧移动0.1个单位因此你会发现它比之前的transform.Translate(X, 0, 0);移动缓慢。 【4】定向矢量 它是【3】的简短写法Vector3.right 是一个单位向量表示X轴的正方向也就是(1, 0, 0)会让每帧下的物体位置向右移动而移动的速度由 X 控制并且移动速度会在不同的帧率下保持一致。 public float X 0.1f;
void Update(){transform.position Vector3.right * X * Time.deltaTime;
}Unity中移动涉及到的方向向量主要是单位向量表示在三维空间中的各种方向。
方向向量向量值描述Vector3.forward(0, 0, 1)物体正面Z轴正方向的单位向量Vector3.back(0, 0, -1)物体背面Z轴负方向的单位向量Vector3.right(1, 0, 0)物体右侧X轴正方向的单位向量Vector3.left(-1, 0, 0)物体左侧X轴负方向的单位向量Vector3.up(0, 1, 0)物体上方Y轴正方向的单位向量Vector3.down(0, -1, 0)物体下方Y轴负方向的单位向量Vector3(1.0f, 1.0f, 1.0f).normalized(1, 1, 1).normalized对角线上方向的单位向量
需要注意的是这些向量通常需要标准化normalized以确保它们的长度为1这样它们才是单位向量。
【5】停在指定位置未旋转与已旋转的游戏对象不同操作下的区别 根据前面所学的知识可知稍微改进一下【2】、【3】、【4】的内容就可以实现而StopX就是我们要停下来的最终X轴位置。 public float X 0.1f;
void Update(){float StopX 22.0f;if(transform.position.x StopX) return;transform.position Vector3.right * X * Time.deltaTime;//[4]//transform.position new Vector3(X * Time.deltaTime);//[3]//transform.Translate(Vector3.right * X * Time.deltaTime);//[2]
}结论(在游戏对象没有刚体组件的情况下) 如果是移动旋转后的游戏对象[3]和[4]的移动操作不会影响它已经旋转的结果它最终仍会移动到指定的X轴和Y轴位置。 但是[2]的平移X轴的移动操作则会考虑它已经旋转的问题到达终点Y轴的位置会发生变化与[3]和[4]的操作结果不同而X轴的值仍旧保持不变。 这就是平移与改变位置的操作的不同。
二、移动距离、方向和目的地
【1】查找两个对象之间的距离 可以通过简单的双方减去矢量的位置就能计算相互之间的距离了。 public class BoxMovement : MonoBehaviour
{public float X 0.1f;private GameObject redBox;private void Start(){// 移除GameObject的类型声明redBox GameObject.Find(redBox);}void Update(){if (redBox ! null) // 检查redBox是否被正确找到{Vector3 heading redBox.transform.position - transform.position;var distance heading.magnitude;Debug.Log(distance);//显示距离transform.position Vector3.right * X * Time.deltaTime;}else{Debug.LogError(redBox not found!); // 如果找不到redBox记录错误消息}}
}①获取游戏对象的两种方式
使用GameObject.Find(GameObject);注意私有变量表示内部重新声明了这就会导致无法在Update()种访问该变量因此要按如上脚本的写法添加对游戏对象判断是否为空的检查否则unity将会引发异常。公开引用或者序列化操作。
②Vector3.magnitude是什么
一个用于计算三维向量长度模的属性即数学公式 ( x 2 y 2 z 2 ) \sqrt(x^2y^2z^2) ( x2y2z2)
【2】找到两个物体之间的方向 在一中【4】我们就讲过向量的用法通过对**二中【1】**的改造获得两个游戏对象的向量然后归一化就可以得到两个物体之间的方向。 Vector3 heading redBox.transform.position - transform.position;
var distance heading.magnitude;
var direction heading.normalized;
transform.Translate(direction * X * Time.deltaTime);后续如果需要改变最终目的地redBox的Y轴位置则只需修改heading.y的值完全看自己的需求。
而上面的X值也可以进行修改变为速度 s p e e d d i s t a n c e t o t a l t i m e speed\frac{distance}{totaltime} speedtotaltimedistance控制物体以特定的速度到达目的地也即我们希望通过修改 totaltime \text{totaltime} totaltime在特定时间内移动到目的地。
【3】使用 MoveTowards 移动到目的地 使用Vector2.MoveTowards函数逐渐将物体从当前位置移动到target位置每一帧移动的距离由step决定以实现平滑移动的效果同时没必要对是否到达目的地这个条件进行判断了。 var speed 2;
var step speed * Time.deltaTime;
var target redBox.transform.position;
transform.position Vector2.MoveTowards(transform.position, target, step);在视频当中作者使用Vector3.Distance计算两个Vector3类型的点之间的距离。它的用法如下
float distance Vector3.Distance(pointA, pointB);因此后续补上用来停止作者自己写的计时器另外距离的单位通常与你的游戏场景的单位一致例如米或厘米这取决于你的游戏设置。
if(Vector3.Distance(transform.position,target)0.001f) return;三、 Lerp
box带有刚体2DbodyType为动态重力0、碰撞体2D、sprite Render组件
redBox带有刚体2DbodyType为静态重力0、碰撞体2D、sprite Render组件
【1】使用 Mathf Lerp 移动 通过Mathf.Lerp线性插值返回在给定的时间 timeElapsed/totalTime 插值因子t从起始值 this.transform.position.x 到目标值 target.x 之间的一个新值 x。 public GameObject redBox;private float timeElapsed 0.0f;private void Update(){timeElapsed Time.deltaTime;var target redBox.transform.position;var totalTime 1.5f;var time timeElapsed / totalTime;var boxStartPosition this.transform.position;this.transform.position Vector3.Lerp(boxStartPosition, target, time);if (this.transform.position.x redBox.transform.position.x){return;}}Mathf.Lerp有以下用途 平滑移动 Mathf.Lerp来平滑地移动物体或相机从一个位置到另一个位置而不是瞬间跳转。 颜色渐变 Mathf.Lerp在两种颜色之间进行插值以创建颜色渐变效果。 动画 在动画制作中Mathf.Lerp可用于插值关键帧之间的值以创建流畅的动画效果如移动、旋转和缩放。 过渡效果 用于创建过渡效果例如淡入淡出效果或过渡画面之间的混合效果。
注意插值因子t通常在0到1之间表示从起始值到结束值的插值程度。例如t为0.5表示取起始值和结束值的中间值。
可以用var timeElapsed Time.deltaTime;更好地表示已经过去时间这个含义。
【2】Lerp 缓入 使用Vector3.Lerp函数根据缓动函数 EaseIn 的返回值逐渐将当前游戏对象的位置向 target 移动。Lerp函数通过插值将两个位置之间的中间值计算出来根据时间的增加游戏对象的位置会逐渐靠近目标位置具有缓动效果。 private float EaseIn(float k) 是一个自定义的缓动函数使用了一个简单的缓动函数 k * k * k它实现了一种缓慢开始的效果使物体开始时移动速度较慢然后逐渐加速。 private void Update(){timeElapsed Time.deltaTime;var target redBox.transform.position;var totalTime 1.5f;var time timeElapsed / totalTime;var boxStartPosition this.transform.position;this.transform.position Vector3.Lerp(boxStartPosition, target, EaseIn(time));if (this.transform.position.x redBox.transform.position.x){return;}
}private float EaseIn(float k){return k * k * k;
}【3】 Lerp 缓出 这个函数接受一个时间参数 k值在范围 [0, 1] 内表示经过的时间占总时间的比例。函数返回一个经过缓动计算的值该值会在输入时间 k 接近1时迅速增加以实现缓慢结束的效果。 private float EaseOut(float k){return 1f((k - 1f)*k*k);
}优先级拆分
k - 1f首先将 k 减去1这是为了将时间范围从 [0, 1] 映射到 [-1, 0]。k * k * k然后求立方。1f ...最后将上述计算的结果与1相加以将值的范围映射回 [0, 1]并使得在输入时间 k 靠近1时值迅速增加从而实现了缓慢结束的效果。
【4】Lerp 反弹 看自己怎么写的吧下面这里就示范模拟不同阶段造成不同程度的反弹效果也要考虑Lerp里面的插值因子t的值大小这真的太TM难调了在X轴方向上很难看到效果 在Y轴方向上因为加入了重力的影响所以会有一个细微的不断反弹然后最后平稳至固定位置。 下面的效果还是固定time变量值的情况如果考虑到timeElapsed不断增大那么最终box物体就能固定到redBox位置总之要自行调好time的大小不然看不到效果 private float BounceIn (float k) {return 1f - BounceOut(1f - k);}private float BounceOut (float k) { if (k (1f/2.75f)) {return 7.5625f*k*k; }else if (k (2f/2.75f)) {return 7.5625f*(k - (1.5f/2.75f))*k 0.75f;}else if (k (2.5f/2.75f)) {return 7.5625f *(k - (2.25f/2.75f))*k 0.9375f;}else {return 7.5625f*(k - (2.625f/2.75f))*k 0.984375f;}}private float BounceInOut (float k) {if (k 0.5f) return BounceIn(k*2f)*0.5f;return BounceOut(k*2f - 1f)*0.5f 0.5f;}四、Rigidbody控制移动操作
【1】如何使用键盘移动 使用Input.GetAxis来获取玩家输入的水平和垂直轴上的值。通常这对应于键盘上的方向键左右和上下箭头或游戏手柄的摇杆输入。 Vector2 movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));
transform.Translate(movementDirection * 2 *Time.deltaTime);【2】使用 AddForce 移动刚体 通过刚体组件实现物体的一个任意方向上的移动。 注意如果不能实现移动就检查刚体组件是否需要加物理材质。
public class BoxMovement : MonoBehaviour
{private Rigidbody2D rb;private void Start(){rb GetComponentRigidbody2D();}void Update(){var speed 10f;Vector2 movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));rb.AddForce(movementDirection * speed);}
}【3】使用AddForce 跳跃 检测玩家是否按下了上箭头键Up Arrow key如果按下了就给物体应用一个向上的冲量来使其跳跃。 ForceMode2D.Impulse 是一个枚举值表示应用冲量的方式。在这里使用Impulse模式表示应用一个瞬时的冲量即瞬间增加速度。 if (Input.GetKeyDown(KeyCode.UpArrow)){var amount 6f;rb.AddForce(Vector2.up * amount, ForceMode2D.Impulse);
}【4】恒定速度移动 rb.velocity表示物体的速度即它以恒定的速度移动下面操作不同于AddForce有一个施加力物体会立即改变速度没有物理模拟效果。 var speed 10f;
Vector2 movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));
rb.velocity movementDirection * speed;【5】使用 MovePosition 移动刚体 MovePosition会直接改变位置因此就通过当前帧的增量做到移动的效果但是它改变的非常平滑直接到某个位置没有看到一个渐变的效果这是它不同于AddForce的地方。 var speed 10f;
Vector2 movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));
rb.MovePosition(
(Vector2)transform.position
(movementDirection * speed * Time.deltaTime));区别MovePositionAddForce移动方式修改位置这方面是非物理特性的其他方面不改变施加力改变速度和位置适用场景运动学平台移动、摄像机移动等等物体爆炸效果、角色加速等平滑性高无速度渐变低模拟惯性
五、运动学平台
【1】unity操作
给我们红色平台加入运动学BodyType 自己引用开始的位置和结束的位置也可以自己设置数值 不同Body Type的适用范围 Dynamic动态刚体 bodyType设置为Dynamic时刚体会受到物理引擎的模拟包括重力、碰撞、力和速度等。 动态刚体会自由地响应外部力例如重力、施加的力等并根据物理规则进行模拟可以在物理世界中产生真实的动力学行为。 适用于需要物理模拟的物体如角色、子弹、球体等。这些物体可以受到外力的影响并与其他物体发生碰撞。 Kinematic运动学刚体 bodyType设置为Kinematic时刚体不受物理引擎的力的影响角色对象就推不动但仍然可以与其他物体发生碰撞。 运动学刚体通常由开发者手动控制可以通过脚本来移动但不会受到物理引擎的力和重力的影响。 适用于需要精确控制位置和移动的物体如平台、门、电梯等。它们可以与其他物体交互但不会受到外部力的干扰。 Static静态刚体 bodyType设置为Static时刚体被视为静态不会受到任何外部力的影响也不会移动。 静态刚体通常用于固定的物体如墙壁、地板、建筑等。它们不会响应物理引擎的模拟不参与碰撞响应只是作为静态的环境元素存在。
【2】Transform的写法 Mathf.PingPng(t,length);实现循环效果t参数代表一个随时间变化的值而length代表循环的周期。 public class redPlatform : MonoBehaviour
{public Transform startPosition;public Transform endPosition;public float speed 3f;private void Update(){float time Mathf.PingPong(Time.time * speed, 1);Vector3 position Vector3.Lerp(startPosition.position, endPosition.position, time);transform.position position;}
}【3】刚体的写法
上面的【2】和这里的【3】的效果是一致的。 public class redPlatform : MonoBehaviour
{public Transform startPosition;public Transform endPosition;public float speed 3f;private Rigidbody2D rb;private void Start(){rb GetComponentRigidbody2D();}private void Update(){float time Mathf.PingPong(Time.time * speed, 1);Vector3 position Vector3.Lerp(startPosition.position, endPosition.position, time);rb.MovePosition(position);}
}总结Rigidbody和transform的不同
Rigidbody移动 物理模拟当使用刚体移动时物体会受到物理引擎的模拟包括重力、碰撞和其他力的影响同时它也会带有着这些物理性质与周围的环境交互。 碰撞检测刚体会自动进行碰撞检测和响应避免物体穿越其他物体。 物理引擎刚体移动是由物理引擎控制的通常用于需要物理交互和模拟的情况如角色控制、物体受力推动等。
Transform移动
非物理模拟直接改变Transform的位置是非物理性的移动方式物体会立即到达新的位置不受物理引擎的影响。 无碰撞检测Transform移动不会自动进行碰撞检测和响应这意味着物体可以穿越其他物体可能导致不自然的现象。 控制简单Transform移动更容易控制可以用于一些简单的情况如UI元素移动、相机跟随、物体的初始位置设置等。
防止混淆
MovePosition和velocity它们类似于Transform组件的移动但也有物理特性。
身边趣闻 人类的本质就是装逼和抄袭我经常听到某群的大佬们说Unity自带的物理效果太烂了不如自己造轮子然后他们中就有人就直接拿蔚蓝的开源代码研究有没有物理特性了。 ( Д)b (ω)b (o^-)b (*▽)d 六、四元数
本章总结 都是在讲轴向量与欧拉角不同的方式实现旋转这个目标。 【1】使用四元数欧拉旋转
2D旋转的原理欧拉角实现改变局部坐标轴的Z轴 Quaternion.Euler方法来创建一个绕着Z轴旋转degrees度的四元数。Vector3.forward代表了世界坐标系中的Z轴方向因此Vector3.forward * degrees表示绕着Z轴旋转指定角度的旋转。 transform.rotation ...将计算出的四元数赋值给物体的旋转属性 void Update(){var degrees 30;transform.rotation Quaternion.Euler(Vector3.forward * degrees);
}效果如下 简单了解四元数
四元数是一种复数扩展到三维空间的数学概念用于表示旋转可以避免万向锁问题Gimbal Lock
它由一个标量实部和一个三维向量虚部组成可以表示为q s xi yj zk其中s是标量部分x、y、z是虚部分。
更多的内容请看这篇文章四元数-欧拉角-万向锁 - 知乎 (zhihu.com)
【2】使用 LookRotation 旋转查看移动对象 vectorToTarget计算的是从当前物体到redBox的位置向量 rotateVectorToTarget旋转了 vectorToTarget 向量使其旋转90度也就是微调会使得transform的值跟不微调的值不一样但是最后由于用了LookRotation函数所以看到的效果是一致。 Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget) 创建了一个四元数该四元数会使当前物体的X轴指向 rotateVectorToTarget使物体朝向 redBox 位置。 总结box朝向另一个物体 redBox 的位置但是朝向时会旋转90度。
public class BoxMovement : MonoBehaviour
{public GameObject redBox;void Update(){Vector3 vectorToTarget redBox.transform.position - transform.position;Vector3 rotateVectorToTarget Quaternion.Euler(0, 0, 90) * vectorToTarget;transform.rotation Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget);}
}效果如下 再次复述一遍原理在这个2D场景中我们一个全局坐标轴是不会发生任何改变的改变的是局部坐标轴即Transform的Z轴值会发生变化从而模拟出一个物体旋转的效果下图显示的是全局(没显示的话就点击游戏对象后按W键) 想理解更多原理就请看以下两篇
理解Quaternion.LookRotation()_quaternion.lookrotation(,)_keneyr的博客-CSDN博客
https://devpress.csdn.net/game/6462fb0b6618ef1144e308d8.html
【3】使用 FromToRotation 旋转查看移动对象 我看到的效果跟上面【2】的例子一致局部坐标轴的改动也是类似一开始我想不明白为什么unity要多次一举他们到底有什么地方不同后面看了一下VS的文档注释。 void Update(){Vector3 vectorToTarget redBox.transform.position - transform.position;transform.rotation Quaternion.FromToRotation(Vector3.right, vectorToTarget);
}根据Unity官方文档Quaternion.FromToRotation 和 Quaternion.LookRotation 都用于设置一个四元数来旋转一个物体以使其朝向目标方向但它们的用途略有不同 Quaternion.FromToRotation Quaternion.FromToRotation 用于创建一个将一个向量从一个方向旋转到另一个方向的四元数。你需要提供两个向量作为参数分别是原始方向和目标方向。这个函数不考虑物体的当前朝向它只计算从一个方向旋转到另一个方向所需的旋转。这意味着它通常用于在不考虑当前旋转的情况下将对象朝向某个方向。 Quaternion.LookRotation Quaternion.LookRotation 用于创建一个四元数将物体的前方通常是Z轴指向目标方向。你需要提供两个参数一个用于指定物体的上方方向通常是Y轴另一个用于指定目标方向2D项目我们并不需要改动Y轴这个函数考虑了物体当前的旋转以确保物体的上方与指定的方向保持一致同时前方指向目标方向。这通常用于将对象朝向某个目标但保持物体当前的上方方向。
结论 在unity的显示当中它们的局部坐标轴的变化效果都是一致都是旋转Z轴令X轴指向移动的目标两个不同的函数区别在于改变Z轴旋转的方式是不一样的因此你就会发现transform.rotation当中的Z轴值完全不一致。 【4】使用四元数 AngleAxis 旋转 通过轴向量实现旋转不同于欧拉角方式不一样结果跟【1】一致。 void Update(){var degree 30;transform.rotation Quaternion.AngleAxis(degree, Vector3.forward);
}【5】 RotateAround 围绕另一个对象旋转 Transform.RotateAround 是Unity中的一个方法用于围绕指定点执行旋转。 point旋转的中心点即物体绕其旋转的点的坐标。 axis旋转轴指定物体绕哪个轴进行旋转。 angle旋转的角度以度为单位表示物体将围绕旋转轴旋转的角度。 void Update(){var rotationSpeed 30;transform.RotateAround(blueBox.transform.position,Vector3.forward, rotationSpeed * Time.deltaTime);
}效果如下3D观察不会强制改变某个轴的指向另一个物体 RotateAround 顺时针旋转
transform.RotateAround(blueBox.transform.position,Vector3.back, rotationSpeed * Time.deltaTime);举一反三 如果想要完成实现box单方面去面向blueBox就参考【2】提前微调即可。 问题①那么两个旋转的游戏对象怎么相互注视 提示Vector3.back和LookRotation这两个怎么用 问题②怎么制作一个小型太阳系 其实不就是多个不同星球绕着同一个太阳嘛速度和距离不一样罢了。 七、Camera复刻简单的cinemachine插件
【1】相机跟随玩家 其实就是修改摄像机的position属性另外注意Z值不要只看视频瞎写成0。 public class Box : MonoBehaviour
{private Rigidbody2D boxPhysicsRb;private Vector2 movementDirection;public GameObject camera;private void Start(){boxPhysicsRb GetComponentRigidbody2D();}private void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));var speed 10f;boxPhysicsRb.MovePosition((Vector2)this.transform.position (movementDirection * speed * Time.deltaTime));camera.transform.position new Vector3(this.transform.position.x,this.transform.position.y, -4);}
}【2】使用 SmoothDamp 顺畅地跟随带有相机的玩家
Vector3.SmoothDamp 是Unity中的一个函数用于平滑地将一个向量从一个位置移动到另一个位置。它经常用于摄像机跟随、平滑移动物体等场景以避免突兀的移动效果。以下是 Vector3.SmoothDamp 的基本用法和参数解释
Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed Mathf.Infinity, float deltaTime Time.deltaTime);current当前位置即从哪里开始移动。 target目标位置即要移动到的位置。 currentVelocity引用参数用于存储当前速度。通常你需要在外部定义一个变量来存储速度然后将其传递给 SmoothDamp 函数以便函数在每帧中更新速度。 smoothTime平滑时间表示从 current 移动到 target 所需的时间。较小的值会导致更快的平滑移动较大的值会导致更慢的平滑移动。 maxSpeed可选最大速度限制以确保移动不会太快。默认情况下它被设置为正无穷大可以根据需要进行调整。 deltaTime可选每帧的时间间隔。通常你可以使用 Time.deltaTime 作为这个参数以便平滑效果与帧率无关。
public class Box : MonoBehaviour
{private Rigidbody2D boxPhysicsRb;private Vector2 movementDirection;public GameObject camera;private void Start(){boxPhysicsRb GetComponentRigidbody2D();}private void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));var speed 10f;boxPhysicsRb.MovePosition((Vector2)this.transform.position (movementDirection * speed * Time.deltaTime));}void LateUpdate(){Vector3 velocity Vector3.zero;//获取摄像机位置Vector3 targetPosition new Vector3(boxPhysicsRb.transform.position.x, boxPhysicsRb.transform.position.y, camera.transform.position.z);camera.transform.position Vector3.SmoothDamp(camera.transform.position, targetPosition, ref velocity, 0.06f);}
}Lerp也是可以的
camera.transform.position Vector3.Lerp(camera.transform.position, targetPosition, 10f * Time.deltaTime);【3】设置相机边界
①移动到特定距离后才移动摄像机 用Vector3.Distance(摄像机位置刚体位置)然后条件判断就是了。 缺点一些细致的边界问题处理麻烦 void LateUpdate(){Vector3 targetPosition new Vector3(boxPhysicsRb.transform.position.x, boxPhysicsRb.transform.position.y, camera.transform.position.z);Vector3 velocity Vector3.zero;var distance Vector3.Distance(camera.transform.position,targetPosition);if (distance 2f){camera.transform.position Vector3.SmoothDamp(camera.transform.position,targetPosition, ref velocity, 0.06f);}}②使用 Matf.Clamp 设置相机边界移动出范围后摄像机就不移动了 Mathf.Clamp 是一个常用的数学函数用于将一个值限制在指定的范围内。它的作用是确保一个值不会超出最小值和最大值之间的范围。具体来说Mathf.Clamp 接受三个参数 第一个参数是要限制的值。 第二个参数是范围的最小值。 第三个参数是范围的最大值。 void LateUpdate(){Vector3 velocity Vector3.zero;Vector3 bounds new Vector3(Mathf.Clamp(boxPhysicsRb.gameObject.transform.position.x, -4f, 4f),Mathf.Clamp(boxPhysicsRb.gameObject.transform.position.y, -4f, 4f),camera.transform.position.z);camera.transform.position Vector3.SmoothDamp(camera.transform.position, bounds, ref velocity, 0.06f);}注意按照Mathf.Clamp的意思超出范围了摄像机就不管了不同于①的意思所以在实际的游戏中还要额外设置边界碰撞体挺麻烦的
八、子弹发射与弹道轨迹射线提示
【1】简单的子弹发射
用键盘旋转角色
public float rotationSpeed 90f;
void Update(){float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
}Instantiate 是Unity中的一个函数用于在游戏运行时运行时实例化创建新的游戏对象GameObject的副本。
public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);original要实例化的原始对象。通常是一个预制体Prefab也可以是其他可实例化的对象如音频剪辑、材质等。 position新实例的位置。即要将新实例放置在场景中的位置。 rotation新实例的旋转。即要将新实例旋转到的方向。 Instantiate 函数将返回一个 Object 类型的引用。
步骤
新建子弹预制体带有刚体组件和在该预制体脚本中的Start()生命周期实现刚体初速度
public float speed 10f;
public Rigidbody2D rb;
void Start(){rb.velocity transform.right * speed;
}然后在box中新建对象即可自己需要确定shotGo子弹生成的位置另外注意刚体的动力学与动态之间的不同。
Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);效果如下 【2】使用物理模拟脚本的弹道轨迹
var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
bullet.GetComponentBullet().Shoot(force);接着就稍微改进一下变成子弹脚本变成可以公共函数调用即可。
box.cs
var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
var bs bullet.GetComponentBulletScript();
bs.Test(10);bulletScript.cs
public class BulletScript : MonoBehaviour
{public float speed 10f;public Rigidbody2D rb;void Start(){//rb.velocity transform.right * speed;}public void Test(float speed){Debug.Log(Testspeed);rb.velocity transform.right * speed;}
}然后给box加入LineRender组件这样就能描绘一条弹道射线按如下的视频写法确实可以但是子弹不能发射了)
有问题的代码
public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;private void Start(){boxLineRenderer.positionCount 0;}void Update(){float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);var bs bullet.GetComponentBulletScript();bs.Test(10);DrawTrail(bullet);}private void DrawTrail(GameObject bullet){Vector3[] points new Vector3[50];boxLineRenderer.positionCount points.Length;float accumulatedTime 0f;Physics2D.simulationMode SimulationMode2D.Script;for (int i 0; i points.Length; i){accumulatedTime Time.fixedDeltaTime;Physics2D.Simulate(accumulatedTime);points[i] bullet.transform.position;}Physics2D.simulationMode SimulationMode2D.FixedUpdate;boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}
}因为Physics2D.simulationMode SimulationMode2D.Script会与我们的子弹发射逻辑产生冲突那么问题变成了只能自己写物理模拟了。
开玩笑的呢其实就是改个逻辑就好不让物理模拟和子弹发射的代码逻辑出现在同一个局部作用域问题如果我们的逻辑过于复杂、不控制出现的过多子弹数性能会很差看你后续是否需要【3】的需求。
private float previousBoxRotation -1f;
void Update(){//...if (previousBoxRotation ! this.transform.rotation.eulerAngles.z){DrawTrail(bullet);previousBoxRotation this.transform.rotation.eulerAngles.z;}
}【3】按键控制子弹发射Debug的过程
我们在上面有问题的代码基础上改进按住空格键前显示射线松开就抛出子弹这也符合我们玩游戏的逻辑。
public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;private void Start(){boxLineRenderer.positionCount 0;}void Update(){float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);FireBullet();}private void FireBullet(){GameObject bullet null;if (Input.GetKeyDown(KeyCode.Space)){bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);DrawTrail(bullet);}else if(Input.GetKeyUp(KeyCode.Space)){bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);bullet.GetComponentBulletScript().Test(5f);}}private void DrawTrail(GameObject bullet){Vector3[] points new Vector3[50];float accumulatedTime 0f;Physics2D.simulationMode SimulationMode2D.Script;for (int i 0; i points.Length; i){accumulatedTime Time.fixedDeltaTime;Physics2D.Simulate(accumulatedTime);points[i] bullet.transform.position;}Physics2D.simulationMode SimulationMode2D.FixedUpdate;boxLineRenderer.positionCount points.Length;boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}
}效果如下 意外这是因为没有改射线的属性。
public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;private bool isDrawingTrajectory false;private GameObject bullet null;void Update(){float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);FireLogic();}private void FireLogic(){if (Input.GetKeyDown(KeyCode.Space)){StartDrawingTrajectory();}else if(Input.GetKeyUp(KeyCode.Space)){LaunchBullet();}if (isDrawingTrajectory){bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);DrawTrail(bullet);}}private void StartDrawingTrajectory(){isDrawingTrajectory true;boxLineRenderer.enabled true;boxLineRenderer.positionCount 1;boxLineRenderer.SetPosition(0, shootGo.position);}private void LaunchBullet(){isDrawingTrajectory false;boxLineRenderer.enabled false;bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);bullet.GetComponentBulletScript().Test(5f);}private void DrawTrail(GameObject bullet){Vector3[] points new Vector3[50];float accumulatedTime 0f;Physics2D.simulationMode SimulationMode2D.Script;for (int i 0; i points.Length; i){accumulatedTime Time.fixedDeltaTime;Physics2D.Simulate(accumulatedTime);points[i] bullet.transform.position;}Physics2D.simulationMode SimulationMode2D.FixedUpdate;boxLineRenderer.positionCount points.Length;boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}
}效果如下 累了毁灭吧。 下面就是最终效果了自己写物理效果各位也可以看需要修改
public class Box : MonoBehaviour
{// 存储子弹的预制体Prefabpublic GameObject bulletPrefab;// 存储发射点的Transform对象public Transform shootGo;// 用于绘制轨迹的线渲染器public LineRenderer boxLineRenderer;// 游戏对象的旋转速度public float rotationSpeed 90f;// 子弹发射的力量public float launchForce 10f;// 绘制轨迹的持续时间public float timeToDrawTrajectory 2f;// 子弹的生存时间public float bulletLifeTime 3f;private bool isDrawingTrajectory false;private void Update(){// 检测并处理旋转CheckRotate();// 处理子弹发射逻辑和轨迹绘制FireWithTrajectory();}private void CheckRotate(){// 获取水平输入通常是键盘上的左右箭头或A/D键float horizontalInput Input.GetAxis(Horizontal);// 根据输入旋转游戏对象transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);}private void FireWithTrajectory(){// 计算从发射点到鼠标位置的方向向量Vector2 dir (Camera.main.ScreenToWorldPoint(Input.mousePosition) - shootGo.position).normalized;if (Input.GetMouseButtonDown(0)){// 当鼠标左键按下时开始绘制轨迹StartDrawingTrajectory();}else if (Input.GetMouseButtonUp(0)){// 当鼠标左键释放时发射子弹LaunchBullet(dir);}if (isDrawingTrajectory){// 如果正在绘制轨迹继续绘制DrawTrajectory(dir);}}private void StartDrawingTrajectory(){// 开始绘制轨迹isDrawingTrajectory true;// 启用线渲染器boxLineRenderer.enabled true;// 设置线渲染器的起始点boxLineRenderer.positionCount 1;boxLineRenderer.SetPosition(0, shootGo.position);}private void DrawTrajectory(Vector2 dir){// 绘制子弹的轨迹Vector2 currentPosition shootGo.position;Vector2 currentVelocity dir * launchForce;float elapsedTime 0f;int pointCount 50; // 要绘制的点的数量Vector3[] trajectoryPoints new Vector3[pointCount];trajectoryPoints[0] currentPosition;for (int i 1; i pointCount; i){float timeStep timeToDrawTrajectory / pointCount;elapsedTime timeStep;currentPosition currentVelocity * timeStep;// 应用重力currentVelocity Physics2D.gravity * timeStep;trajectoryPoints[i] currentPosition;}// 设置线渲染器的点boxLineRenderer.positionCount pointCount;boxLineRenderer.SetPositions(trajectoryPoints);}private void LaunchBullet(Vector2 dir){// 发射子弹isDrawingTrajectory false;// 关闭线渲染器boxLineRenderer.enabled false;// 获取子弹的方向Vector2 direction dir;// 实例化子弹对象GameObject bullet Instantiate(bulletPrefab, shootGo.position, Quaternion.identity);// 获取子弹上的BulletScript组件var bs bullet.GetComponentBulletScript();// 调用子弹脚本的Test方法传递方向和发射力bs.Test(direction, launchForce);// 启动协程在一定时间后销毁子弹模拟子弹的生命周期StartCoroutine(DestroyBulletAfterTime(bullet, bulletLifeTime));}private IEnumerator DestroyBulletAfterTime(GameObject bullet, float lifetime){// 等待一定时间后销毁子弹yield return new WaitForSeconds(lifetime);Destroy(bullet);}
}效果如下终于成功了 【4】具有独立物理场景的射弹轨迹让子弹变得像弹夹一样
我们继续【2】的部分内容暂时不理会有bug的部分。 CreatePhysicsScene_Trajectory()方法 用于创建一个新的物理场景来模拟子弹的轨迹。创建名为PhysicsTrajectorySimulation的新场景并设置其物理模式为LocalPhysicsMode.Physics2D。获取新场景的2D物理场景引用。遍历physicsSceneObjects列表中的每个Transform对象实例化对应的游戏对象并将其移动到新场景中。如果游戏对象指的是地面引用的标签是StopBullet则将其Collider2D组件添加到stopBulletColliders列表中这样的做法用于确保在物理模拟中特别是子弹与地面交互时能够识别带有StopBullet标签的地面对象从而实现特殊的碰撞或物理行为。 public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;public ListTransform physicsSceneObjects new();public GameObject physicsGround;private float previousBoxRotation -1f;private Scene sceneSimulation;private PhysicsScene2D physicsScene;private ListCollider2D stopBulletColliders new();private void Start(){boxLineRenderer.positionCount 0;physicsSceneObjects.Add(physicsGround.transform);CreatePhysicsScene_Trajectory();}void Update(){float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);var bs bullet.GetComponentBulletScript();bs.Test(10);if (previousBoxRotation ! this.transform.rotation.eulerAngles.z){DrawTrail(bullet);previousBoxRotation this.transform.rotation.eulerAngles.z;}}private void DrawTrail(GameObject bullet){Vector3[] points new Vector3[50];boxLineRenderer.positionCount points.Length;float accumulatedTime 0f;Physics2D.simulationMode SimulationMode2D.Script;for (int i 0; i points.Length; i){accumulatedTime Time.fixedDeltaTime;Physics2D.Simulate(accumulatedTime);points[i] bullet.transform.position;}Physics2D.simulationMode SimulationMode2D.FixedUpdate;boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}private void CreatePhysicsScene_Trajectory(){sceneSimulation SceneManager.CreateScene(PhysicsTrajectorySimulation,new CreateSceneParameters(LocalPhysicsMode.Physics2D));physicsScene sceneSimulation.GetPhysicsScene2D();foreach (Transform obj in physicsSceneObjects){var physicsObject Instantiate(obj.gameObject, obj.position, obj.rotation);if (physicsObject.tag StopBullet){stopBulletColliders.Add(physicsObject.GetComponentCollider2D());}SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);}}
}【5】为对象添加弹性化为无情的网球发射机
就是新建2D物理材质然后添加到我们的子弹中去这样就能反弹了。 记得材质要设置弹力范围[0,1]可以看到我们的轨道发生改变了。 【6】带点的射弹轨迹线(03:56) 这一块的原理我不懂暂时跳过 实现步骤
从代码链接中获取DotLineMaterial.mat和DottedLineShader.shader这两个文件然后通过给材质添加对应的Shader实现接着就到我们box游戏对象的lineRender组件中修改材质属性即可。
【7】物体处的射弹轨迹停止线 识别弹道射线在带有TagStopBullet情况下不可以反射其他Tag就能反射 public class Box : MonoBehaviour
{// 刚体组件用于控制物体的物理行为public Rigidbody2D rb;// 游戏对象的旋转速度public float rotationSpeed 90f;// 子弹的预制体public GameObject bulletPref;// 子弹发射点的Transformpublic Transform shootGo;// 用于绘制轨迹的线渲染器public LineRenderer boxLineRenderer;// 存储物理场景中的对象public ListTransform physicsSceneObjects new ListTransform();// 物理地面的游戏对象public GameObject physicsGround;// 上一次游戏对象的旋转角度private float previousBoxRotation -1f;// 场景模拟private Scene sceneSimulation;// 物理场景private PhysicsScene2D physicsScene;// 用于停止子弹的碰撞体列表private ListCollider2D stopBulletColliders new ListCollider2D();private void Start(){// 初始化线渲染器的位置点数量boxLineRenderer.positionCount 0;// 将物理地面添加到物理场景对象列表中physicsSceneObjects.Add(physicsGround.transform);// 创建用于轨迹模拟的物理场景CreatePhysicsScene_Trajectory();}void Update(){// 获取水平输入来旋转游戏对象float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);// 实例化子弹var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);// 将子弹对象移到模拟场景中SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);var bs bullet.GetComponentBulletScript();bs.Test(10);// 当游戏对象旋转时绘制轨迹if (previousBoxRotation ! this.transform.rotation.eulerAngles.z){DrawTrail(bullet);previousBoxRotation this.transform.rotation.eulerAngles.z;}}private void DrawTrail(GameObject bullet){var bulletCollider2D bullet.GetComponentCollider2D();Vector3[] points new Vector3[50];var pointsBeforeCollision 0;for (int i 0; i points.Length; i){// 模拟物理场景physicsScene.Simulate(Time.fixedDeltaTime);// 如果子弹与停止碰撞体相撞退出循环if (isBulletToichingStopCollider(bulletCollider2D)){break;}pointsBeforeCollision;points[i] bullet.transform.position;}// 设置线渲染器的位置点boxLineRenderer.positionCount pointsBeforeCollision;boxLineRenderer.SetPositions(points);// 销毁子弹对象Destroy(bullet.gameObject);}private bool isBulletToichingStopCollider(Collider2D bulletCollider2D){ var pos bulletCollider2D.gameObject.transform.position;foreach (Collider2D collider in stopBulletColliders){var distance (pos - collider.transform.position).magnitude;// 如果子弹与停止碰撞体相撞返回trueif (collider.IsTouching(bulletCollider2D)){return true;}}// 子弹未与停止碰撞体相撞返回falsereturn false;}private void CreatePhysicsScene_Trajectory(){// 创建用于轨迹模拟的场景sceneSimulation SceneManager.CreateScene(PhysicsTrajectorySimulation, new CreateSceneParameters(LocalPhysicsMode.Physics2D));physicsScene sceneSimulation.GetPhysicsScene2D();foreach (Transform obj in physicsSceneObjects){// 实例化物理场景中的物体var physicsObject Instantiate(obj.gameObject, obj.position, obj.rotation);if (physicsObject.tag StopBullet){// 如果物体标签是StopBullet则将其碰撞体添加到停止碰撞体列表中stopBulletColliders.Add(physicsObject.GetComponentCollider2D());}// 将物体移到模拟场景中SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);}}
}【8】给子弹添加渲染
其实就是给子弹这个预制体添加下图的组件改射线的宽度和 颜色让子弹的效果更炫酷。 【9】弹道长度和射弹速度 其实就是按空格键来蓄力实现弹道长度的增加同时改变子弹的蓄力速度。 public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;public ListTransform physicsSceneObjects new();public GameObject physicsGround;public float bulletForce 10f;private float previousBoxRotation -1f;private Scene sceneSimulation;private PhysicsScene2D physicsScene;private ListCollider2D stopBulletColliders new();private float speedLerp 0f;private float speedTimeElapsed 0.0f;private bool isHoldingSpace false;private void Start(){boxLineRenderer.positionCount 0;physicsSceneObjects.Add(physicsGround.transform);CreatePhysicsScene_Trajectory();}void Update(){if (Input.GetKeyDown(KeyCode.Space)){speedTimeElapsed 0;isHoldingSpace true;}if (Input.GetKeyUp(KeyCode.Space)){isHoldingSpace false;}if (isHoldingSpace){speedTimeElapsed Time.deltaTime;}float horizontalInput Input.GetAxis(Horizontal);var totalTime 2f;speedLerp Mathf.Lerp(0, bulletForce, speedTimeElapsed / totalTime);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);var bs bullet.GetComponentBulletScript();bs.Test(speedLerp);ShowTrajectoryBouncyPhysicsScene(bullet);}public void ShowTrajectoryBouncyPhysicsScene(GameObject bullet){Vector3[] points new Vector3[50];boxLineRenderer.positionCount points.Length;for (int i 0; i points.Length; i){physicsScene.Simulate(Time.fixedDeltaTime);points[i] bullet.transform.position;}boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}private void CreatePhysicsScene_Trajectory(){sceneSimulation SceneManager.CreateScene(PhysicsTrajectorySimulation,new CreateSceneParameters(LocalPhysicsMode.Physics2D));physicsScene sceneSimulation.GetPhysicsScene2D();foreach (Transform obj in physicsSceneObjects){var physicsObject Instantiate(obj.gameObject, obj.position, obj.rotation);if (physicsObject.tag StopBullet){stopBulletColliders.Add(physicsObject.GetComponentCollider2D());}SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);}}
}效果如下图 【10】用鼠标改变和长度速度 手感超级怪是我拆的不对嘛 public class Box : MonoBehaviour
{public Rigidbody2D rb;public float rotationSpeed 90f;public GameObject bulletPref;public Transform shootGo;public LineRenderer boxLineRenderer;public ListTransform physicsSceneObjects new();public GameObject physicsGround;public float bulletForce 10f;private float previousBoxRotation -1f;private Scene sceneSimulation;private PhysicsScene2D physicsScene;private ListCollider2D stopBulletColliders new();private bool dragging false;private Vector3 mouseStartDragPosition;private Vector3 mouseCurrentDragPosition;private void Start(){boxLineRenderer.positionCount 0;physicsSceneObjects.Add(physicsGround.transform);CreatePhysicsScene_Trajectory();}void Update(){if (Input.GetMouseButtonDown(0)){mouseStartDragPosition Camera.main.ScreenToWorldPoint(Input.mousePosition);mouseCurrentDragPosition mouseStartDragPosition;dragging true;}else if (Input.GetMouseButtonUp(0)){dragging false;}float horizontalInput Input.GetAxis(Horizontal);transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);if (dragging){var rotationSpeed 20f;var mousePosition Camera.main.ScreenToWorldPoint(Input.mousePosition);mouseCurrentDragPosition mousePosition;float yAxis mousePosition.y * rotationSpeed;transform.Rotate(Vector3.forward * yAxis * Time.deltaTime);}if (previousBoxRotation ! transform.rotation.eulerAngles.z){var maxDrag 2f;var maxSpeed 10f;var drag Mathf.Clamp(mouseStartDragPosition.x - mouseCurrentDragPosition.x, 0, maxDrag);var currentSpeed drag / maxDrag * maxSpeed;ShowTrajectoryBouncyCollisionPhysicsScene(currentSpeed);previousBoxRotation transform.rotation.eulerAngles.z; }}public void ShowTrajectoryBouncyCollisionPhysicsScene(float force){var bullet Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);bullet.GetComponentBulletScript().Test(force);var bulletCollider2D bullet.GetComponentCollider2D();Vector3[] points new Vector3[50];var pointsBeforeCollision 0;for (int i 0; i points.Length; i){physicsScene.Simulate(Time.fixedDeltaTime);if (isBulletToichingStopCollider(bulletCollider2D)){break;}pointsBeforeCollision;points[i] bullet.transform.position;}boxLineRenderer.positionCount pointsBeforeCollision;boxLineRenderer.SetPositions(points);Destroy(bullet.gameObject);}private bool isBulletToichingStopCollider(Collider2D bulletCollider2D){var pos bulletCollider2D.gameObject.transform.position;foreach (Collider2D collider in stopBulletColliders){var distance (pos - collider.transform.position).magnitude;if (collider.IsTouching(bulletCollider2D)){return true;}}return false;}private void CreatePhysicsScene_Trajectory(){sceneSimulation SceneManager.CreateScene(PhysicsTrajectorySimulation,new CreateSceneParameters(LocalPhysicsMode.Physics2D));physicsScene sceneSimulation.GetPhysicsScene2D();foreach (Transform obj in physicsSceneObjects){var physicsObject Instantiate(obj.gameObject, obj.position, obj.rotation);if (physicsObject.tag StopBullet){stopBulletColliders.Add(physicsObject.GetComponentCollider2D());}SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);}}
}效果如下 说实话效果很糟糕至少对于PC来说手感非常差。 九、磁铁/抓钩
【1】射出磁铁
public class Box : MonoBehaviour
{public Transform shootPosition;public float hookDistance 20f;public LayerMask grabMask;public GameObject physicsHook;private Rigidbody2D physicsHookRb;private Vector2 movementDirection;private RaycastHit2D raycastHit2D;private bool shoot false;private float hookTimeElapsed 0.0f;private Rigidbody2D rb;private void Start(){rb this.GetComponentRigidbody2D();physicsHookRb physicsHook.GetComponentRigidbody2D();}void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));if (Input.GetKeyDown(KeyCode.Space)){hookTimeElapsed 0;Example_Shoot_Hook();}hookTimeElapsed Time.deltaTime;}void FixedUpdate(){var shootDistance Vector3.Distance(shootPosition.transform.position, raycastHit2D.point);var hookSpeed 2;var totalTime shootDistance / hookSpeed;var time hookTimeElapsed / totalTime;if (shoot raycastHit2D.collider ! null){var push Vector3.Lerp(shootPosition.transform.position, raycastHit2D.point, time);physicsHookRb.MovePosition(push);}MovePhysicsBox(2);}private void MovePhysicsBox(float speed){rb.velocity movementDirection * speed;}private void Example_Shoot_Hook(){RaycastHit2D hit Physics2D.Raycast(shootPosition.transform.position, Vector3.right, hookDistance, grabMask);if (hit.collider ! null){shoot true;raycastHit2D hit;}else{shoot false;physicsHook.transform.localPosition new Vector2(0, 0);}}
}给我们的redBox添加名为Grab的Layer就能保证之后能够抓取具体如下自行额外添加对应的刚体和碰撞体吧。 效果如下 【2】取回磁铁
//...
private bool toggleOnClick false;
private float mouseTimeElapsed 0.0f;void Update(){if (Input.GetMouseButtonDown(0)){toggleOnClick !toggleOnClick;mouseTimeElapsed 0;}if (toggleOnClick){mouseTimeElapsed Time.deltaTime;}//...
}void FixedUpdate(){//...if (toggleOnClick){var pullTime mouseTimeElapsed / totalTime;var pull Vector3.Lerp(physicsHookRb.transform.position, shootPosition.transform.position, pullTime);physicsHookRb.MovePosition(pull);shoot false;}MovePhysicsBox(2);
}效果如下 空格键发射然后鼠标键第一次就回到发射点而要按下第二次才能重新空格键发射需要注意的是我让box为动态而redBox为静态blueBox为动态。 【3】用磁铁拉住另一个物体
public class Box : MonoBehaviour
{// 存储射击位置的游戏对象public GameObject shootPosition;// 钩子的最大伸展距离public float hookDistance 20f;// 用于射击和抓取物体的层级掩码public LayerMask grabMask;// 物理钩子的游戏对象public GameObject physicsHook;// 用于存储玩家移动方向的向量private Vector2 movementDirection;// 用于射击的射线命中信息private RaycastHit2D raycastHit2D;// 标志是否进行射击private bool shoot false;// 钩子射出后的时间累计private float hookTimeElapsed 0.0f;// 鼠标点击状态的标志private bool toggleOnClick false;// 鼠标按下后的时间累计private float mouseTimeElapsed 0.0f;// 物理钩子的固定关节组件private FixedJoint2D physicsHookFixedJoint;// 物理钩子的碰撞器组件private Collider2D physicsHookCollider;// 射击位置的碰撞器组件private Collider2D shootPositionCollider;// 标志是否完成了物体拉取private bool finishedPull false;// 玩家自身的刚体组件private Rigidbody2D rb;// 物理钩子的刚体组件private Rigidbody2D physicsHookRb;private void Start(){// 获取玩家自身的刚体组件rb this.GetComponentRigidbody2D();// 获取物理钩子的刚体组件physicsHookRb physicsHook.GetComponentRigidbody2D();// 获取物理钩子的碰撞器组件physicsHookCollider physicsHook.GetComponentCollider2D();// 获取射击位置的碰撞器组件shootPositionCollider shootPosition.GetComponentCollider2D();// 获取物理钩子的固定关节组件physicsHookFixedJoint physicsHook.GetComponentFixedJoint2D();}void Update(){// 获取玩家的输入以确定移动方向movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));if (Input.GetMouseButtonDown(0)){// 切换鼠标点击状态并重置时间toggleOnClick !toggleOnClick;mouseTimeElapsed 0;finishedPull false;}if (toggleOnClick){// 鼠标点击状态下累计时间mouseTimeElapsed Time.deltaTime;}if (Input.GetKeyDown(KeyCode.Space)){// 重置钩子时间并射出钩子hookTimeElapsed 0;Example_Shoot_Hook_2();}hookTimeElapsed Time.deltaTime;}void FixedUpdate(){// 计算钩子的速度和总时间var shootDistance Vector3.Distance(shootPosition.transform.position, raycastHit2D.point);var hookSpeed 20;var totalTime shootDistance / hookSpeed;var time hookTimeElapsed / totalTime;if (shoot raycastHit2D.collider ! null){// 将钩子向命中点推进var push Vector3.Lerp(shootPosition.transform.position, raycastHit2D.point, time);physicsHookRb.MovePosition(push);}if (raycastHit2D.collider ! null physicsHookCollider.IsTouching(shootPositionCollider) physicsHookCollider.IsTouching(raycastHit2D.collider)){// 钩子与物体碰撞标志物体已被拉取finishedPull true;}if (raycastHit2D.collider ! null !physicsHookCollider.IsTouching(shootPositionCollider) physicsHookCollider.IsTouching(raycastHit2D.collider) !finishedPull !toggleOnClick){// 如果钩子与物体碰撞但尚未完成拉取且不在点击状态下固定物体var redBox raycastHit2D.collider.gameObject.GetComponentRigidbody2D();physicsHookFixedJoint.connectedBody redBox; }if (toggleOnClick !finishedPull){// 如果在点击状态下且尚未完成拉取将物理钩子收回var pullTime mouseTimeElapsed / totalTime;var pull Vector3.Lerp(physicsHookRb.transform.position, shootPosition.transform.position, pullTime);physicsHookRb.MovePosition(pull);shoot false;}// 移动物理盒子MovePhysicsBox(2);}private void MovePhysicsBox(float speed){// 移动玩家物体rb.velocity movementDirection * speed;}private void Example_Shoot_Hook_2(){// 射出钩子检测是否命中可抓取物体RaycastHit2D hit Physics2D.Raycast(shootPosition.transform.position, Vector3.right, hookDistance, grabMask);if (hit.collider ! null){// 标志已射出钩子并存储射线命中信息shoot true;raycastHit2D hit;// 断开物理钩子的连接physicsHookFixedJoint.connectedBody null;}else{// 如果未命中物体标志未射出钩子并将物理钩子重置shoot false;physicsHook.transform.localPosition new Vector2(0, 0);}}
}这里的关键就是要保证引用不出错、bodyType不要设置错误具体如下另外redBox选择刚体为动态 效果如下 当我们空格射出blueBox后 TIP如果游戏对象刚体的BodyType是动态加入这个组件Fixed Join 2D后就不能移动了因为它会强制将其变成静态。
【4】创建抓钩 其实就是【3】的基础上加条绳子做法是让lineRender模拟成绳子如果不能调颜色的话就选择添加材质再修改即可。 public class Hook : MonoBehaviour
{public Transform shootPosition;public LineRenderer lineRenderer;void Update(){lineRenderer.SetPosition(0, shootPosition.transform.position);lineRenderer.SetPosition(1, transform.position);}
}十、贪吃蛇
【1】拾起范围内的物体
box.cs
public class Box : MonoBehaviour
{private Vector2 movementDirection;public LayerMask collectMask;void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));}void FixedUpdate(){var rotationSpeed 150f;var moveSpeed 1.5f;this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);this.transform.Rotate(Vector3.forward * - movementDirection.x * rotationSpeed * Time.fixedDeltaTime);var radius 3f;Collider2D[] hitColliders Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);Debug.Log(hitColliders);foreach (var hitCollider in hitColliders){var collect hitCollider.gameObject.GetComponentCollect();if (!collect.isCollecting){collect.StartCollecting(this.transform);}}}private void OnDrawGizmos(){Gizmos.color Color.yellow;Gizmos.DrawWireSphere((Vector2)this.transform.position, 1.5f);}
}collect.cs 我们要让被收集的物体挂载该脚本然后让其具有碰撞体组件即可对被收集的游戏对象添加上要被box识别的Collect的Layer。 public class Collect : MonoBehaviour
{private bool isCollected;private float timeElapsed 0;public bool isCollecting false;private Transform character;void Update(){timeElapsed Time.deltaTime;if (isCollecting){var speed 2; // secondsvar step speed * Time.deltaTime;var target character.transform.position;//实现一个被吃的效果就是移动销毁gameObject.transform.position Vector2.MoveTowards(gameObject.transform.position, target, step);if (Vector3.Distance(gameObject.transform.position, target) 0.001f){isCollecting false;Destroy(gameObject);}}}public void StartCollecting(Transform target){timeElapsed 0;isCollecting true;character target;}
}效果如下 【2】跟随物体蛇形运动
脚本
box.cs
using UnityEngine;public class Box : MonoBehaviour
{private Vector2 movementDirection;public GameObject followPartPref;public GameObject bodyParts;private void Start(){//实例体的父类也是需要被实例化的不然会报错不要按视频的写法来var bodyPartsInit Instantiate(bodyParts);GameObject part1 CreateFollowPart(this.transform,bodyPartsInit);GameObject part2 CreateFollowPart(part1.transform, bodyPartsInit);}void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));}void FixedUpdate(){var rotationSpeed 150f;var moveSpeed 1.5f;this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);}private GameObject CreateFollowPart(Transform followTarget,GameObject bodyPartsInit){var spaceBetween 2f;var bodyPart Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);bodyPart.transform.parent bodyPartsInit.transform;BodyPart bodyPartComponent bodyPart.GetComponentBodyPart();bodyPartComponent.FollowTarget followTarget;bodyPartComponent.SpaceBetween spaceBetween;return bodyPart;}private Vector3 FollowPosition(Transform target, float spaceBetween){var position target.position;return position - target.right * spaceBetween;}
}BodyPart.cs
public class BodyPart : MonoBehaviour
{// 要跟随的目标对象public Transform FollowTarget;// 跟随部件之间的间隔距离public float SpaceBetween 10f;// 默认移动速度private float defaultSpeed 1.5f;// 当前移动速度private float speed 1.5f;// 用于处理速度变化的方法public void ChangeSpeed(bool isHoldingSpace){// 根据是否按住空格键来改变速度if (isHoldingSpace){speed defaultSpeed * 2;}else{speed defaultSpeed;}}// 每帧执行的方法private void Update(){// 计算向目标对象移动的向量Vector3 vectorToTarget FollowTarget.position - transform.position;// 旋转向量以确保部件始终面向前方Vector3 rotateVectorToTarget Quaternion.Euler(0, 0, 90) * vectorToTarget;transform.rotation Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget);// 计算到目标对象的距离var distanceToHead (transform.position - FollowTarget.position).magnitude;// 如果距离超过指定的间隔距离则向前移动if (distanceToHead SpaceBetween){transform.Translate(Vector3.right * speed * Time.deltaTime);}}
}
unity操作
这个BodyParts需要规范操作此外需要设为实例体的父类时更是需要实例化看注释。 【3】吃东西时跟随物体蛇运动并长出蛇
脚本
box.cs
public class Box : MonoBehaviour
{private Vector2 movementDirection;public GameObject followPartPref;public GameObject bodyParts;public LayerMask collectMask;private int count;public Transform GO;private void Start(){GameObject part1 CreateFollowPart(this.transform);GameObject part2 CreateFollowPart(part1.transform);count GO.childCount;}void Update(){movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));}void FixedUpdate(){var rotationSpeed 150f;var moveSpeed 1.5f;this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);var radius 1.5f;Collider2D[] hitColliders Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);foreach (var hitCollider in hitColliders){var collect hitCollider.gameObject.GetComponentCollect();collect.callback OnEat;if (!collect.isCollecting){collect.Eat(this.transform);}}}private GameObject CreateFollowPart(Transform followTarget){var spaceBetween 2f;var bodyPartsInit Instantiate(bodyParts);var bodyPart Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);bodyPart.transform.parent bodyPartsInit.transform;BodyPart bodyPartComponent bodyPart.GetComponentBodyPart();bodyPartComponent.FollowTarget followTarget;bodyPartComponent.SpaceBetween spaceBetween;//这里的效果就类似负责存放蛇尾巴的编号bodyPart.transform.parent GO;return bodyPart;}private Vector3 FollowPosition(Transform target, float spaceBetween){var position target.position;return position - target.right * spaceBetween;}private void OnEat(){if (count 0){//Debug.Log(count);Transform previousPart GO.GetChild(count - 1);CreateFollowPart(previousPart);count 1;}else{Debug.Log(WITHOUT);}}
}Collect.cs
public class Collect : MonoBehaviour
{private bool isCollected; // 标志物体是否已被收集private float timeElapsed 0; // 记录时间的累计public bool isCollecting false; // 标志物体是否正在被收集public bool isEaten false; // 标志物体是否被吃掉private Transform character; // 与物体交互的角色的Transform组件public Action callback; // 当物体被吃掉后要执行的回调函数void Update(){timeElapsed Time.deltaTime; // 更新时间累计if (isCollecting){var speed 2; // 移动速度单位秒var step speed * Time.deltaTime; // 计算每帧的移动步长var target character.transform.position; // 目标位置// 将物体向目标位置移动gameObject.transform.position Vector2.MoveTowards(gameObject.transform.position, target, step);// 如果物体接近目标位置停止收集并销毁物体if (Vector3.Distance(gameObject.transform.position, target) 0.001f){isCollecting false;Destroy(gameObject);}}if (isEaten){var speed 4; // 移动速度单位秒var step speed * Time.deltaTime; // 计算每帧的移动步长var target character.transform.position; // 目标位置// 将物体向目标位置移动gameObject.transform.position Vector2.MoveTowards(gameObject.transform.position, target, step);// 如果物体接近目标位置停止吃掉并销毁物体然后执行回调函数if (Vector3.Distance(gameObject.transform.position, target) 0.001f){isCollecting false;Destroy(gameObject);callback();}}}// 启动收集物体的方法public void StartCollecting(Transform target){timeElapsed 0; // 重置时间累计isCollecting true; // 标志物体正在被收集character target; // 设置与物体交互的角色Transform}// 吃掉物体的方法public void Eat(Transform target){timeElapsed 0; // 重置时间累计isEaten true; // 标志物体已被吃掉character target; // 设置与物体交互的角色Transform}
}unity操作 视频代码问题真是太令人生艹。 效果如下 【4】使用电影机cinemachine跟踪蛇头
下载插件cinemachine 然后就在Hierarchy中右键该电影机游戏对象接着就这样弄到同一个父空对象中去然后Follow我们的蛇头即可。 【5】使用带边界的电影机跟踪对象 这里的带边界意思是指当我们的蛇头运动到我们划定的好的多边形边界后我们的cinemachine并不会跟谁蛇头出这个边界而蛇头也会因为碰撞体的缘故无法物理上出界。 【6】贪吃蛇游戏空格增加移动速度 球的生成的位置和数量设置的挺不合理的懒得改bug了空格加速这个简易的贪吃蛇就弄完了另外没有失败判断。 box.cs
委托是一种代表方法的类型它可以用来存储对这些方法的引用并随后调用它们。
下面的代码中委托被定义为OnSpeedChangeDelegate该委托接受一个 bool 类型的参数。随后onSpeedChangeDelegate 被声明为一个具有相同委托类型的静态变量。
此委托的目的是存储对一个或多个具有相同参数签名的方法的引用。然后在代码的其他部分可以使用 onSpeedChangeDelegate 来调用这些方法而无需明确知道这些方法的名称或实现。
public class Box : MonoBehaviour
{// 存储玩家的移动方向向量private Vector2 movementDirection;// 用于跟随的游戏对象预制体public GameObject followPartPref;// 身体部件的父对象public GameObject bodyParts;// 用于收集的层级掩码public LayerMask collectMask;// 身体部件的数量private int count;// 用于生成普通食物的预制体public GameObject normalBulletCollisionPref;// 存储生成的食物的容器对象public GameObject cleanBucket;// 用于处理速度变化的委托private delegate void OnSpeedChangeDelegate(bool isHoldingSpace);private static OnSpeedChangeDelegate onSpeedChangeDelegate;private bool isHoldingSpace false;private float spawnElapsedTime;private ListCollider2D allColliders new ListCollider2D();private void Start(){// 创建初始的跟随部件GameObject part1 CreateFollowPart(this.transform);GameObject part2 CreateFollowPart(part1.transform);// 获取子对象数量count GO.childCount;}void Update(){// 计算生成食物的时间累计spawnElapsedTime Time.deltaTime;// 获取玩家的输入以确定移动方向movementDirection new Vector2(Input.GetAxis(Horizontal), Input.GetAxis(Vertical));if (Input.GetKeyDown(KeyCode.Space)){// 按下空格键时标志正在按住空格键isHoldingSpace true;}if (Input.GetKeyUp(KeyCode.Space)){// 松开空格键时取消标志isHoldingSpace false;}}void FixedUpdate(){var seconds 0.5f;if (spawnElapsedTime * seconds seconds){// 生成食物spawnElapsedTime 0;var height Camera.main.orthographicSize;var width height * Camera.main.aspect;var foodPosition new Vector2(UnityEngine.Random.Range(width, -width),UnityEngine.Random.Range(height, -height));NormalBulletCollision(foodPosition);}var rotationSpeed 150f;var moveSpeed 1.5f;if (isHoldingSpace){// 如果按住空格键加快移动速度moveSpeed * 2;}onSpeedChangeDelegate(isHoldingSpace);// 移动玩家this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);var radius 1.5f;// 检测碰撞区域内的可收集物体Collider2D[] hitColliders Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);foreach (var hitCollider in hitColliders){var collect hitCollider.gameObject.GetComponentCollect();collect.callback OnEat;if (!collect.isCollecting){// 收集可收集物体collect.Eat(this.transform);}}}private GameObject CreateFollowPart(Transform followTarget){// 创建跟随部件var spaceBetween 2f;var bodyPartsInit Instantiate(bodyParts);var bodyPart Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);bodyPart.transform.parent bodyPartsInit.transform;BodyPart bodyPartComponent bodyPart.GetComponentBodyPart();bodyPartComponent.FollowTarget followTarget;bodyPartComponent.SpaceBetween spaceBetween;bodyPart.transform.parent GO;onSpeedChangeDelegate bodyPartComponent.ChangeSpeed;return bodyPart;}private Vector3 FollowPosition(Transform target, float spaceBetween){// 计算跟随部件的位置var position target.position;return position - target.right * spaceBetween;}private void OnEat(){// 当吃到食物时创建新的跟随部件if (count 0){Transform previousPart GO.GetChild(count - 1);CreateFollowPart(previousPart);count 1;}else{Debug.Log(WITHOUT);}}private void NormalBulletCollision(Vector2 position){// 生成普通食物GameObject bulletCollision (GameObject)Instantiate(normalBulletCollisionPref.gameObject, position, Quaternion.identity);bulletCollision.transform.parent cleanBucket.transform;var collider bulletCollision.GetComponentCollider2D();allColliders.Add(collider);}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919608.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!