注意:这篇文章上次更新于1122天前,文章内容可能已经过时。
Studio 简介
Roblox 基本上所有的资源都存储在云端,也就是说如果你想使用 Studio , 就需要你的计算机保持联网状态,这一点与大多数软件开发 IDE 都不同。打开 Roblox Studio 首先会进行自动更新,和登录操作,更新和登录完成后才可以正常使用 Roblox Studio。关于中国版 Roblox 卡在登录界面的问题,在 这篇博客里给出了解决方案。
Roblox Studio 的主界面
我这里 Studio 的语言为中文,主题为暗色主题,其他相关配置项都可以在 Studio 的设置中进行更改。
Alt + s 进入设置界面
接下来我们打开 Studio 的 Village 模板,来介绍 Studio 的相关界面。
主界面的组成部分
在 游戏主界面 中点击一个对象后,项目管理器中就会高亮显示这个对象,并且在项目管理器下面的对象属性中会展示这个对象的属性。
如果你的界面上没有显示某些窗口,可以在菜单栏中的 视图 对相应窗口进行开关。其中高亮的部分是已经显示的窗口,点击即可取消该窗口的显示。相应的,点击没有高亮的选项,可以显示相应的窗口。
下面介绍相机的基本操作:
-
按住鼠标右键可以左右移动视角,对应键盘的
,
和.
-
使用鼠标滚轮可以前后移动相机,对应键盘的
↑
和↓
或者w
和s
使用鼠标滚轮移动时,会朝着鼠标指针的位置移动。 -
按住鼠标滚轮拖动可以上下左右平移相机,对应键盘快捷键是
QEAD
-
按住
shift
+QWEASD
会降低镜头移动速度。 -
点击一个目标对象,按
f
可以聚焦到这个对象(镜头中心是这个对象),这个时候进行右键旋转和鼠标滚轮滚动,都会以这个目标为中心,如果上下移动或者左右移动,则会自动退出聚焦模式。
关于对象的创建,缩放,和旋转操作,在这篇博客里已经有过了相应介绍,此处不再举例。
部件属性
由于在 Studio 的编辑模式下,物理运算的有些内容是不更新的,部件的某些属性需要在程序运行时才能体现出来。所以在介绍部件属性之前,先介绍一下运行游戏测试的相关按钮和功能。
Roblox 游戏采用 客户端/服务器 模式,通常许多客户端连接到同一个服务器。每一名玩家对应一个客户端。在 Studio 的 测试 选项卡中可以选择玩家的数量。
当选择 2 名玩家时,点击启动后就会额外启动 3 个 Roblox Studio,其中一个是服务器,两个客户端。服务器与客户端的区别是服务器可以使用自由相机来观察世界,而在客户端这里相机是与玩家绑定的。
服务器视角
玩家1视角
玩家2视角
开始游戏按钮会启动一个客户端和一个服务器,默认为客户端视角,玩家会在重生点的任意位置随机出现。
启动一个客户端
点击可以切换到服务器视角
重生点就是一个特殊的部件,类型名是SpawnLocation
我们可以在模型中点击 重生点 来插入部件,当然,插入部件的方式有很多,具体可以看这篇博客
接下来介绍部件的常用属性。官方文档会有更详细的介绍。
插入部件时可以选择部件的形状,包括方块、球体、楔形和圆柱。
其对应的时部件的 Shape 属性。楔形部件和其他形状部件有所不同,楔形部件有其独立的属性。新插入的部件会默认插入在屏幕中央,所以,在你决定要插入部件的时候,请调整相机使其对准地面,如果相机对着天空插入部件的话,部件会被插入在很远的地方。
接下来我们介绍部件的属性列表。
首先时颜色,颜色的属性包含 BrickColor 和 Color,这两个属性都可以修改部件的颜色,为什么会有两个属性呢,这里面包含一个 Roblox 的历史原因,几年前,Roblox 还只有一个 BrickColor 属性,只可以从弹出的选项中选择颜色。
后来,想要加入更多颜色选项,实现可以自定义颜色数值,但是已经有许多脚本使用了 BrickColor 属性,就只能新增加一个 Color 属性。现在这两个属性几乎没有区别了。
Material 材质属性,里面包含 10 几个内置的材质可供选择,在使用不同的材质后,不仅可以改变部件的外观,还会修改部件的物理属性,比如表面摩擦力和弹性之类的。如果要修改默认的物理属性,需要勾选下面的 CustomPhysicalProperties,然后修改出现的子属性。
Reflectance 反射,调高之后会混合天空的颜色,这个属性只对 塑料 、光滑塑料和玻璃生效。不推荐大家用这个属性,因为效果比较假,以后很可能被弃用。
Transparency 透明度。其中 0 表示不透明 1表示全透明。
ClassName 类型名,如果想使用脚本创建一个对象,就需要知道它的类型名。
Instance.new("Part")
Name 是当前对象的名字,也是资源管理器中显示的名字,可以修改 Name 属性为部件重命名。
Parent 是当前对象的父对象。
Transform 属性列表下包含的是属性的位置及大小属性。
Anchored 这个属性需要在运行游戏时才能看出区别,勾选了 Anchored 属性后,部件可以悬停在半空中,取消勾选后,部件会由于重力的原因落到地上。
CanCollide 是否可以与其它物体发生碰撞,true 表示可以 ,false 表示不可以。当我们的部件既没有勾选 CanCollide 属性 也没有勾选 Anchored 属性时,开始游戏后部件就会消失,准确的说时落到地面上由于不可碰撞又会继续下落。
CollisionGroupId 碰撞组。我们可以给每个部件设置不同的分组,然后设定哪些组之间可以发生碰撞。这个属性只是设置 Id ,具体实现哪些组可以发生碰撞需要在脚本中实现。
Locked ,勾选后我们无法用鼠标在场景中选中部件,但是我们依然可以在资源管理器中选中部件。
脚本基础
要使用 Roblox 脚本需要创建脚本对象,Roblox 有三种脚本对象,Script、LocalScript 和 ModuleScript ,我们使用 Script 来演示脚本的编写,其他两种脚本对象与 Script 的语法都是相同的,只是运行的位置不同。Roblox 使用的时 Lua 脚本的语法。
我们在 WorkSpace 下新建一个 Script 对象,Studio 会自动写上第一行代码。当我们运行游戏就会在输出栏打印相关内容。
服务器脚本需要放在 WorkSpace 或者 ServerScriptService 目录下才会被自动执行,放在其他位置是不会自动执行的。
如果不想脚本立即运行,可以把写好的脚本放在 ServerStorage 下,这里是专门用来存放预先制作好的对象的。
所有语言的基础内容都大致相似,这里重点介绍一下 Lua 和 Python 语法上的一些区别。
字符串连接
Lua 中使用 ..
来连接字符串。
a = "Hello " .. 'Lua'
Table 类型
Table 类型是一种表结构,是一种键值对的类型,类似于 Python 中字典和列表的整合。
Table 类型的初始化使用一对大括号 {}
Table 中可以保存很多变量,每个变量使用一个 key 来索引,key 可以是数字,也可以是字符串。索引方式可以使用中括号的方式,但是需要注意,中括号中的数字 1 和字符 1 是不相等的两个数值。当 key 是合法的变量名的时候,也可以使用 .
来进行索引。
Table 类型变量中不仅可以存放普通的变量,还可以存放函数。
t = {}
t[1] = 123
t["1"] = 222
t["Name"] = "lua"
print(t)
print(t[1],t['1'],t.Name) -- T.Name 和 t["Name"] 是等价的
Roblox 中所有的对象都是 Table 类型,当我们想获得某个对象的引用时,可以通过Table 索引的方式来获得对象的引用。例如
p = game.Workspace.Baseplate.Position
print(p)
-- 也可以使用 [] 的方式进行索引
pp = game["Workspace"]["Baseplate"]["Position"]
print(pp)
-- 混合使用两种方式
ppp = game.Workspace["Baseplate"].Position
print(ppp)
函数
函数定义的两种方式
- [作用域] 函数名 = function(参数列表)
- [作用域] function 函数名(参数列表)
a = 10
b = '15'
local add = function(a,b)
return a + b -- 当字符串与数字相加减时 Lua 会首先尝试把字 符串转 换为 数值类型
end
local function sub(a,b)
return b - a
end
print(add(a,b))
print(sub(a,b))
当 Table 类型变量进行函数参数传递时,会发生 地址传递
看示例代码:
local a = 10 -- 定义局部数值类型变量
local t = {} -- 定义局部 Table 类型变量
t.Name = "lua" -- 修改 Table 类型变量的内容
t["Friend"] = "C"
local function test(a,t)
-- 函数接收两个参数 第一个参数赋值为一百,修改第二个参数的值
a = 100
t.Name = "lualualua"
t.Friend = "C++"
end
test(a,t) -- 调用 test 函数
print(a) -- test 函数没有修改当前作用域下 a 的值
print(t.Name) -- test 函数修改了 t 的内容
print(t.Friend)
在函数执行过程中,形参的 a 和 实参的 a 不是相同的内存空间,因此修改实参的 a 不会改变实参 a 的值。此过程类似于 C++ 中的值传递,在调用函数时,系统会再声明一个变量用来接收 a 。但是由于 Table 类型的变量有可能很大,当 Table 类型的变量作为参数传递时,不会重新声明一个变量来进行值的拷贝,而是直接修改该变量的值,类似于 C++ 中的地址传递。
在 Roblox 中,函数很大的一个作用是用来连接部件的事件。例如,如果我们想实现对部件的点击操作。我们首先应该在部件下新建一个 ClickDetector 对象,在 ClickDetector 对象下新建一个脚本,用来实现点击事件。
在 ClickDetector官方文档中,可以找到鼠标点击事件,我们在脚本中将鼠标点击事件与我们的函数绑定起来。
function onclick(player)
print("You clicked")
end
script.Parent.MouseClick:Connect(onclick) --MouseClick 使用冒号的方式调用 Connect 函数
连接事件也可以使用匿名函数,像下面这样,也可以实现相同的功能。
script.Parent.MouseClick:Connect(function onclick(player)
print("You clicked")
end)
使用冒号和点的区别。
在上面的例子中,我们使用冒号来调用 Connect 函数,冒号的点的区别在于:使用冒号调用函数默认传入函数的第一个变量是 函数调用者本身。
local t = {}
t.Name = "Lua"
t.Value = 1
t.add_func = function(tab) -- 在 Table 类型变量中加入函数
tab.Value += 1
end
t.add_func(t) -- 使用点的方式调用函数,函数的参数需要传入自身
print(t.Value) -- 2
t:add_func() -- 使用冒号的方式调用函数
print(t.Value) -- 3
于是,上面例子中的鼠标点击事件就可以重写成如下代码。
function onclick(player)
print("You clicked")
end
-- script.Parent.MouseClick:Connect(onclick)
script.Parent.MouseClick.Connect(script.Parent.MouseClick,onclick)
-- 代码更长了,所以还是使用冒号的方式更加方便。
我们除了在调用函数时可以使用冒号,在写函数体的时候也可以使用冒号。
local t = {}
t.Name = "Lua"
t.Value = 1
--t:add_func = function() -- 这种方式是错误的
-- self.Value += 1
--end
function t:add_func() -- 使用冒号来实现函数体,此时函数默认传入的第一个参数为 self
self.Value += 1
end
t.add_func(t) -- 使用点的方式调用函数,函数的参数需要传入自身
print(t.Value) -- 2
t:add_func() -- 使用冒号的方式调用函数
print(t.Value) -- 3
基本上所有 Roblox 对象上的函数都需要使用冒号来调用,因为这些函数都需要获取对象本身的一些参数。
全局变量和局部变量
在变量声明时,不加 local 关键字则默认声明为全局变量,全局变量在整个程序运行时任何一个地方都可以使用,局部变量则只可以在当前代码块中使用。最大的代码块就是脚本文件本身,其次,你每看见一个 end 关键字就标志着一个代码块的结束,比如一个函数就是一个代码块。
使用局部变量的好处:
- 避免命名冲突
- 存取速度块
- 方便阅读代码,当你读到一个 local 关键字的变量时,你可以马上意识到,这个变量只在当前代码块有用。
下面的例子来展示全局变量和局部变量在使用上的区别。
local a = 10
local f = function(param) -- 函数的实参是局部变量
print(a,param)
c = function() -- 此处的 c 是全局变量,因此可以在当前代码块外继续使用
end
local d = 1000
end
local b = 100
f(b)
print(param,c) -- nil function: 0x48b06e735faff19b
-- param 是函数实参,属于局部变量,在作用域外部无法使用。或者说当前代码块中没有 param 这个变量
print(d) -- nil
-- 同理 d 是被声明在函数内部的局部变量,在函数外部无法使用
使用 do … end 可以生成代码块,防止命名冲突。
do
local a = 10
end
do
local a = 100
end
Roblox 中的全局变量和正统的 Lua 中的全局变量有所不同,即使变量声明为全局变量,在其他脚本文件中依然无法使用。
循环语句
循环语句有三种基本形式。
local a = 0
-- while 循环
while a < 10 do
print(a)
a += 1
end
-- repeat until
repeat
a = a + 1
until a >= 10
for i = 1,10,1 do -- 1,10,1 从 1 开始 到 10 结束 步长 是 1
print(i)
end
使用 for 循环遍历 table。
local arr = {"WANG","GUANG","XIN"} -- 数组类型
local dic = {WANG = 1,GUANG = 2,XIN = 3} -- 字典类型
-- 虽然一个表里可以同时有这两种方式进行索引,但是不推荐大家这么做,因为很多内置函数是假设你的 Table 是数组或者字典的。
-- 针对数组的 for 循环
for i = 1,#arr do -- 数组索引从1开始,#数组名 可以获得数组的长度
print(arr[i])
end
for index,value in ipairs(arr) do -- ipairs 每次返回数组索引和值
print(arr[index],index,value)
end
-- 针对字典的 for 循环
for key,value in pairs(dic) do
print(dic[key],value)
-- dic.key 这样是不对的,使用点的方式,点后面不能是变量 如果索引是变量 只能dic[key]
end
不同脚本间的数据共享
前面提到过 Roblox 的脚本中全局变量也无法被其他脚本文件使用,这里介绍不同脚本间数据共享的几种方法。
- 存储在 Roblox 的对象里
这种方法适用于一个简单的数据,比如一个数值、字符串之类的。
首先点击 Workspace 创建一个 value 对象。
搜索 value 可以出现以下几种对象。
我们创建一个 IntValue 对象,顾名思义这个对象就是用来存储整数数据的,比如 Boss 的血量等等。
我们可以在不同脚本中修改这个 Value 对象的 Value 属性,可以修改这个对象的 Name 属性方便理解这个值的作用,比如修改成 HP 等等。
接下来我们可以在其他脚本文件中对这个 Value 对象进行操作。
local HP = game.Workspace:WaitForChild("HP")
-- 这里我们使用 WaitForChild 是为了防止脚本执行时 Value 对象还没有被创建导致的程序出错。
-- local HP = game.Workspace.HP 这种方式 需要先创建了 HP 对象,再执行脚本对象才不会出错。
-- 每隔 1 秒钟打印以下 HP 的值 并且使其减一
for i = 1,100 do
print(HP.Value)
HP.Value -= 1
wait(1)
end
- 使用全局表
在 Lua 里有个特殊的表,名字是 _G 。这个表里存了一些对 Lua 比较重要的东西,比如一些内置函数。
我们可以把需要共享的变量存储在这个表里,这样所有脚本之间都可以使用这个变量。为了避免与 _G 表内已经存在的内容出现冲突,我们可以在 _G 表下创建一个表,用来专门存储我们自己声明的变量。
_G.MyValues = {}
_G.MyValues.HP = 1000
print(_G.MyValues.HP)
使用这种方法共享数据可能出现一个问题,由于脚本之间的执行顺序是不确定的,比如你想实现第一个脚本写入数据,第二个脚本读取数据,但实际上脚本的执行顺序是相反的,可能会读到一个不存在的数据。因此,使用这种方式共享数据还需要编写其他的逻辑来加以控制。
另外,Roblox 客户端和服务器的全局表是不同的,这种方法不能在客户端和服务器之间共享数据。
- 使用 ModuleScript 对象
ModuleScript 自己单独存在的时候并不会运行,你需要在别的脚本中调用它,他才会执行。在 ServerScriptService 中新建一个 ModuleScript 对象。
Studio 会为我们自动生成两行代码。
我们把 Boss 的信息存在这个文件的表内。
local Boss = {}
Boss.HP = 1000
Boss.Name = "Lua"
Boss.shou_shang = function(Boss)
Boss.HP -= 1
end
return Boss
然后在用到的地方先执行 require 函数,然后就可以操作这个表了。
local Boss = require(game.ServerScriptService:WaitForChild("ModuleScript"))
while Boss.HP > 0 do
print(Boss.HP)
Boss:shou_shang()
wait(0.5)
end
以上就是我关于 Roblox 所了解的全部内容了🚩
更多精彩内容请自行查阅官方文档
如果你足够细心的话,你会发现下面有个按钮在闪光😉