教程网址:Unity2D官方入门案例 - Ruby’s Adventure
初识 Unity
资源商店:Window -> Asset Store
Unity 五大基本视图面板:
1、项目面板 (Project Window)
2、层次面板 (Hierarchy Window)
3、场景面板 (Scene Window)
4、游戏演示面板 (Game View)
5、检视面板 (Inspector Window)
输出面板 (Console Window)
顶部的工具栏:
- Play (运行按钮):运行按钮用于测试当前在层次面板中放置的游戏物体加载后的游戏运行情况
- Pause (暂停按钮):暂停在游戏面板里的游戏测试,帮助发现游戏问题
- Step (逐帧运行按钮):逐帧运行按钮用于逐帧遍历暂停的游戏场景
-
Hand Tool (视野查看工具):可以拖拽移动当前视野,快捷键 Q -
Move Tool (移动工具):可以将选中的物体进行移动,快捷键 W -
Rotate Tool (旋转工具):可以将选中的物体进行旋转,快捷键 E -
Scale Tool (缩放工具):可以将选中的物体进行大小缩放,快捷键 R -
Rect Transform Tool (矩形工具):可以查看和编辑2D游戏物体的rectTransform组件,可以移动缩放旋转
2D和UI等等游戏物体,快捷键T。按下shift键可以等比缩放游戏对象
- Rotate Move Or Scale Tool (多功能工具):可以对选中的游戏物体进行移动,旋转,缩放,快捷键 Y
其他快捷键:
- 聚焦某一个游戏物体:选中游戏物体,按下 F
- 视野移动:按下鼠标右键或鼠标中键
- 调整视野大小:滚动鼠标中键
- 3D游戏视野:按下鼠标右键 + 键盘上的 WSADQE 键进行视野移动
主角 Ruby 的创建
坐标:场景中的所有物体都有 x、y、z 三个坐标(2D中一般只需要用到 x、y)
C#脚本:包含命名空间,类(组件),函数(功能),其中两个重要的函数:
Start() :当游戏开始时,Unity 只 在Start 中执行一次代码,且在第一帧更新之前调用Update() :在创建该图像(帧)之前,Unity 会执行在所有游戏对象的 Update 函数中编写的代码(1秒钟大约调用60次)。在此函数中,可以编写任何希望在游戏中连续发生的事情(例如,读取玩家的输入、移动游戏对象或计算累计时间)。
帧:为了给人留下运动的印象,游戏(就像电影)是高速播放的静止图像。通常在游戏中,30或60个图像显示在一秒钟内,这些图像都称为帧。
脚本挂载方式:
- 直接将脚本拖到 Ruby 的 Inspector 中
- 在 Inspector 中点击 Add Component,加载我们写好的脚本
最简单的脚本:让 Ruby 游戏开始后不停的往右移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RubyController : MonoBehaviour
{
void Start()
{
}
void Update()
{
Vector2 position = transform.position;
position.x += 0.1f;
transform.position = position;
}
}
Ruby 的移动控制
输入设置:Edit > Project Settings > Input Manager
如果想要游戏物体以每秒多少的速度移动可以乘上Time.deltaTime
实现 Ruby 键盘控制上下左右移动的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RubyController : MonoBehaviour
{
void Start()
{
}
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector2 position = transform.position;
position.x += 3 * horizontal * Time.deltaTime;
position.y += 3 * vertical * Time.deltaTime;
transform.position = position;
}
}
使用 TileMap 创建世界地形
按住 ctrl 移动游戏物体,可以移动固定长度
- Edit > Grid and Snap > Move 设置固定长度
TileMap 创建:Hirerachy > 右键 > 2D Object > TileMap
-
网格 (Grid):可以用来将游戏对象均匀地放置在其单元格中 -
瓦片地图 (Tilemap):是网格的子游戏对象。由Tiles(瓦片)组成的 -
精灵 (Sprite):纹理的容器。大型纹理图集可以转换为精灵图集(Sprite Sheet) -
瓦片 (Tile):一种特殊的精灵。使用瓦片就像在画布上画画一样,画画时可以设置一些颜色和属性 -
调色板 (Palette):保存瓦片,将它们绘制到网格上 -
笔刷 (Brush):用于将画好的东西绘制到画布上。使用 Tilemap 时,可以在多个笔刷中任意选择, 绘制出线条、方块等各种形状
创建调色板:Window > 2D > Tile Palette
- 将图片拖到调色板中即可变成 Tile 文件,然后可以用调色板的工具绘制地图
每单位像素数 (Pixels Per Unit):选中精灵资源在 Inspetor 面板里会看到,表示在一个 Unity 单位中一行或一列有几个像素点
- 如果瓦片拖到界面上后,瓦片之前有间距,可以调整这个参数
切割图集:选中一张精灵图片资源,将 SpriteMode从single 改为 mutiple,可制作成图集,再点击 SpriteEditor 可进行切割。切割有三种形式:自动,按像素切割,按行列切割
- 对于一张比较大的精灵图,我们拖到 Scene 上看似占用比较大的空间,其实只占用了一个格子,为了让它的实际占用很看起来一样大,需要将这张图切割成图集
调色板的工具与快捷键
- 选择工具 (Select Tool):选择想要使用的瓦片,可以点击选择或拖拽多选选择,快捷键 S
- 移动工具 (Move Tool):可以选择并移动瓦片位置,快捷键 M
- 画笔工具 (PaintBruch Tool):选择瓦片并在场景面板中绘制,快捷键 B
- 方形区域填充工具 (Fill Box Tool):选择一个或一片区域大小的瓦片,绘制区域小于选中区域时会部分绘制选中内容的纹理,等大区域时会全部显示,大于则会平铺显示,快捷键 U
- 取色工具 (Picker Tool):取样选中瓦片并绘制,快捷键 I
- 橡皮擦工具 (Eraser Tool):擦去在场景中已绘制瓦片,快捷键 D
- 填充工具 (Fill Tool):大范围绘制选中的瓦片,快捷键 G
Edit 按钮激活后,是对调色板中瓦片的操作,未激活是对 Scene 面板中瓦片的操作
层级 (Layer):可以在 2D 游戏物体中的 SpriteRenderer 和地形 TilemapRenderer 组件中的 Order in layer 属性中去设置层级的大小(值越大,越后渲染,值大的游戏物体会覆盖值小的游戏物体)
丰富游戏世界
根据游戏对象的某个轴向去绘制游戏对象:Edit > Project Settings > Graphics > Camera Settings > Transparency Sort Mode,调整想要决定绘制的轴向,值的正负决定是大或小的先绘制,例如:Y: 1,则Y坐标越小的游戏对象越后绘制(渲染),不会被遮挡。
- 由于这个是2D游戏,一般来说 Z。设为0,Y 设为1即可
- 注意,只有两个物体在同一个层级 (Order in layer),才能实现上面的效果
轴心点 (Pivot):是可以自定义的特殊点,充当精灵的“锚点”。精灵以此为支点进行旋转,坐标点位置则是指轴心点的位置
修改精灵渲染顺序点:将游戏物体中的 SpriteRenderer 组件中的 SpriteSortPoint 从 Center 设置为 Pivot
修改精灵资源轴心点的两种方法:
- 单张图片的精灵资源可选中 Pivot 属性进行设置
- 单张和图集都可使用 SpriteEditor 去自定义轴心点位置
预制体 (Prefab):Unity 中的一种特殊资源,预制体就是一个或者一系列组件的集合体,可以使用预制体实例化克隆体,后续可对克隆体属性进行统一修改。
进入预制体编辑模式的方法:
- 双击预制体
- 选中预制体资源,点击 openPrefab
- 点击蓝色克隆体名称右边的小箭头
- 点击选中克隆体,点击检视面板中的 Open 按钮
Unity 中的物理系统
Unity中的物体系统:模拟游戏世界中物体的运动受力与碰撞
刚体组件 (Rigidbody):使游戏物体能获得重力,接受外界的受力和扭力功能的组件,可以通过脚本或是物理引擎为游戏对象添加刚体组件
碰撞器组件 (BoxCollider):使游戏物体具有跟挂载刚体组件的游戏物体发生碰撞能力的组件
发生碰撞检测的必要条件:双方必须都有碰撞器,且一方有刚体,最好是运动的一方(因为Unity为了性能的优化,刚体长时间不发生碰撞检测会休眠)
2D 游戏物体因为碰撞受力会发生 Z 轴上的角度旋转,可以通过 Rigidbody2D > Constraints > FreezeRotation 去冻结 Z 轴上的角度旋转
2D 物体碰撞抖动的原因:物理系统控制的位移与开发者编写代码产生的位移起了冲突,需要使用物理系统里的位移方式来进行位移,也就是借助刚体组件的移动方法来移动:
物理系统的代码与开发者编写的移动代码发生了冲突,开发者让游戏物体进行了移动,物理系统检测到不合理再弹出,反复这个过程出现了抖动现象
private Rigidbody2D rigidbody2D = GetComponent<Rigidbody2D>();
rigidbody2D.MovePosition(position);
设置河流为不可进入:使用一个空的 GameObject 覆盖到河流上,设置碰撞器组件、范围 (Edit Collider);这个做法有局限性,但是如果河流很多,后面要改不太方便
给 Tilemap 游戏地形上添加碰撞器:在 Tilemap 游戏物体上添加 Tilemap Collider 2D 组件,将不需要添加碰撞器的瓦片资源选中,将 Collider Type 从 Sprite 改为 None
合并瓦片碰撞器:在 Tilemap 游戏物体上添加 Composite Collider 2D 组件,在 Tilemap Collider 2D 组件中启用 Used by Composite 属性,之后将 Rigidbody 2D 组件中的 Rigidbody Body Type 属性设置为 Static(防止地形移动)
道具的添加与交互
C# 中的访问级别:
- public(公有的) :任何代码均可以访问,应用于所有类或成员
- protected(受保护的) :只能在当前类或其派生类中使用,应用于内嵌类或成员
- private(私有的) :只能在当前类中使用,应用于内嵌类或成员
- …
当把一个变量设置成 Public 时,可以在 Inspector 中去修改这个变量的值
变量赋值顺序:变量声明时赋值 -> Inspector 赋值 -> Start() 中赋值
触发器 (Trigger):触发某一个事件的机关,在 BoxCollider 组件中把 isTrigger 属性勾上可以把碰撞器做成触发器
- 触发检测的必要条件:双方都有碰撞器,一方是触发器,一方有刚体(最好是运动的一方)
为了使某个变量只能够在内部修改,外部只读,可以给它制作一个属性值:
public int maxHealth = 5;
private int currentHealth;
public int Health { get { return currentHealth; } }
在脚本中,gameObject 指当前脚本挂载的游戏物体对象,transform 指当前脚本挂载游戏物体对象身上的 transform 组件,其他组件同理:
public class HealthCollectible : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
RubyController rubyController = collision.GetComponent<RubyController>();
if (rubyController != null)
{
if (rubyController.Health < rubyController.maxHealth)
{
rubyController.ChangeHealth(1);
Destroy(gameObject);
}
}
}
}
敌人的开发
如果想要放大一张有很多个图形组成的精灵资源,直接放大会变形,则需要将 SpriteRenderer 组件中 DrawMode 设置成 Tiled(平铺),之后将 TileMode 改为 Adaptive(自适应)
出现黄色警告,则将精灵图片的 Mesh Type 改为 Full Rect ,即可使图片填满整个矩形区域
拉大图片后,碰撞检测区域不会自动变,将 Box Collider 中 Auto Tiling 选中即可自动变化
触发检测调用的方法:
OnTriggerEnter :进入触发器触发OnTriggerStay :在触发器内部每帧触发OnTriggerExit :出来触发器触发
触发的游戏对象:Collider2D 可以直接调用 GetComponent 方法:
private void OnTriggerEnter2D(Collider2D collision)
{
RubyController rubyController = collision.GetComponent<RubyController>();
}
碰撞检测调用的方法:
OnCollisionEnter OnCollisionStay OnCollisionExit
获取碰撞到的游戏物体:Collision2D 无法直接调用 GetComponent 方法,可以先获取到 gameObject 再调用:
private void OnCollisionEnter2D(Collision2D collision)
{
RubyController rubyController = collision.gameObject.GetComponent<RubyController>();
}
Unity 中的动画系统
动画制作组件 (Animator):为了完成游戏对象动态效果的控制和创建,Unity 为我们提供的一个动画设计解决方案,同时也是一个组件
- 需要给想实现动画效果的物件挂载这个组件,然后选择对应的动画控制器
动画控制器 (Animator Controller):根据当前游戏对象的状态控制播放对应的游戏动画,也可以叫做动画状态机
动画 (Animation):Unity 中的动画资源,可以由动画控制器,代码去进行播放,也是一种组件(这种方式比较古老)
开发动画的步骤:
-
选中对应游戏物体打开动画面板 (Window -> Animation -> Animation),在动画面板中可以创建新的动画,第一次动画可直接创建,后续需要在动画名称上点击,在下拉菜单中点击 CreateNewClip 创建(也可以在 Project 面板里右键进行创建) -
创建动画后可以给动画添加该游戏物体上的所有组件所持有的属性改变的相应动画,点击 Preview 可以预览动画效果,点击右边红色圆点按钮可以进行录制操作,在 Inpector 里属性的改变会直接将关键帧添加在时间轴上,也可以右键在时间轴上添加关键帧 -
将若干个图片拖拽进动画窗口可自动添加制作成一个关键帧动画,可全选待鼠标变为双向箭头进行等比拉伸,延长动画时间,变慢动画速度
如果没有对应动画的图片资源,可以借由改变 SpriteRenderer 中的 Flip 属性去翻转动画
转换 (Transition) 用于在给定时间量内从一个动画状态 (Animation State) 平滑转换为另一个状态,转换指定为动画状态机 (Animation State Machine) 的一部分,如果转换迅速,则通常可从一个运动很好地转换为完全不同的运动
转化的使用:将动画拖拽进**动画控制器窗口 (Window -> Animation -> Animator)**中可以添加动画状态:
- Parameters 列表里可以添加配置参数,参数可用于设置动画过渡的条件
- 右键动画状态可设置默认动画以及添加过渡条件
- 选中过渡条件连线可在 Inpector 面板中的 Conditions 去设置参数条件
- 在代码中获取 Animator 组件之后可以使用 set + 变量类型 的方法去设置状态机里的参数值
- 如果想要让上一个动画瞬间过渡到下一个动画,需要把条件中的 HasExitTime 取消勾选,让动画没有退出时间,而是可以瞬间退出,且需要把 TransitionDuration 过渡时间间隔设置为 0
混合树 (Blend Tree):用于允许通过按不同程度组合所有动画的各个部分来平滑混合多个动画。各个运动参与形成最终效果的量使用混合参数进行控制,该参数只是与动画器控制器关联的数值动画参数之一;要使混合运动有意义,混合的运动必须具有相似性质和时间;混合树是动画器控制器中的特殊状态类型
- 一些拥有比较接近性质的动画可以使用混合树,例如涉及 上下左右 四个方向的动作…
代码控制:
- Vector 类型的变量既可以用来表示坐标,也可以用来表示向量
Mathf.Approximately(a,b) 可以用来判断两个浮点数 a、b 之间是否近似相等a.Normalize() 方法可以用来使向量a单位化;a.magnitude 是可以取到当前a的向量模长
private Animator animator;
private Vector2 lookDirection = new Vector2(1, 0);
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector2 move = new Vector2(horizontal, vertical);
if (!Mathf.Approximately(move.x, 0) || !Mathf.Approximately(move.y, 0))
{
lookDirection.Set(move.x, move.y);
lookDirection.Normalize();
}
animator.SetFloat("Look X", lookDirection.x);
animator.SetFloat("Look Y", lookDirection.y);
animator.SetFloat("Speed", move.magnitude);
Vector2 position = transform.position;
position += speed * move * Time.deltaTime;
rigidbody2d.MovePosition(position);
}
子弹的开发
通过物理系统给游戏对象施加力的效果,需要先获取到刚体组件,然后调用 AddForce() 方法
监听玩家按下某个按键可以使用 Input.GetKeyDown(KeyCode.* ),* 代表某个按键
实例化 (Instantiate):如果想要动态生成一个游戏物体,需要调用实例化方法来对预制体进行克隆
-
第一个参数是想要克隆的预制体资源,第二个参数是需要生成到的位置,第三个参数是旋转角度 旋转角度跟检视面板里显示的是不一样的,是一个四元数类型的参数,不是 Vector3 类型的参数 Quaternion.identity 表示预制体默认旋转角度
GameObject projectileObject = Instantiate(projectilePrefab,
rigidbody2d.position, Quaternion.identity);
Projectile projectile = projectileObject.GetComponent<Projectile>();
projectile.Launch(lookDirection, 300);
animator.SetTrigger("Launch");
Awake() 先于 Start() 被调用,都是Unity内置封装的方法,它们属于生命周期函数
Awake 会在游戏物体实例化(生成)时就调用,Start*() 不会
层级 (Layer) 是 Unity 中场景物体的一种属性,可用于设置物理碰撞关系
- 设置是否发生碰撞检测的层级:Edit > Project Settings > Physics 2D
- 在游戏物体身上要设置对应的图层
让某个游戏物体的刚体从当前物理系统中移除,不再发生碰撞检测,设置 rigidbody2d.simulated=false
Cinemachine 工具包的使用
Unity 提供了许多内置功能,大多数游戏和应用程序都需要这些功能;但是并非所有游戏都需要通过软件包提供更专业的功能,对有些类型的游戏来说,包只会增加使用它们的项目的大小,并且不易于它们游戏的更新
可以使用包管理器将各种包添加到项目中,它们将功能添加到项目中,这样开发者就不必自己编写代码,例如虚拟现实支持、后期处理效果等等
包管理:Window -> Package Manager -> 添加项目需要的工具包
Cinemachine 工具包可以快速创建复杂的相机设置,允许在多个相机之间移动,跟随和剪切
Cinemachine 使用:引入包后,顶部菜单栏 Cinemachine -> Create Virtual Camera,然后给 Main Camera 设置 CinemachineBrain 并且指定 Live Camera 为刚刚创建的 CM vcam
摄像机的投影模式:
- 透视 (Perspective):所有离开相机的线都会聚到一个点上,当它们离相机越远时,使物体看起来越小
- 正交 (Orthographic):所有平行线保持平行的地方
规定摄像机边界:可以给地形做一个碰撞器边界,在 CM vcam 里的 Cinemachine Confiner 中的 Boundingshape2D 设置一个带有 PolygonConllider2D 或是 CompositeCollider2D 的碰撞器的空游戏对象
规定 Ruby 移动的边界:在地图的四周用空气墙围起来
使相机跟随某个游戏物体:在 CM vcam 的 CinemachineVirtualCamera 设置 Follow 为 Ruby
Unity 中的粒子系统
粒子在交互应用中广泛用于效果,粒子系统会产生几十个甚至几百个粒子,这些粒子是具有方向、速度、寿命、颜色和许多其他特性的小图像。通过使用所有这些参数,粒子可以集体创建诸如烟雾、火花甚至火焰之类的效果。
创建粒子系统:右键点击 Effect > ParticleSystem,如果想要粒子系统使用精灵图片资源,则需要勾选 SheetAnimation,将模式改为 Sprites
-
在多个图片资源中随机产生:点击?按钮添加多个精灵资源,设置 StartFrame 为 Random Between Two Constants(常量代表使用的资源索引) -
禁止粒子在播放中更换图片资源:将 FrameOverTime 的动画曲线设置为水平点,删除线段末尾的关键帧 -
设置粒子系统的基础属性:点击 ParticleSystem 打开属性栏 Start Lifetime(起始生命周期):粒子被销毁前显示在屏幕上的时间 Start Size(开始大小):粒子创建时的大小 Start Speed(开始速度):粒子开始移动的速度 Size Over Lifetime:随着生命周期粒子大小的改变 Color Over Lifetime:随着生命周期粒子颜色的改变 … -
设置粒子不跟随游戏物体进行移动:粒子系统基础属性 > Simulation Space > 将 Local 改为 World -
在一段时间爆发产生大部分粒子:Emission
获取游戏物体身上的的组件的两种方式:
- 定义一个 private 的组件变量,通过
GetComponent 在代码中获取 - 定义一个 public 的组件变量,在 Unity 的 Inspector 面板中给该变量赋值
Unity 中的 UGUI
玩家不会有一个控制台窗口来查看最终游戏中的日志,为了给他们反馈,交互应用程序使用用户界面(简称 UI),它将图像和文本覆盖在场景上以显示信息,这也被称为平视显示器(HUD)
Unity 中的 UI 必须使用画布 (Canvas) 组件来对其进行渲染,画布定义了每个 UI 元素在屏幕上的呈现方式,并负责呈现它的所有子 UI 组件
- 创建画布的时候会同时创建 EventSystem(事件系统),来处理事件和与UI的交互,比如鼠标点击
- 画布的 Canvas 中也可以设置层级 Order In Layer
Canvas 组件中的 RenderMode(渲染模式):
-
Screen Space-Overlay(屏幕空间-覆盖):这个默认模式使 Unity 一直在游戏顶部绘制用户界面。大多数应用程序使用此模式是因为它们希望 UI 始终位于最上面,以提供所有信息。(最常用) -
Screen Space-Overlay(屏幕空间-相机):这将在与相机对齐的平面上绘制用户界面。该平面的大小将始终填充屏幕,因此可以四处移动相机,并且该平面将与相机一起移动,以显示与覆盖相同的效果。但是,因为平面是在世界上绘制的,而不是在屏幕上,所以世界上的对象可以在用户界面上绘制。(也就是说一些游戏物体可能会覆盖UI) -
World Space(世界空间):这将在世界任何地方绘制一个平面,可以将此平面用作游戏中计算机的屏幕、墙壁或角色的顶部。(比如制作血条) 默认创建的 Canvas 是很大的,可以设置 scale 里 X、Y、Z 为 0.01
Canvas Scaler(画布缩放器组):该组件定义 UI 如何根据不同屏幕大小进行缩放
UI Scale Mode(UI 的缩放模式):
-
Constant Pixel Size or Constant Physical Size(恒定大小):这使得 UI 保持相同恒定不变的大小,无论屏幕是什么大小或形状。这使得 UI 在任何屏幕上都保持可读性,但是较小的屏幕可能有很大的 UI 覆盖空间,如果屏幕太小,UI 元素可能会重叠 -
Scale With Screen Size(按屏幕大小缩放):按屏幕大小缩放,这使得 UI 的缩放取决于开发者设置为参考分辨率的屏幕大小
选中 Image,点击组件中的 Set Native Size 按钮可以使图片保持原本大小
锚点:锚点可以保证当前 UI 的轴心点与锚点之间的距离保持不变,可以通过调整锚点的值来做 UI 的自适应;使用自定义锚点可以使图片基于父对象等比进行缩放
不规则图形的填充可以使用 mask 组件,父对象 UI 需要挂载 mask 组件,子对象则为具体填充UI
实现百分比填充 UI:首先需要获取到 mask 组件的 width 属性(轴心点要设置为左上),设置血条填充百分比使用如下方法:
public Image mask;
float originalSize
void Start()
{
originalSize = mask.rectTransform.rect.width;
}
public void setValue(float fillPercent)
{
mask.rectTransform.SetSizeWithCurrentAnchors(
RectTransform.Axis.Horizontal,
originalSize * fillPercent);
}
想要全局访问某一个变量的方法或者成员变量可以使用单例模式
Unity 中的射线检测
小技巧:可以同时选中多张图片,直接拖进 Hierachy 面板中,可以直接作出动画控制器和动画
可以使用射线检测的方法去判断当前前方是否有某个游戏物体
创建 UI 文本可以使用 Text 组件,outLine 组件则可以描边
if (Input.GetKeyDown(KeyCode.T))
{
RaycastHit2D hit = Physics2D.Raycast(
rigidbody2d.position + Vector2.up * 0.2f,
lookDirection,
1.5f,
LayerMask.GetMask("Npc"));
if (hit.collider != null)
{
NPCDialog npcDialog = hit.collider.GetComponent<NPCDialog>();
if (npcDialog != null)
{
npcDialog.DisplayDialog();
}
}
}
问题记录
VS 中写 C# 脚本没有智能提示:Unity脚本的不变绿VS无智能提示补全问题解决
编译器下方爆黄色警告 Visual Studio Editor Package version 2.0.12 is available, we strongly encourage you to update from the Unity Package Manager for a better Visual Studio integration:Unity报错解决办法:Visual Studio Editor Package version 2.0.11 is available…
|