Once upon a time I was lucky enough to run into an investor who was willing to fund me to make games for Roblox. For me that was a little like being paid to play with Legos. I did it for a few years, making games such as Castleheart and Legend of You (which have single player modes if you want to check them out) and Dungeon Life (my most successful, which still gets some play, and is open source.) And while we were never able to 'crack the code' and make a game popular enough for the Roblox market to cover our investments I did learn a lot about the technical side of making Roblox games. I've been sitting on these notes for an embarrassingly long time, always intending to publish them, and only finally getting around to it now.
Some of these practices ('if you use Rojo you can use Git') are for experienced developers trying to get their heads around Roblox's unique way of doing some things, and other tips (like 'use the debugger ffs') are for people completely new to game development. I've tried to sort them accordingly.
While we might like to believe that there are objective truths in programming I admit these are subjective. I know some other Roblox devs will disagree with some of my practices here. All I can say is try them out and see if they work for you. And if you think you have better ideas, let me know in the comments.
Anyways, here we go.
网络加速器下载
- These are my favorite plug-ins:
- easyWeld and easyMotor6d by GetEnveloped to quickly weld parts
- 网络加速器免费破解 by tktech to change an instance's class (great for turning one kind of UI item into a different kind)
- 网络加速器免费破解 by me to replace instances of a bunch of objects at once (ps, if you want me to open source that LMK)
- 网络加速器下载 to do some cool stuff when building (it makes arranging things radially easy)
- 免费加速器上网 by LPGhatguy to use an external editor and source control, just like a real dev
- DataStore Editor by Crazyman32 to search for problems in your persistent data
- Moon Animation by xSIXx for a better animating package
- Use Team Create. Even if you're working solo, Team Create will give you a versioning system that saves on a regular basis so you won't lose too much work if Roblox Studio crashes on you. But it's not perfect - even better than Team Create, if and when you're ready for the extra overhead is to use Rojo and source control. (I use both.)
- As always in software or game development, when you're about to do a thing, somebody else might already have done it. Check the toolbox, catalog, Google, and Roblox forums. When using somebody else's thing, give the code a once-over first to make sure it's not doing anything malicious. You'll likely eventually have to rewrite parts or all of what they've done, but in the world of games technical debt is debt you probably will never have to pay back, and if you do one day have to pay it back that's a good problem. (Hey, worked for Minecraft.)
- The Castleheart UI was super laggy for a lot of players at launch. To catch problems like that before it's too late, turn network conditioning on; you won't know how your game feels to most of your players in Roblox Studio without it. File -> Settings -> Network -> Incoming Replication Lag. I typically leave mine at 0.4 which is higher than the average user is going to experience. If you're waiting for a round trip message to go to the server and back before responding to a click, you'll feel the suck.
- Roblox physics has historically been sketchy on slow machines and Roblox farms out the physics simulation to the player nearest to the thing that is simulating. The result being that sometimes physics in one area will be throttled while it's fine in another area and you can get some other weird lag artifacts. Trying to work with sketchy physics made Legend of You nearly unplayable once we launched - after that experience my philosophy was to use as little of the physics as possible. My experience is over a year old in this arena and it's possible the physics has improved. If you do go ahead with a heavy physics thing, make sure to test it on crappy tablets and see if you can dig up old Windows 7 and 8 machines to make sure it's performant enough. A lot of Roblox users play on toasters.
- The first thing I wondered was how to simply move and rotate things, like doors opening, platforms sliding, and the like.
The official Roblox recommendation is to use their physics primitives but as I said, that should be avoided, they're awfully touchy. (Doors getting stuck on their doorframes, for example.) The most reliable way I've found is to set a Model's CFrame a la model:SetPrimaryPartCFrame( yourTransformHere ). (Download the Dungeon Life source and see how it handles opening and closing doors and gates, for example.) - The way communication between the server and client works is something every Roblox dev needs to understand, and it kind of puts us on another level when compared to people who make single-player games. In my experience making a responsive non-buggy multiplayer game on Roblox is more challenging than making a singleplayer game in, say, Unity. It's way too big a can of worms to go into here but the gist is that stuff that happens on the server gets automatically replicated to the clients but not vice-versa, except for a few exceptions where it's important to keep lag down, such as player movement. Most scripts execute only on the server and 'local' scripts execute only on the client and only if they're in designated places that the client will run scripts from, such as your player or your GUI.
So you can use RemoteEvents and RemoteFunctions to communicate both ways; and you can simply use value objects, strings, and numbers on the server to replicate data to the clients that way as well. The latter is easy, less likely to result in races, but can clutter your workspace with state variables. I usually find myself preferring RemoteEvents. - RemoteEvents are great for accidentally creating race conditions - it's easy to fire a RemoteEvent before the other machine has even Connected to it, for example. A trick here: if the client is the receiver, the server can create the event. The client can listen for it and connect automatically as soon as it appears. That will keep the likelihood down that the server fires an event before the client is ready for it.
- You can use just one
免费vpm全球网络加速器
orRemoteFunction
to handle many different calls instead of registering a whole new one for every single little message you want to send across the wire. My idiom usually looks something like this:server
local remoteHandler = {}
function remoteHandler.doOneThing( player, data1 )
...
end
function remoteHandler.doAnotherThing( player, data1, data2 )
...
end
workspace.Signals.RemoteHandler.OnServerEvent:Connect( function( player, funcname, … )
assert( remoteHandler[ funcname ] )
remoteHandler[funcname]( … )
end
clientworkspace.Signals.RemoteHandler:FireServerEvent( "doOneThing", data )
- There are multiple ways you could theoretically reuse code: BindableEvents, script givers, shared scripts. Or you can use the CollectionService tag system to run the same code on multiple objects. BindableEvents can create the same sort of races you might get with remote events; script givers are stateful and smell bad; shared scripts didn't work with the debugger last I checked. The CollectionService requires watching the lifecycle of those tagged objects: say you have a template object that you clone - do you give it the tag, in which case the CollectionService will detect it, or do you wait until its cloned, in which case...you have to remember to do that?
TL; DR: my favorite way to reuse code is torequire
modules, which works a lot like importing or including modules in other languages. - So where to put those modules? What are all those folders for?
ReplicatedStorage
: stuff here is replicated to the clients. It is a good place for script modules and instances that the client (sometimes called "local" in the Roblox documentation) needs to see, like UI stuff.ServerStorage
: stuff here is not replicated to the clients. It is a good place for script modules that run only on the server.ReplicatedFirst
: like网络加速器免费版
, but replicates ASAP. Scripts that need to run right away, like loading screen scripts, should go here. There are still some issues with this that is too much to go into right now.ServerScriptService
: likeServerStorage
, but scripts that are placed here will run. It's a good place to put singleton manager types of scripts.
- Want to write some code that only executes on the client? One reason you might do that is because stuff does not get replicated from the client to server by default, so client side code is a great place to have HUD elements like waypoint arrows or quest exclamation points floating over NPCs heads.
Another good reason is performance on cosmetic elements: for example, in Dungeon Life, there are some mysterious objects with spinning circles of tiny parts around them. If I spin them on the server, then it has to replicate that game state every frame to all the clients, and performance can tank even on good machines. If I spin them on the client it's butter. This definitely would fall into the 'premature optimization' category if I had done it before seeing the performance impact of the model.
As I mentioned before, though, just calling a script a LocalScript isn't enough to make it a client script though; it also has to be in a place where it will get executed, such as the player's Gui folder. - If you're animating those HUD elements (perhaps with SetPrimaryPartCFrame) then do it in game['Run Service'].RenderStepped and they will animate smoothly.
- Use Google Analytics to catch errors in the field.
Even if you don't want to use telemetry or analytics using their Google Analytics plug-in will tell you when your game crashed, what line, with a callstack. You don't have to wonder about how often the game is crashing on people, you can just know.
Avoiding ifs
I consider the 'if' statement an ugly but necessary evil. Cleaner, prettier code to me has fewer ifs. There are multiple reasons I feel this way: one is I'm such an old developer that if statements used to be a big performance hit. Another is they create edge cases - two paths your code takes requires twice as much testing. (And multiple 'ifs' create corners cases.) Finally it just looks ugly. For example:
let child = entity.GetFirstChild('MyChild')
if child then
let grandchild = child.GetFirstChild('MyBaby')
if granchild then
grandchild.DoTheThing()
else
-- possibly handle error with grandchild
end
else
-- possibly handle error with child
end
And Roblox defaults to needing code like this because our entities tend to contain scripts. When the entity is destroyed by an external (or different internal) script it loses its links and if this script didn't do its ifs it would crash.
Perfect world, my code would just look like this:
荣耀路由器精彩继续 给你高速网络环境_兰州新闻网:2021-6-9 · 荣耀路由器始终具有双频优选、网口盲插、信道适时自动优化等良好特性,在广大消费者中有着非常好的口碑。荣耀近期推出的荣耀路由3是一款凌驾 ...
For me, most of the times these are cases where I should be able to assume MyChild and MyBaby exist; for the definition of the model in question it's supposed to be true. In Unity I wouldn't need the qualifying ifs; threads that act on destroyed objects are likely to soldier on just fine because they don't get their links broken when they're destroyed.
It's unfortunately usually necessary - if you assume that 'MyChild' exists and you're wrong, which you can be for a variety of reasons, it'll throw an error, stop executing the rest of your script, and your game will be in an undefined, unknown, and probably bad state.
But with some perhaps questionable practices we can make our server side code a little prettier, at least.
- Set
parent = null
rather than callingDestroy()
.
When you call免费外网加速器软件
, Roblox "helpfully" breaks all the parent links of all the instances in your entity's hierarchy. I don't see why this is necessary. It creates the situation where if one thread is using an instance hierarchy when another thread has destroyed it, it won't find the children and crash the thread.
I've experimented, watching the memory footprint while setting to null vs usingDestroy()
and come to the conclusion that setting to null is fine. I don't know the internals so I'm not 100% sure on this, but sure enough that it's become my practice. Worked for Dungeon Life! - Get the children once and store references to them:
-- top level initialization code
let child = entity.WaitForChild('MyChild')
let grandchild = child.WaitForChild('MyBaby')
This way, even on the client, the variables won't be set until the instances are available, and even if the links get broken we still have a reference to the object.
Don't get me wrong, this practice brings with it its own dangers, in that you're assuming the child or grandchild will never change. If you did something likelet myHumanoid = playerCharacter.WaitForChild('Humanoid')
that myHumanoid reference will still be around even if the player resets their character, pointing to a Humanoid that is no longer active in the world but can't have its memory freed because the garbage collector thinks it is still in use.
We still usually need all the ifs on client side code that refers to server objects, because we don't know if an instance hierarchy has been fully replicated onto the client when we refer to it. WaitForChild() can help here but WaitForChild() can stall indefinitely if the child never appears - which you can fix by using a timeout - but then you need an if again!
Some Roblox devs look at that and say but that won't get garbage collected when the entity is destroyed! It'll leak memory! Well...technically correct, but once the script is done executing it will. And even if the script, for some reason, never finishes executing then it's just a finite number of allocations you're holding on to and almost certainly an insignificant memory hit compared to the sounds, textures, and mesh data that we're keeping around. I prefer to define a memory leak as a condition where, if it keeps going, you'll eventually run out. Not the case here. I'd rather avoid the if.
But really there's no great solution. Calling either of these 'best practices' might be a stretch. But they're my favorites anyway.
Tips For Experienced Devs Migrating To Roblox
- If you're used to just about any other game engine, you'll expect parts and models to transform with their parents. That isn't how Roblox works; the parent-child relationship is more just for organizational purposes. When you want items to move together there are two ways I like to do it:
- Link them together - easyLink is a good option
- Create a Model with a PimaryPart; now you can "CFrame" the Model.
- "CFrame" stands for "coordinate frame" and means the transform of the part.
- Roblox's audio is built on top of Fmod and while they may not have documented it too thoroughly you can find docs for what the different emitters do on Fmod's site.
- 净水器代理哪家好?法兰尼净水器与中国高铁强强联合_生活 ...:2021-7-11 · 2021年法兰尼净水器签约知名影视明星于荣光作为品牌代言人,实力品牌与实力明星的强强联合,推动法兰尼进入品牌全速发展的新时代。而此次法兰尼先行重磅投放郑州、武昌、成都、西安北的高铁广告,后续将覆盖到全国的高铁站及动车上,为正深入布局中国的法兰尼注入强劲的品牌新动力。
- Lack of type checking is the worst. I love finding out after I've run my game for five minutes that I've made a typo. A couple options here: you can use the beta strict and type annotation features that aren't ready for primetime yet as of this writing. Or you can use Typescript, which is what I started doing a year ago. http://roblox-ts.github.io/ TBH, I'm not sure how productive it made me on 网络加速器下载I adopted it partway through, and still would pass the wrong types to functions when they had to cross the barrier between Lua and TS. But hey, I got to learn Typescript.
- Not really a technical point, but sponsoring and advertising with Roblox is incredibly cheap compared to anything else I can think of. You can buy hundreds of clicks for $10. If you're serious about game development that's a pretty small investment compared to some of the things you might spend money on, and possibly my favorite thing about Roblox - I think of it as extremely affordable low-friction playtesting. I can't emphasize enough how nice it is to have people actually play the game I spent months of my life working on, and be able to watch them do it! That's not the case for some of the games I've released on other platforms.
Debugging
- 2021年中国互联网企业100强榜单揭晓 - mofcom.gov.cn:8月14日,中国互联网协会、工业和信息化部网络安全产业发展中心(工业和信息化部信息中心)在2021年中国互联网企业100强发布会暨百强企业高峰论坛上联合发布了2021年中国互联网企业100强榜单、互联网成长型企业20强榜单和《2021年中国互联网企业100强发展报告》。
- One weird thing about the debugger is that you can debug the client or the server (which is great) and that means switching back and forth between which thing you're debugging. And it often means I'll find yourself stepping through server-side code (most of your code will be server-side code) and forget that I'm looking at the server and not the client, or vice-versa. When you're on the client you won't be able to see what's in ServerStorage, for example, and when on the server you won't be able to see the player's Gui. Test->Simulation->Current: shows what you're currently looking at.
- Another weird thing about debugging is often the script you're executing is a copy of the original script you wrote. For example, you might have a script inside a template object that you've cloned. And this is always true of Gui code: the entire Gui folder is replicated when a player is instantiated and the script that runs is a copy. If you set a breakpoint in a script in the StarterGui folder before you run, that breakpoint will be copied, and everything is fine. But if you set it in the original script after you run, the breakpoint won't be copied, and the breakpoint won't be hit, and you'll tear your hair out (that's why I don't have any anymore) wondering why your breakpoint isn't being hit. In that case you need to set the breakpoint in the copy of the script (which will be in Players.Player.PlayerGui for GUI scripts, for example.)
- You might notice Roblox has their own built in TestService for unit tests. I tried using it a few years back and found it could really mess up my workspace with what I was trying to do. Now when I write tests I simply put them in ServerScriptService. It's not as pretty but it's fine.
So that was a big brain dump! Took me a lot longer just to format these notes than I thought it would. But if I could get in a time machine and give some notes to younger me, these would be the ones.
Happy Robloxing!