Roblox学习笔记

概述

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

  1. 参数提示:

  1. 函数参数 —— 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
  1. 查找替换快捷键(这是正则匹配,Roblox中无法查看所有引用,因该是Lua的原因)

  1. Ctrl + Shift + F比上面的功能更强,可在所有脚本中查找/替换

  1. Alt + F 函数筛选器

  1. 多光标

  1. Alt + 鼠标点击或拖动,添加多光标

  1. 在所有匹配项处添加光标(完全可以替换CTRL + F的功能,但是无法匹配多脚本)

  1. Ctrl + D,在下一个匹配处添加光标。Ctrl + P在上一个匹配处添加光标。

  1. 在每行末尾添加光标

  1. Ctrl + U,删除最近添加的光标

  1. 其它快捷键

  1. 导航到定义 Ctrl + F12

  1. 折叠所有项 Ctrl + Shift + E

  1. 格式化所选内容 Alt + Shift + F

#. 通过Instance GetPlayerFromCharacter(Instance character)可以获取Character对应的Player,但是我不知道为啥官方的实现不是Character直接指向Player而是需要一个遍历的过程,大概是出于安全性考虑的吧

#. 可以通过ContentProvider去预加载模型。即在游戏开始时加载模型。

#. 关于对象池,我感觉对于容量可确定且需要频繁创建对象时可以稍微提升一些性能。最影响游戏体验的应该还是GPU和网络不行吧。性能优化我还完全不懂。

#. Roblox中的默认帧间隔比较小,所以应该尽量避免每帧执行的操作,除非是相机等模块。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来抓月亮啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值