Intro
Hello, this is a progress update and a sequel to a previous post.
I’ve now been working on my game for a little over 3 months now, and wanted to share my updated progress.
My game is finally starting to gain an identity. Project Windfall is an MMO with heavy magic-based themes. The player’s goal is to level up and unlock new, grand spells to use. There’s a heavy focus on visuals and gameplay to make the player feel powerful. The central theme is spellcasting with strong RPG elements.
Here’s a tech demo/showcase that I spent a lot of time preparing especially for this post.
Stats
I’ve said this before, but lines of code really don’t matter. I just enjoy tracking the metric.
Stats
- Lines of code:
15,000
- ModuleScripts: 84
- LocalScripts: 31
- Remote Events: 53
- Remote functions: 2
Knowledge Share
Here’s a collection of some valuable knowledge I learned since my first post.
Knowledge
- VFX - VFX are typically created using a plugin that the VFX artist will use to set attributes on ParticleEmitters and Beams such as EmitDelay, EmitDuration, and EmitCount. We can use these properties in our code to simulate what the VFX artist sees when they are testing their VFX. This code took me a while to land on, but it works perfectly for me.
VFX Code
-- Using EmitCount, EmitDelay, and EmitDuration set by VFX plugins, we can emit exactly
-- EmitCount particles after EmitDelay, or become Enabled for EmitDuration. Applies to beams
-- as well. Will Destroy ParticleEmitters and Beams with an emitDuration > 0.
function EmitterControls.playVFX(parent: Instance)
for _, child in parent:GetDescendants() do
local emitCount = child:GetAttribute("EmitCount") or 0
local emitDelay = child:GetAttribute("EmitDelay") or 0
local emitDuration = child:GetAttribute("EmitDuration") or 0
task.delay(emitDelay, function()
if child:IsA("ParticleEmitter") then
if emitDuration > 0 then
child.Enabled = true
Debris:AddItem(child, emitDuration)
end
task.wait(0.1) -- needed when StreamingEnabled is true
child:Emit(emitCount)
elseif child:IsA("Beam") then
if emitDuration > 0 then
child.Enabled = true
Debris:AddItem(child, emitDuration)
end
end
end)
end
end
-
Preload Assets - To avoid textures loading improperly the first time they’re played for a player, we can use ContentProvider:PreloadAsync in order to tell the client to preload the texture IDs. This ensures VFX do not appear glitchy when a player first joins.
-
Beam Render Limit - When working with VFX, it is a good idea to limit the number of beams. Beams already act weird on lower-end devices, but the roblox engine has an undocumented limit on the number of beams it is willing to render at any given time. Exceed this unknown limit and random beams will not be rendered. I ran into this issue and spent a long time debugging it to learn this information.
-
StreamingEnabled client-side only VFX - At some point I wanted to render an aura only on the client. My problem was, with Streaming Enabled, sometimes the enemy that I wanted to apply the aura to wasn’t loaded into the client yet. In order to get around this, we can use CollectionService. By using the
AddTag
method, we can add a tag to the enemy’s model.
Now, on the client we can do something like this:
CollectionService Client
for _, strangeEnemy in CollectionService:GetTagged(SharedEnums.Tags.StrangeEnemy) do
addStrangeAura(strangeEnemy)
end
CollectionService:GetInstanceAddedSignal(SharedEnums.Tags.StrangeEnemy):Connect(function(strangeEnemy)
addStrangeAura(strangeEnemy)
end)
-
Small one, but use :PivotTo when you want to modify a part/model’s position instead of modifying the CFrame directly. This will allow child parts to move with the instance.
-
Use the CharacterAppearanceLoaded event to add your own custom gear to the player. Here’s some example code I used to load custom gear onto the player when they join.
AddGearCode
local function OnPlayerAdded(player: Player)
local connection = player.CharacterAppearanceLoaded:Connect(function(character)
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then
return
end
local overrideAccessories = { Enum.AccessoryType.Hat, Enum.AccessoryType.Back, Enum.AccessoryType.Neck }
for _, child in character:GetChildren() do
if child:IsA("Accessory") then
for _, accessoryType in overrideAccessories do
if child.AccessoryType == accessoryType then
child:Destroy()
end
end
end
end
humanoid:AddAccessory(ReplicatedStorage.Gear.Hats.WaterMageHat1)
humanoid:AddAccessory(ReplicatedStorage.Gear.Capes.WaterMageCape1)
end
Conclusion
I feel motivated and focused on this project, and I think it will go far. I’ve made a lot of progress towards a game people will actually enjoy playing, so I’ve never felt more motivated than I do now. Thanks for following this update. I will continue posting updates sometime in the future.