概述
Roblox笔记,可能会顺便写一下Lua相关的。
1. Roblox中变化整个物体
(1)通过组合为Model整体变换
① 问题概要
Unity中,改变父物体的Transform子物体会一起变换。
Roblox中,在【属性】面板中有两个与位置相关的参数,改变父物体的CFrame.Position只有父物体移动,改变父物体的Origin.Position则子物体会跟着移动。
这似乎没什么区别,但是我在实现代码时遇到了问题,参见示例1、示例2。
② 示例1
我试图通过以下Move函数来改变整个物体(包括该物体下的所有子物体)的移动,但效果却是只有父物体移动。(父物体、子物体均锚固)
function Move(object)
object.Position = object.Position + Vector3.new(0, 10, 0)
end
③ 示例2
于是,我又尝试修改CFrame.Position属性。
function Move(object)
object.CFrame.Position = object.CFrame.Position + Vector3.new(0, 10, 0)
end
如下所示,这个属性不可赋值。
Position cannot be assigned to
④ 问题分析
因为我们使用的是part来操作的,所以我们先在文档中查阅part的属性。Roblox API | Part
直接CTRL+F搜索position,得到下面的结果。(然后用有道词典翻译)







做出如下表格。
position 相关条目 | 属性 | 类型 | 描述 | 特性 | ||
继承自BasePart | CFrame | CFrame | 确定BasePart的位置与方向 | world坐标系 | ||
CenterOfMass | Vector3 | 质心的位置 | 只读 不可复制 | |||
Orientation | Vector3 | part的位置 | 隐藏 不可复制 | |||
Position | Vector3 | part的位置 | ||||
继承自PVInstance (页面预览实例) | Origin Position | Vector3 | 用于移动对象 | 脚本必须使用PVInstance:PivotTo()替代 | 编辑器【属性】面板中的属性 | 无对应脚本 不可复制 |
Pivot Offset Position | Vector3 | 用于设置part的轴心(local坐标系中心)位置 | 脚本必须使用BasePart.PivotOffset()替代 |
(我可好奇为啥没有对应的脚本却写在API文档里面了)
从表格中可以看出官方文档中对Orientation属性的描述有误。Position相比CFrame除了多了两个特性(限制)外似乎没有什么不同,是【隐藏】的(这里的【隐藏】指的是在【属性】面板中隐藏,脚本中依然可以调用),所以我们遇到问题的原因就是把object.Position误解为了Origin Position属性。
首先对于示例2,CFrame.Position只读,但是我们可以修改CFrame达到移动的效果,当然这个只能改变父物体的position。但不如示例1的写法方便。
function Move(object)
object.CFrame = object.CFrame * CFrame.new(0, 10, 0)
end
官方文档指明了修改整个物体变换的方法。点击文档中的PVInstance:PivotTo()跳转至该界面,查看官方示例。
-- 这段代码应该放在StarterPlayerScripts下的LocalScript中
local Players = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local player = Players.LocalPlayer
local function doTeleport(_actionName, inputState, _inputObject)
local character = player.Character
if character and character.Parent and inputState == Enum.UserInputState.Begin then
-- 将角色向面朝的方向移动10个单位
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(0, 0, -10))
end
end
ContextActionService:BindAction("Teleport", doTeleport, true, Enum.KeyCode.F)
所以想要父物体与子物体一起旋转或是移动,代码如下。
function MoveByLocal(object)
-- 以local坐标系为参考,移动一段距离
object:PivotTo(object:GetPivot() * CFrame.new(0, 10, 0))
end
function MoveByWorld(object)
-- 以world坐标系为参考,移动一段距离
object:PivotTo(object:GetPiovt() + Vector3.new(0, 10, 0))
end
function RotateByLocal(object)
-- 以local坐标系为参考,旋转一定角度
-- 注意Angles传入的参数单位为弧度,90°转换为弧度就是2π(rad)
object:PivotTo(object:GetPivot() * CFrame.Angles(0, math.rad(90), 0))
end
-- 怎么写以world坐标系为参考,旋转一定角度的函数呢
(但是一起缩放,我依然不知道怎么弄)
⑤ 备注:特性
我猜这个【不可复制】指的是——赋值的时候不能复制,什么意思呢,就是Lua中没有引用变量这个东西,所以复制某些变量(在Roblox中通常是Instance)时会先复制(深拷贝)一份再赋值。这个特性可能是出于性能考虑,但更多地应该是出于设计考虑(即只存在一份该变量)。
官方文档没有将【不可赋值】作为特性标出来,就如示例2中所说我无法对Part的CFrame.Position赋值但是官方并未详细标注。
⑥ 备注:local与world
与Unity不同的是,Roblox里面没有使用position与localPosition来表示world坐标或是local坐标。Roblox中的Position、Rotation都是以world坐标系为参考。
print(part.CFrame == part.CFrame:ToWorldSpace()) -- true
而CFrame的引进就是为了解决这个问题,即以local坐标系为参考操作Position与Rotation。CFrame实际上就是带位置信息与旋转信息的4 * 3矩阵。即旋转信息就是用3 * 3的旋转矩阵表示的,但是CFrame.Rotation是CFrame类型并不是3 * 3的旋转矩阵(设计成这样是便于计算)。

可以随便写点代码看一下:
local part = script.Parent
print("===========< CFrame >==========")
print("CFrame:", part.CFrame)
print("==========< Rotation >=========")
print("Rotation:", part.Rotation)
print("CFrame.Rotation:", part.CFrame.Rotation)
print("==========< Position >=========")
print("Position:", part.Position)
print("CFrame.Position:", part.CFrame.Position)

CFrame.Position就是Position,CFrame.Rotation与Rotation都是表示的在world坐标系中的旋转(只是表示方式不一样)。
所以呃,就很麻烦。CFrame与本身的Position、Rotation存储的信息是一样的,只是CFrame可以进行部分以local坐标系为参考的操作,而Position、Rotation不能。不过CFrame还提供了相应的函数来解决local坐标系与world坐标系转换的问题……到这里,你可能会觉得CFrame好像一点用没有。但是记得Pivot吗,它允许修改物体本地坐标系中心到物体的偏移。Unity中物体的localPosition实际上是在该物体的父物体的local坐标系中的位置,而Roblox中通过那几个函数得到的好像是相对于自身local坐标系的local值……
这还是和Roblox的零件理念有关,那就是即使一个物体在另一个物体下面(项目管理器中),它们默认是分开的,不会一起Transform。
因此仅仅通过CFrame来操作未免有些太过麻烦。还需要结合LookVector、RightVector、UpVector一起使用。

⑦ 备注:关于旋转
EulerAnglesXYZ与EulerAnglesYXZ,就是欧拉角的渲染顺序不同。而Orientation对应旋转顺序YXZ,Rotation对应XYZ的旋转顺序。旋转矩阵与欧拉角可以相互转换,Roblox中可以像下面这样写,不过角度与弧度相互转换时精度丢失了……
-- 转换为CFrame
print(CFrame.Angles(math.rad(part.Rotation.X),
math.rad(part.Rotation.Y), math.rad(part.Rotation.Z)))
-- 转换为欧拉角
print(Vector3.new(part.CFrame.Rotation:ToEulerAngles()) * 180 / math.pi)
-- 等效于ToEulerAnglesYXZ()\ToEulerAngles(Enum.RotationOrder.YXZ)
print(Vector3.new(part.CFrame.Rotation:ToOrientation()) * 180 / math.pi)
例如,我们想要让一个物体沿world坐标系中的Y轴一直旋转,我们可以这样写:
game:GetService("TweenService"):Create(part,
TweenInfo.new(2, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1),
{ Orientation = part.Orientation + Vector3.new(0, 360, 0) }
):Play()
(2)焊接约束
虽然上面的方法已经解决了问题,但是这种方法无法结合TweenService使用。而TweenService的插值功能比较方便强大,用它可以快速实现游戏效果。
所以这时可以使用焊接约束,需要注意的是焊接约束需要将其中一个零件进行锚固,否则焊接约束处于非激活状态。
Understanding Assemblies | Roblox Creator Documentation
然后就如下面文档所说,修改CFrame即可。

这个方法比上面的方法好用多了,但是约束挂多项目管理器会比较乱。毕竟这些玩意不能像组件一样附加到物体上面,而是一个单独的物体。
(3)Motor6D
可以让两个零配件一起移动或旋转,并用动画改变这两个零配件的相对位置。
2. Roblox中数据的共享
(1)多脚本调用
(2)C/S数据共享
3. 其它
①
son.IsA(fater)判断son是否为fater类或fater类的派生类的实例。
例如,FindFirstAncestorOfClass()、FindFirstAncestorWitchIsA()的区别也是如此。
②
WaitForChild更安全。对于哪些需要在游戏开始就执行的脚本中的引用,应当使用WaitForChild( ),因为很难判断该脚本执行到引用语句时,引用的对象是否在游戏中加载完毕。
④
Roblox文档里面的Object通常指Model(包括该实例及该实例的所有子实例),实例则指本身.
(例如,名为parent的part下面挂了一个叫child的part,则object指这两者,Instance指名为parent的part)
⑤
服务器与客户端之间的通信可以通过RemoteEvent或RemoteFunction完成。
服务器内部或客户端内部通信则使用BindableEvent或BindableFunction完成。
RemoteFunction可以传递返回值,该返回值往往用于安全检测。所以RemoteFunction更安全但效率更低。BindableFunction与BindableEvent同理。
⑥
EasingDirection定义easing style插值如何应用于对象,默认值为 Out。请注意,具有线性缓动样式的补间不受影响,因为线性插值从头到尾都是恒定的。
Out的话就是就取插值函数的凸区间进行插值,而In的话就是取插值函数的凹区间进行插值。(out凸in凹)
⑥
tween的Pause()与Cancel()的区别:
Pause()会记录Instance暂停时的状态,并在重新Play时,将Instance恢复至此状态
而Cancel()则不会记录Instance取消时的状态。
*但是,想恢复插值前的初始状态,则需要手动处理。
Roblox会先加载服务器资源再加载客户端资源,释放顺序反之。所以服务器脚本加载完成才会加载客户端脚本。
每个事件(RemoteEvent、BindableEvent)最好只负责完成一种一对一的单向通讯(参与的脚本不超过2个,且该事件只负责参与一种通讯的来回消息传递)。否则代码将变得极其难以维护。
比较常见的通信:服务器处理Touched等事件、数据,处理完再给客户端发消息,客户端更新UI、场景等(只负责显示)。
Q:加速器导致Roblox进不去。
S:换加速器节点即可。




官方文档指南,工作室和工具->脚本工具->脚本编辑器
https://create.roblox.com/docs/studio
参数提示:
函数参数 —— parmName: parmType(?)
参数名称:参数类型(?)
?可选,表示该参数是否可省略。
function PrintName(name: string?)
if name == nil then
name = "Default Character"
end
print(name)
end
b. 函数返回值 —— ( parmType... )
function GetUpAngleAndSpeed(dPos, alpha): (number, number)
-- balabala
return beta, speed
end
查找替换快捷键(这是正则匹配,Roblox中无法查看所有引用,因该是Lua的原因)
Ctrl + Shift + F比上面的功能更强,可在所有脚本中查找/替换
Alt + F 函数筛选器
多光标
Alt + 鼠标点击或拖动,添加多光标
在所有匹配项处添加光标(完全可以替换CTRL + F的功能,但是无法匹配多脚本)

Ctrl + D,在下一个匹配处添加光标。Ctrl + P在上一个匹配处添加光标。
在每行末尾添加光标

Ctrl + U,删除最近添加的光标
其它快捷键
导航到定义 Ctrl + F12
折叠所有项 Ctrl + Shift + E
格式化所选内容 Alt + Shift + F
#. 通过Instance GetPlayerFromCharacter(Instance character)可以获取Character对应的Player,但是我不知道为啥官方的实现不是Character直接指向Player而是需要一个遍历的过程,大概是出于安全性考虑的吧
#. 可以通过ContentProvider去预加载模型。即在游戏开始时加载模型。
#. 关于对象池,我感觉对于容量可确定且需要频繁创建对象时可以稍微提升一些性能。最影响游戏体验的应该还是GPU和网络不行吧。性能优化我还完全不懂。
#. Roblox中的默认帧间隔比较小,所以应该尽量避免每帧执行的操作,除非是相机等模块。