2D小狐狸

创建并控制角色

精灵 Sprite 是 Unity 中 2D 素材的默认存在形式,是 Unity 中的 2D 图形对象。

在 2D 游戏中,使用 2D 素材的过程:
PNG(JPG 等)----> Sprite ----> GameObject

2D 游戏中,没有 Z 轴来表示深度,只有 X 轴和 Y 轴,以中心点为原点(0,0),左 x 负,右 x 正,上 y 正,下 y 负
通过游戏对象的 Transform 组件,可以获取该对象在场景中的位置 Position,并通过更改 Transform 组件 Position 的值,可以更改其位置,依据的坐标轴就是上面描述的 2D 坐标轴

public class RubyController : MonoBehaviour
{
    // 每帧调用一次 Update
    // 让游戏对象每帧右移 0.1
    void Update()
    {
        // 创建一个 Vector2 对象 position,用来获取当前对象的位置
        Vector2 position = transform.position;
        // 更改 position 的 x 坐标值,让其 加上 0.1
        position.x = position.x + 0.1f;
        // 更新当前对象的位置到新位置
        transform.position = position;
    }
}

在 Unity 项目设置中,可以通过 Input Manager 进行默认的游戏输入控制设置

Edit > Project Settings > Input

键盘按键,以 2 个键来定义轴:

负值键 negative button,被按下时将轴设置为 -1
正值键 positive button ,被按下时将轴设置为 1
Axis 轴 Axes 是它的负数形式

Horizontal Axis: 水平轴 对应 X 轴
Vertical Axis:纵轴 对应 Y 轴

Input 类

使用该类来读取传统游戏输入中设置的轴/鼠标/按键,以及访问移动设备上的多点触控/加速度计数据。

若要使用输入来进行任何类型的移动行为,请使用 Input.GetAxis。 它为您提供平滑且可配置的输入 - 可以映射到键盘、游戏杆或鼠标。 请将 Input.GetButton 仅用于事件等操作。不要将它用于移动操作。Input.GetAxis 将使脚本代码更简洁。

时间和帧率

当前的代码中,帧数越高,同一时间内,执行 Update 的次数越多,角色移动速度越快。
而帧数是由硬件水平影响的(越好越高),不同电脑中,会导致游戏效果完全不同

可以采取锁帧,但并不推荐

void Start()
    {
        // 只有将垂直同步计数设置为0,才能锁帧,否则锁帧的代码无效
        // 垂直同步的作用就是显著减少游戏画面撕裂、跳帧,因为画面的渲染不是整个画面一同渲染的,而是逐行或逐列渲染的,能够让FPS保持与显示屏的刷新率相同。
        QualitySettings.vSyncCount = 0;
        //设定应用程序帧数为10
        Application.targetFrameRate = 10;
    }

硬件能达到的情况下(显卡、显示器),锁帧会降低画面效果。

要解决此问题,需要以单位/秒来表示 Ruby 的移动速度,而不是采用单位/帧(目前采用此设置)。

public class RubyController : MonoBehaviour
{
    // 将速度暴露出来,使其可调
    public float speed=0.1f;
// 每帧调用一次 Update
   void Update()
   {
       float horizontal = Input.GetAxis("Horizontal");
       float vertical = Input.GetAxis("Vertical");
       Vector2 position = transform.position;
       position.x = position.x + speed * horizontal * Time.deltaTime;
       position.y = position.y + speed * vertical * Time.deltaTime;
       transform.position = position;
   }
}

使用瓦片地图创建世界

Rectangler 矩形瓦片地图

Hexagonal 六边形瓦片地图:除常规瓦片地图外,Unity 还提供 Hexagonal Point Top Tilemap 和 Hexagonal Flat Top Tilemap 瓦片地图。六角形瓦片通常用于战略类桌面游戏,因为它们的中心与边上的任何点之间具有一致的距离,并且相邻的瓦片总是共享边。因此,这些瓦片非常适合构建几乎任何类型的大型游戏区域,并让玩家做出关于移动和定位的战术决策。

Isometric 等距瓦片地图: 等距透视图显示所有三个 X、Y 和 Z 轴,因此可以将伪深度和高度添加到瓦片地图。
等距瓦片地图常用于策略类游戏,因为等距透视图允许模拟 3D 游戏元素,例如不同的海拔和视线。这样可使玩家在游戏过程中做出关于移动和定位的战术决策。

瓦片

瓦片是排列在瓦片地图上的资源,用于构建 2D 环境。每个瓦片引用一个选定的精灵,然后在瓦片地图网格上的瓦片位置处渲染该精灵。

Tile :新版本中已经看不到,但可以使用
Scriptable Tile:自编程瓦片
Rule Tile:规则瓦片
Animated Tile:动画瓦片

伪透视图

透视图指的是有深度、距离感的图,一般要三维中的深度轴来表现场景的深度,而二维游戏中没有这个深度,只能通过前后来仿造深度效果,称为“伪透视图”
在 2D 游戏中,场景里的 “前后” 是由 Y 轴决定的,需要让 Unity 根据游戏对象的 y 坐标来绘制游戏对象

Y 轴 y 坐标值越小,越靠前,应该遮挡 y 坐标值较大的游戏对象,也就是 y 坐标较小的游戏对象后绘制,就会位于上层

在游戏中,如果要设置 2D 伪透视试图,需要在项目设置中进行更改:

Edit > Project Settings > Graphics > Camera Settings > Transparency Sort Mode = Custom Axis > Transparency Sort Axis x = 0 / y = 1 / z = 0

2D 刚体 Rigidbody 2D

2D 刚体组件将对象置于物理引擎的控制之下,和 3D 标准刚体不同,刚体在 2D 中,对象只能在 XY 平面中移动,并且只能在垂直于该平面的轴上旋转。

发生碰撞时, 2D 刚体组件可以将碰撞将要导致的效果(如位移、旋转等),传达回 Transform 组件,Transform 组件就按照接收到的这些信息进行位移和旋转,表现出应用的碰撞效果

碰撞过程:

collider 碰撞体 ---- 负责监测是否能够发生碰撞
rigidbody 刚体 ---- 根据物理引擎设计的规则,产生碰撞后的效果数据
Transform 变换 ---- 接收刚体传递过来的效果数据,将这些数据对应的效果表现出来,根据数据移动位置,发生旋转

Body Type 属性

Body Type 有三个选项;每个选项定义一种常见和固定的行为。附加到 2D 刚体的 2D 碰撞体将继承 2D 刚体的 Body Type。这三个选项是:

Dynamic(动态):

Dynamic 类型的 2D 刚体设计为在模拟条件下移动。这种刚体类型具有可用的全套属性(例如有限质量和阻力),并受重力和作用力的影响。Dynamic 刚体类型将与每个其他刚体类型碰撞,是最具互动性的刚体类型。这是需要移动的对象的最常见刚体类型,因此是 2D 刚体的默认刚体类型。此外,由于具有动态性并与周围所有对象互动,因此也是性能成本最高的刚体类型。选择此刚体类型时,所有 2D 刚体属性均可用。

Kinematic(力学):

Kinematic 类型的 2D 刚体设计为在模拟条件下移动,但是仅在非常明确的用户控制下进行。虽然 Dynamic 2D 刚体受重力和作用力的影响,但 Kinematic 2D 刚体并不会受此影响。因此,Kinematic 2D 刚体的速度很快,与 Dynamic 2D 刚体相比,对系统资源的需求更低。Kinematic 2D 刚体按设计应通过 Rigidbody2D.MovePosition 或Rigidbody2D.MoveRotation 进行显式重定位。应使用物理查询来检测碰撞,并通过脚本确定 2D 刚体应该移动的位置和方式。

Kinematic 2D 刚体仍然通过速度移动,但是此速度不受作用力和重力的影响。Kinematic 2D 刚体不会与其他 Kinematic 2D 刚体和 Static 2D 刚体碰撞,只会与 Dynamic 2D 刚体碰撞。与 Static 2D 刚体(见下文)相似,Kinematic 2D 刚体在碰撞期间的行为类似于不可移动的对象(就像具有无限质量)。选择此刚体类型时,与质量相关的属性将不可用。

Static:

Static 2D 刚体设计为在模拟条件下完全不动;如果任何对象与 Static 2D 刚体碰撞,此类型刚体的行为类似于不可移动的对象(就像具有无限质量)。此刚体类型也是使用资源最少的刚体类型。Static 刚体只能与 Dynamic 2D 刚体碰撞。不支持两个 Static 2D 刚体进行碰撞,因为这种刚体不是为了移动而设计的。
适合加到不会移动的固定物体上,比如 墙、树 等

所选的选项将定义:
移动(位置和旋转)行为
碰撞体相互作用

Body Type 发生变化时,各种与质量相关的内部属性都将立即重新计算,并且在游戏对象的下一个 FixedUpdate 期间需要重新估算连接到 2D 刚体的 2D 碰撞体的所有现有触点。根据触点数量以及连接到刚体的 2D 碰撞体数量,更改 Body Type 可能会导致性能变化

2D 碰撞体

2D 碰撞体组件可定义用于物理碰撞的 2D 游戏对象的形状。碰撞体是不可见的,其形状不需要与游戏对象的网格完全相同;事实上,粗略近似方法通常更有效,在游戏运行过程中难以察觉。

可用于 2D 刚体的 2D 碰撞体类型为:

用于圆形碰撞区域的 2D 圆形碰撞体。
用于正方形和矩形碰撞区域的 2D 盒型碰撞体。
用于自由形状碰撞区域的 2D 多边形碰撞体。
用于自由形状碰撞区域和非全封闭区域(例如圆凸角)的 2D 边界碰撞体。
用于圆形或菱形碰撞区域的 2D 胶囊碰撞体。
用于合并 2D 盒型碰撞体与 2D 多边形碰撞体的 2D 复合碰撞体。

CompositeCollider2D 复合碰撞体

可合并其他碰撞体的碰撞体。

与大多数碰撞体不同,此碰撞体没有定义固有的形状。相反,此碰撞体将合并所设置的 2D 盒型碰撞体 (Box Collider 2D) 或 2D 多边形碰撞体 (Polygon Collider 2D) 的形状。2D 复合碰撞体使用所有此类碰撞体的顶点(几何体),并将这些顶点合并为由 2D 复合碰撞体本身控制的新几何体。

2D 盒型碰撞体和 2D 多边形碰撞体组件具有 Used By Composite 复选框。勾选此复选框即可将这些碰撞体附加到 2D 复合碰撞体。这些碰撞体还与 2D 复合碰撞体附加到同一 2D 刚体。启用 Used by Composite 时,其他属性会从该组件中消失,因为这些属性现在由附加的 2D 复合碰撞体控制。

当其他碰撞体的 Collider2D.usedByComposite 设置为 true 时,CompositeCollider2D 将其合并。

当复合碰撞体使用碰撞体时,Editor 将忽略并且不显示 Collider2D.sharedMaterial、Collider2D.isTrigger 和 Collider2D.usedByComposite 属性。而是使用 CompositeCollider2D 上的相同属性。应将复合碰撞体的这些属性设置为将所有碰撞体均合并到复合碰撞体中。

复合碰撞体只能合并 BoxCollider2D 和 PolygonCollider2D。

触发器

触发器(触发碰撞器)是一种特殊类型的碰撞体。触发器不会阻止移动,但是物理系统仍会检查角色是否会与触发器碰撞。当你的角色进入触发器时,你将收到一条消息,以便你可以处理该事件。

一句话总结:触发器碰撞体只监测碰撞,不阻止移动;碰撞时,可接收到消息,根据需求添加相关处理代码
在使用触发器的游戏对象上添加脚本组件,在其中添加事件
void OnTriggerEnter2D(Collider2D other)
此事件会在每次被碰撞时执行,将需要的逻辑代码写入事件方法中即可。

类(class)

c#中支持面向对象程序设计中的封装概念,对数据成员的保护。数据成员变量,默认一般都应该设置为私有,只能通过当前类的方法或属性进行访问。属性是公有的,可以通过取值器get,赋值器set设定对应字段的访问规则,满足规则才能访问

Collision2D 2D 碰撞细节类

Collision2D API

该类对象是回调函数返回的碰撞细节。用来描述碰撞过程本身

碰撞细节由 MonoBehaviour.OnCollisionEnter2D、MonoBehaviour.OnCollisionStay2D 和 MonoBehaviour.OnCollisionExit2D 回调返回。它详细说明了碰撞中涉及哪些 Collider2D 和 Rigidbody2D 对象,以及 Collider2D 相遇的接触点。

可以通过碰撞细节事件函数(回调函数)来编写两个刚体碰撞后的游戏逻辑

2D 精灵动画 2D Sprite Animation

在这里插入图片描述
unity 动画工作流程
动画剪辑(Animation clip)是动画的基本组成部分,包含某些对象应如何随时间改变其位置、旋转或其他属性的相关信息。

动画剪辑一般和不同的状态相挂接,比如“静止状态”挂接站立不移动时的动画、“奔跑状态”挂接跑动的动画剪辑…

Animator Controller 用来控制不同状态下动画的播放,以及状态间的转换

所有这些部分(动画剪辑、Animator Controller 和 Avatar)都通过 Animator 组件一起附加到某个游戏对象上。

Animation clip 动画剪辑
可以是外部导入,也可以在 Unity 中创建

外部导入的,一般是角色骨骼动画;unity 中创建的,一般是轨迹移动动画,在 Animation 窗口中创建

游戏对象任何组件中的任何属性都可以进行随时间变化的动画处理。比如:随时间推移而变化的颜色,也可以是大小变化
Animation 窗口被分为两个部分:

左侧用于动画化属性
右侧的时间轴显示每个属性的关键帧

Animator Controller 动画控制器
用状态机来控制不同的动画剪辑,在 Animator 窗口中编辑
Animator 分为两个部分,左侧是 Layers 和 Parameters,右侧是动画状态机 (Animation State Machine):

第 1 部分:Layers 和 Parameters
Layers 可用于 3D 动画,因为你可以将动画用于角色的不同部分。
Parameters 由我们的脚本用来向 Controller 提供信息。
第 2 部分:动画状态机
动画状态机以图形方式显示动画的所有状态以及如何从一段动画过渡到另一段动画。
混合树 (Blend Tree),这种混合树允许你根据参数来混合多段动画。

Cinemachine

使你可以创建复杂的 3D 摄像机设置,从而允许在多个摄像机之间移动和切换。

1.要开始使用 Cinemachine,你需要在顶部菜单栏上选择 Cinemachine > Create 2D Camera 条目,从而将 Cinemachine 2D 摄像机添加到场景中。
2.此时将创建一个名为 CM vcam1 的新游戏对象(表示 Cinemachine Virtual Camera 1)。
如上所述,Cinemachine 可与多个摄像机配合使用,并根据游戏的需要在多个摄像机之间进行切换,例如在对话中对某个镜头与反拍镜头进行切换。
3.为此,Cinemachine 使用虚拟摄像机,你可以在每个虚拟摄像机上选择不同的设置,然后告诉实际摄像机(在 Hierarchy 中名为 Main Camera)当前哪个虚拟摄像机处于活动状态,以便可以复制设置。
让 Cinemachine 设置摄像机以跟随角色。只需将你的主角从 Hierarchy 拖放到 vcam Inspector 的 Follow 属性
Cinemachine Confiner 定义一些边界。可将摄像机限制在特定边界范围内,这样就不会显示地图边界之外的对象。

UI

Rect Transform 矩形变换
矩形变换像常规变换一样具有位置,旋转和比例,但它还具有宽度和高度表示矩形的尺寸。
Pivot 枢轴(轴心)
旋转、大小和缩放修改都是围绕轴心进行的,因此轴心的位置会影响旋转、大小调整或缩放的结果。
Anchors 锚点
锚点在场景视图中显示为四个小三角形手柄(四叶花)。每个叶子位置对应矩形的四个顶点。当描点随父对象变换时,矩形的顶点与对应的锚点相对位置必须保持不变。
Mask 遮罩
两个图像,一个是原图像,一个是遮罩图像,显示的是原图像,遮罩图像控制原图像的显示范围,只显示背遮罩区域的原图像。

所以可以通过更改遮罩图像的大小,来不失真地显示原图像的部分区域
静态成员
菜鸟教程
可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员,并且被所有类对象所共享。

射线投射

这个功能在交互式应用程序中非常有用。
射线投射是将射线投射到场景中并检查该射线是否与碰撞体相交的行为。射线具有起点、方向和长度。之所以使用射线“投射”这种说法,是因为要从射线的起点一直到终点进行测试。
在这里,你将从主角的位置朝着她目光的方向投射射线,投射距离很小,例如 1 或 1.5 个单位。
RaycastHit2D API

粒子系统 (Particle System)

**Start Lifetime:**粒子的生命周期是指粒子在屏幕上被粒子系统销毁之前存在的时间。如果在 Scene 视图中缩小,你会看到所有粒子都差不多在同一位置消失。这是因为粒子的初始速度和生命周期都相同,因此最终在相同的距离处被销毁。
**Start Size:**这是粒子创建后的大小。现在只设置了一个数字,因此所有粒子的大小都相同。和上面一样,选择 Random Between Two Constants 并分别设置为 0.3 和 0.5,这样来引入一些随机性。粒子现在变小了且大小不同,但移动速度仍然过快。
**Start Speed:**通过将这个属性设置为 Random Between Two Constants,可以降低粒子的初始移动速度并增加一些随机性。将两个常量分别设置为 0.5 和 1。
AsyncOperation
在你不主动设置AllowSceneActivation = false的情况下,isDown会随着progress的增长(从0增长到1)自动变成true,所以会自动跳转场景;

如果你不想让场景进行自动跳转,你就需要在异步加载时设置allowSceneActivation = false,这会使场景不进行自动跳转,原因是isDown在allowSceneActivation = false 的情况下 永远不会为true,并且此时的aysncOperation.progress也最多可以加载到0.9(90%),当你允许你的场景跳转时(设置allowSceneActivation = true),progress加载最后的10%,直到progress = 1, isDown此时自动变为true,异步加载完成,实现场景跳转。

函数

Mathf.Clamp

限制value的值在min和max之间, 如果value小于min,返回min。 如果value大于max,返回max,否则返回value

public static float Clamp(float value, float min, float max);
Debug.Log

用于在游戏运行时输出调试信息。使用方法:在代码中使用 Debug.Log(“要输出的信息”) 即可,例如:Debug.Log(“玩家坐标:” + player.transform.position)。输出的信息可在 Unity 的控制台中查看。
Debug.Log 和 print 的区别:Debug 是一个密闭的类, print 是 MonoBehaviour 的一个成员。所以在使用的范围上, print 必须要继承 MonoBehaviour 类,而 Debug 不用}

GetComponent

可以在游戏对象上获取其组件。它接受一个参数,表示要获取的组件的类型。如果游戏对象上没有该类型的组件,则会返回 null。

例如,如果你想要获取一个游戏对象上的 Rigidbody 组件,你可以使用以下代码:

Rigidbody rb = gameObject.GetComponent();

这会将游戏对象上的 Rigidbody 组件赋值给变量 rb。

OnTriggerEnter2D(Collider2D)

当另一个对象进入附加到该对象的触发碰撞体时发送(仅限 2D 物理)。

有关另一个碰撞体的更多信息在调用期间传递的 Collider2D 参数中报告。

此消息将发送到触发器碰撞体 2D 和触发器碰撞体 2D 所属的 Rigidbody2D(如果有),以及接触触发器的 Rigidbody2D(如果没有 Rigidbody2D,则发送给碰撞体 2D)。

MovePosition (Vector2 position);

将刚体移动到 /position/。

通过计算在下一次物理更新期间将刚体移动到指定 position 所需的适当线速度来将刚体移动到该位置。在移动过程中,重力或线性阻力都不会影响刚体。这使得对象能够快速从现有位置穿过世界移动到指定的 /position/。

由于该功能允许刚体穿过世界快速移动到指定的 /position/,因此附加到刚体的任何碰撞体都将按预期作出反应,也就是说,它们将产生碰撞和/或触发。这也意味着如果碰撞体产生碰撞,则将影响到刚体的运动,并可能阻止刚体在下一次物理更新期间到达指定的 /position/。如果是运动刚体,则任何碰撞都不影响刚体本身,只会影响任何其他动态碰撞体。

2D 刚体对其移动速度有固定限制,因此在短时间内尝试移动较远的距离会导致刚体无法在下一次物理更新期间到达指定 /position/。建议仅将该函数用于相对较短距离的移动。

请务必注意, 实际的位置更改只在下一次物理更新期间进行, 因此重复调用该方法而不等待下一次物理更新将导致使用最后一次调用。 因此,建议在 FixedUpdate 回调期间调用该函数。

Mathf.Approximately

public static bool Approximately (float a, float b);
描述
比较两个浮点值,如果它们相似,则返回 true。

浮点不精确性使得使用相等运算符比较浮点数不精确。 例如,(1.0 == 10.0 / 10.0) 不会每次都返回 true。 Approximately() 比较两个浮点数,如果它们相互之间的差值处于较小值 (Epsilon) 范围内,则返回 true。

SetFloat

public void SetFloat (string name, float value);
public void SetFloat (string name, float value, float dampTime, float deltaTime);
public void SetFloat (int id, float value);
public void SetFloat (int id, float value, float dampTime, float deltaTime);
将浮点值发送到动画器以影响过渡。

在脚本中使用 SetFloat 将浮点值发送到动画器以激活过渡。在动画器中,定义哪些值如何影响某些动画的过渡方式。这在很多情况下都很有用,尤其是在动画循环中,例如在移动动画中可需要根据所施加的按钮压力来决定走或跑。

Set

public void Set(float newX, float newY)
{
x = newX;
y = newY;
}

Vector2.Normalize

public void Normalize ();
使该向量的 magnitude 为 1。

进行标准化时,向量方向保持不变,但其长度为 1.0。

请注意,此函数将更改当前向量。如果 要保持当前向量不变,请使用 normalized 变量。

如果该向量太小而无法标准化,则将其设置为零。

Instantiate

original 要复制的现有对象。
position 新对象的位置。
rotation 新对象的方向。
parent 将指定给新对象的父对象。
instantiateInWorldSpace 分配父对象时,传递 true 可直接在世界空间中定位新对象。传递 false 可相对于其新父项来设置对象的位置。
public static Object Instantiate (Object original, Vector3 position, Quaternion rotation, Transform parent);

Awake和Start

在加载场景时初始化包含脚本的活动 GameObject 时,或者在将先前非活动的 GameObject 设置为活动时,或者在初始化使用 Object.Instantiate 创建的 GameObject 之后,都将调用 Awake。 在应用程序启动前使用 Awake 来初始化变量或状态。

在首次调用任何 Update 方法之前启用脚本时,在帧上调用 Start。
类似于 Awake 函数,Start 在脚本生命周期内仅调用一次。但是,不管是否启用脚本,初始化脚本对象时都会调用 Awake。如果在初始化时未启用脚本,则可以在与 Awake 不同的帧上调用 Start。
Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。

RaycastHit2D

在 2D 物理中,有关射线投射检测到的对象的返回信息。

射线投射用于检测位于射线路径上的对象,在概念上类似于向场景中发射激光束并观察它命中的对象。RaycastHit2D 类由 Physics2D.Raycast 和其他函数使用,返回有关射线投射检测到的对象的信息。
射线投射使用的是physics2D.Raycast方法
参数1:射线投射的位置 2:投射方向 3.投射距离 4.射线生效的层

static instance 属性表示是静态的公共属性,因此可以在任何脚本中编写 UIHealthBar.instance,都会调用该 get 属性。set 属性是私有属性,因为我们不希望其他人能够从脚本外部进行更改。

UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth);
现在将 currentHealth 相对于 maxHealth 的比值提供给 UIHealthBar SetValue 函数。maxHealth 前面的 (float) 让 C# 将 maxHealth 视为一个浮点值。

使用瓦片地图创建世界
2D精灵动画
2D 初学者
2D物理入门

玩家

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
//68有代码总结,复习用
//89有动画剪辑总结

public class Rubycontroller : MonoBehaviour
{
    public GameObject GameOver;
    public GameObject GameOverPlayer;
    public GameObject projectilePrefab;
    public ParticleSystem healthEffect;
    public ParticleSystem Hit;
    AudioSource audioSource;
    public AudioClip hitClip;
    public float soundVol;
    public AudioClip launchClip;
    //申明刚体对象
    Rigidbody2D rigidbody2d;
    //获取用户输入
    float horizontal;
    float vertical;
    public float speed = 0.1f;
    public int maxhp=5;
    int currenthp;
    //设置玩家无敌的时间间隔
    public float timeInvincible = 2.0f;
    //设置是否无敌的变量
    bool islnvincible;
    //定义变量进行无敌时间的计时,无敌时间计时器
    float invincibleTimer;
    //声明一个动画管理者的组件
    Animator animator;
    //创建一个二维矢量,用来存储Ruby不动时看的方向
    //与机器人相比ruby可以站立不动,Movex和y都为0,这时转肽基就没法获取ruby静止时的朝向,所以需要手动设置
    Vector2 lookDirection = new Vector2(1, 0);
    //移动矢量
    Vector2 move;
    

    public int health
    {
        get { return currenthp; }
    }
    // Start is called before the first frame update
    private void Start()
    {
        GameOver.SetActive(false);
        锁帧
        //QualitySettings.vSyncCount = 0;
        //Application.targetFrameRate = 10;
        //初始生命值满血
        currenthp = maxhp;
        //获取当前对象所在的刚体组件
        rigidbody2d = GetComponent<Rigidbody2D>();
        //组件对象
        animator = GetComponent<Animator>();
        audioSource = GetComponent<AudioSource>();
    }


    // Update is called once per frame

    void Update()
    {
        //获取水平和垂直输入
        //按左会获得-1.0f,按右获得1.0f
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");
        获取位置
        //Vector2 position = transform.position;
        更改位置
        //position.x = position.x + speed * horizontal * Time.deltaTime;
        //position.y = position.y + speed * vertial * Time.deltaTime;
        传入
        //transform.position = position;
        //判断是否处于无敌状态,来进行计时器的倒计时
        if (islnvincible)
        {
            //如果无敌,进入倒计时
            //每次update减去一帧所消耗的时间
            invincibleTimer = invincibleTimer - Time.deltaTime;
            //invincibleTimer -= Time.deltaTime;
            //直到计时器中时间用完
            if (invincibleTimer < 0)
            {
                //取消无敌状态
                islnvincible = false;
                
            }
        }
        //创建一个二维矢量对象来表示Ruby移动的数据信息
        move = new Vector2(horizontal, vertical);
        //如果move中的x/y不为零,表示正在移动
        //将ruby面向方向设置为移动方向
        //停止移动,保持以前方向,所以这个if结构用于转向时重新赋值面朝方向
        if (!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))
        {
            //将现在ruby的面朝方向设置为移动方向
            lookDirection.Set(move.x, move.y);//lookDirection.x=move.x
            //使向量长度为1,可以将此方法称为向量的归一化方法
            //通常用在表示方向,而非位置的向量上
            //因为blend tree 中表示方向的参数取值范围是-1到1,
            //所以一般用向量作为Animator.SetFloat方法的参数时,一般要对向量进行“归一化”处理
            lookDirection.Normalize();
        }
        //传递方向给TRee
        animator.SetFloat("Look X", lookDirection.x);
        animator.SetFloat("Look Y", lookDirection.y);
        //传递Ruby的速度给Tree
        //矢量的magnitue属性,用来返回矢量的长度
        animator.SetFloat("Speed", move.magnitude);
        if (Input.GetKeyDown(KeyCode.C))
        {
            Launch();
        }
        //按x射线投射
        if (Input.GetKeyDown(KeyCode.X))
        {
            //射线投射使用的是physics2D.Raycast方法
            //参数1:射线投射的位置 2:投射方向 3.投射距离 4.射线生效的层
            RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));
            if (hit.collider != null)
            {
                Debug.Log($"射线投射碰撞到的对象是:{hit.collider.gameObject}");
                NPC npc = hit.collider.GetComponent<NPC>();
                if (npc!= null)
                {
                    npc.DisplayDialog();
                }
            }
        }
        if (currenthp== 0)
        {
            GameOver.SetActive(true);
            Destroy(GameOverPlayer);
            SceneManager.LoadScene("scenes0");
        }

    }

    private void FixedUpdate()
    {

        Vector2 position = transform.position;
        //position.x = position.x + speed*horizontal*Time.deltaTime;
        //position.y = position.y + speed*vertical*Time.deltaTime;
        position = position + speed * move * Time.deltaTime;
        rigidbody2d.MovePosition(position);
        //rigidbody2D.position = position;
        
    }
   
    public void ChangeHp(int amount)
    {
        //假设玩家受伤害的时间间隔为2秒
        if (amount < 0)
        {
            if (islnvincible)
            {
                //无敌状态不伤血,跳出函数
                return;
            }
            //当不是无敌状态时,执行以下代码
            //重置无敌状态为真
            islnvincible = true;
            //重置无敌时间
            invincibleTimer = timeInvincible;
            //播放受伤动画
            animator.SetTrigger("Hit");
            PlaySound(hitClip, soundVol);
            Instantiate(Hit, rigidbody2d.position + Vector2.up * 2.0f, Quaternion.identity);

        }
        if(amount>0)
        {
            Instantiate(healthEffect, rigidbody2d.position + Vector2.up * 2.0f, Quaternion.identity);
        }
        //限制生命值
        currenthp = Mathf.Clamp(currenthp + amount, 0, maxhp);
        //控制台输出生命
        Debug.Log("当前生命值:" + currenthp+"/" + maxhp);
        UIHealthBar.Instance.SetValue(currenthp / (float)maxhp);
        
    }
    //发射子弹
    void Launch()
    {
        GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position + Vector2.up * 1.0f, Quaternion.identity);
        Projecttilecontrol projectile = projectileObject.GetComponent<Projecttilecontrol>();
        projectile.Launch(lookDirection, 300);
        animator.SetTrigger("Launch");
        PlaySound(launchClip, soundVol);
    }
    public void PlaySound(AudioClip audioClip,float soundVol)
    {
        audioSource.PlayOneShot(audioClip, soundVol);
    }
}

敌人

using System.Collections;
using System.Collections.Generic;
using UnityEditor.SceneManagement;
using UnityEngine;

public class Robotcontroller : MonoBehaviour
{
    bool broken = true;
    //每次刷新的距离
    public float speed;
    public bool vertical;
    //声明刚体对象
    Rigidbody2D rigidbody2d;
    //转向时间
    public float changeTime = 3.0f;
    //计时器
    float timer;
    //方向
    int direction = 1;
    Animator animator;
    public ParticleSystem smoke;
    GameObject collectObject;
    public GameObject projectilePrefab;
    public GameObject healthCollectible;
    void Start()
    {
        rigidbody2d = GetComponent<Rigidbody2D>();
        timer = changeTime;
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        if (!broken)
        {
            return;
        }
        //操作计时器
        timer -= Time.deltaTime;
        if (timer < 0)
        {
            //重置
            direction = -direction;
            timer = changeTime;
        }
    }
    private void FixedUpdate()
    {
        if (!broken)
        {
            return;
        }
        //获取当前刚体位置
        Vector2 position = rigidbody2d.position;
        if (vertical)
        {
            position.y = position.y + Time.deltaTime * speed * direction;
            animator.SetFloat("move x", 0);
            animator.SetFloat("move y", direction);
        }
        else
        {
            position.x = position.x + Time.deltaTime * speed * direction;
            animator.SetFloat("move x", direction);
            animator.SetFloat("move y", 0);
        }
        rigidbody2d.MovePosition(position);
    }
    private void OnCollisionEnter2D(Collision2D other)
    {
        Rubycontroller rubycontroller = other.gameObject.GetComponent<Rubycontroller>();
        if (rubycontroller != null)
        {
            //角色掉血
            rubycontroller.ChangeHp(-1);
        }
    }
    public void Fix()
    {
        broken = false;
        rigidbody2d.simulated = false;
        animator.SetTrigger("Fixed");
        //Destroy(smoke);
        smoke.Stop();
        GetComponent<AudioSource>().Stop();
        RandomDrop();
    }

    public GameObject GetGameObject()
    {
        return gameObject;
    }

    //随机掉落子弹或者草莓
    public void RandomDrop()
    {
        //草莓与子弹掉落概率为1:2。12~19生成草莓,20~31生成子弹,
        int num = (int)(Random.Range(12, 31) / 10);
        if (num % 2 == 0)
        {
            collectObject = Instantiate(healthCollectible, rigidbody2d.position, Quaternion.identity);
        }
        if (num % 2 == 1)
        {
            collectObject = Instantiate(healthCollectible, rigidbody2d.position, Quaternion.identity);
        }
    }
}

NPC

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class NPC : MonoBehaviour
{
    public float displayTime = 4.0f;
    public GameObject dialogBox;
    float timerDisplay;
    //创建游戏对象获取TMP组件
    public GameObject dlgTxtProGameObject;
    //创建游戏组件类对象
    TextMeshProUGUI tmTxtBox;
    //当前页数
    int currentPage = 1;
    //总页数
    int totalPages;
    void Start()
    {
        dialogBox.SetActive(false);
        timerDisplay = -1.0f;
        tmTxtBox = dlgTxtProGameObject.GetComponent<TextMeshProUGUI>();    }

    // Update is called once per frame
    void Update()
    {
        //获取对话框组件中,对话文字的总页数
        totalPages = tmTxtBox.textInfo.pageCount;
        if (timerDisplay >= 0.0f)
        {
            if (Input.GetKeyUp(KeyCode.Space))
            {
                if (currentPage < totalPages)
                {
                    currentPage++;
                }
                else
                {
                    currentPage = 1;
                }
                tmTxtBox.pageToDisplay = currentPage;
            }

            timerDisplay = timerDisplay - Time.deltaTime;
        }
        else
        {
            dialogBox.SetActive(false);
        }
    }
    public void DisplayDialog()
    {
        timerDisplay = displayTime;
        dialogBox.SetActive(true);
    }
}

血条

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIHealthBar : MonoBehaviour
{
   public static UIHealthBar Instance { get; private set; }
    public Image mask;
    float originalSize;
    private void Awake()
    {
        Instance = this;
    }
    void Start()
    {
        originalSize = mask.rectTransform.rect.width;
    }
    public void SetValue(float value)
    {
        mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,originalSize* value);
    }

  
}

草莓

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HealthCollectible : MonoBehaviour
{
    public AudioClip healthClip;
    public float soundVol;
    public int amount = 1;
    //添加触发器碰撞
    
    private void OnTriggerEnter2D(Collider2D other)
    {
       
        Debug.Log($"和当前物体发生的碰撞是:{other}");
        //获取游戏对象的脚本对象
        Rubycontroller rubycontroller = other.GetComponent<Rubycontroller>();
        if (rubycontroller != null)
        {
            if (rubycontroller.health < rubycontroller.maxhp)
            {
                //更改生命值
                rubycontroller.ChangeHp(amount);
                //草莓消失
                Destroy(gameObject);
                rubycontroller.PlaySound(healthClip,soundVol);
                
            }
            else
            {
                Debug.Log("血量已满,无需加血");
            }
        }
        else
        {
            Debug.LogError("没有获取到游戏组件");
        }


    }
}

异步加载

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class LoadManger : MonoBehaviour
{
    public GameObject loadScreen;
    public Slider slider;
    public Text text;
    public void LoadNextLevel()
    {
        StartCoroutine(LoadLevel());
    }
    IEnumerator LoadLevel()
    {
        loadScreen.SetActive(true);
        AsyncOperation operation = SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex + 1);
        operation.allowSceneActivation = false;
        while (!operation.isDone)
        {
            slider.value = operation.progress;
            text.text = operation.progress * 100 + "%";
            if (operation.progress >= 0.9f)
            {
                slider.value = 1;
                text.text = "Press AnKey to continue";
                if (Input.anyKeyDown)
                {
                    operation.allowSceneActivation = true;
                }
            }
            yield return null;
        }
    }
}

扣血区域

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DamageZone : MonoBehaviour
{
    
    public int demageNum=-1;
    private void OnTriggerStay2D(Collider2D other)
    {
        Rubycontroller rubycontroller = other.GetComponent<Rubycontroller>();
        if (rubycontroller != null)
        {
            rubycontroller.ChangeHp(demageNum);
           
        }
       
    }
}

子弹

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Projecttilecontrol : MonoBehaviour
{
    public AudioClip lunchClip;
    public float soundVol;
    Rigidbody2D rigidbody2d;
    public ParticleSystem FixEffect;
    // Start is called before the first frame update
    void Awake()
    {
        rigidbody2d = GetComponent<Rigidbody2D>();
    }
    private void Update()
    {
        if (transform.position.magnitude > 50.0f)
        {
            Destroy(gameObject);
        }
    }

    // Update is called once per frame
    public void Launch(Vector2 direction,float force)
    {
        //通过对刚体对象调用物理系统的AddForce方法对其施加一个力使其移动
        rigidbody2d.AddForce(direction * force);
    }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        Robotcontroller robotcontroller = collision.collider.GetComponent<Robotcontroller>();
        if (robotcontroller != null)
        {
            robotcontroller.Fix();
            Instantiate(FixEffect, transform.position, Quaternion.identity);
        }
        //向控制台输出信息,打印当前游戏对象
        Debug.Log($"齿轮子弹碰撞到了:{collision.gameObject}");
        Destroy(gameObject);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值