[A LOT OF ADVANCED STUFF IS MENTIONED HERE, SO PLEASE ASK QUESTIONS TO GAIN FURTHER UNDERSTANDING]
Ahh I had a feeling. Alright so if that’s the case, we want to utilize values. In this case, we can add an attribute when the Player joins that designates them a ‘CharacterSelected’ value. When the player is given this value, we then want to; 1.) detect the value change 2.) find the selected character’s data within a module storing all relevant data 3.) applying the changes of switching character to the player via utilizing a metatable method for scalability.
We can start off by firstly utilizing the event trigger ‘Player.PlayerAdded’ in a script inside of ServerScriptService to do this.
-- inside this new hypothetical script inside of ServerScriptService
local PLRS = game:GetService('Players')
local REPS = game:GetService('ReplicatedStorage')
-- [THIS MODULE IS MENTIONED LATER ON IN THE POST; IGNORE IT FOR NOW]
local RealCharacters = require(REPS:WaitForChild('RealCharacters'))
-- config variables
local DEFAULT_CHARACTER = 'Insert Default Character Name Here'
-- if you want to define a character that is automatically
-- set if the player hasn't chosen on before; then utilize
-- this variable above.
PLRS.PlayerAdded:Connect(function(Player)
-- set attribute
Player:SetAttribute('CharacterSelected', DEFAULT_CHARACTER)
-- detect whenever the attribute changes
Player:GetAttributeChangedSignal('CharacterSelected'):Connect(function()
local Value = Player:GetAttribute('CharacterSelected')
--[AGAIN THIS STUFF BELOW IS MENTIONED FURTHER INTO THE
--RESPONSE SO KEEP READING]
local SELECTED = RealCharacters[Value]
SELECTED:LoadCharacterAsSelf(Player, true)
end)
-- get character instance to apply selected character data to.
Player.CharacterAdded:Connect(function(Character)
local Value = Player:GetAttribute('CharacterSelected')
local SELECTED = RealCharacters[Value]
SELECTED:LoadCharacterAsSelf(Player)
end)
end)
FOR MORE ABOUT ATTRIBUTES:
→ See more here: Attributes - Now Available!
What this basically does is add a piece of data to the Player object inside the game that the server and client can read later down the line.
Now revisiting the old server script, (we don’t need to change anything in the button script), we will modify the RemoteEvent detection to change the value of the ‘CharacterSelected’ value within the player if the given character name is valid.
We can check if the character name is valid by utilizing a module as a ‘lookup’ table.
-- inside old script that detected RemoteEvent.
local REP = game:GetService('ReplicatedStorage')
local CharacterSelect = REP:WaitForChild('CharacterSelect')
local RealCharacters = require(REP:WaitForChild('RealCharacters'))
CharacterSelect.OnServerEvent:Connect(function(Player, CharacterName)
-- here we check if the character name is inside the 'Characters'
-- module by indexing with brackets
local SELECTION = RealCharacters[CharacterName]
if SELECTION then
-- tell the server to label the player as this 'Selected Character'
Player:SetAttribute('CharacterSelected', CharacterName)
end
end
Once we set the Player’s CharacterSelected to the character name, the event we connected earlier; “GetAttributeChangedSignal”, will fire, causing the code inside that function block to run. In that function’s case, it will be firing a function that will be held within the character data within the module we required. So let’s create the module.
I recommend creating this module script in ReplicatedStorage as it can be accessed by both client and server which is great for this type of game.
This module script will serve as the main hub for selected character data as mentioned before: I named it ‘RealCharacters’ for the sake of this example but you can name it whatever you see fits.
Now for this example I’m going to be using OOP or Object Orientated Programming as it’s perfect for creating new characters on the fly.
→ See more here: All about Object Oriented Programming
To start creating a new character we first want to define a class by utilizing metatables.
→ See more here: Metatables | Documentation - Roblox Creator Hub
-- this is our class table
local CHARACTER_CLASS = {}
CHARACTER_CLASS.__index = CHARACTER_CLASS -- circular index for class to work
-- we will define some properties our character class will have
CHARACTER_CLASS.Backpack = {} -- items the character loads the player with
-- other properties as an example to give
-- you the jist of what this is capable of.
CHARACTER_CLASS.Health = 250
CHARACTER_CLASS.Speed = 100
-- we define a function that only this class can have.
function CHARACTER_CLASS:LoadCharacterAsSelf(Player : Player, ReloadCharacter : boolean)
-- brief introduction to the variable "self" if you're not knowledgable of it.
-- self is used inside functions that have ':' connecting it.
-- It's implication is that we are referring to the table the function
-- is being called in which in this case is the uniquely created class
-- we call this in. PLEASE TAKE SOME TIME TO LEARN THIS STUFF! IS
-- MEGA IMPORTANT AND VERY VERY USEFUL TO KNOW!
-- reload character
if ReloadCharacter then
Player:LoadCharacter()
end
-- clone all existing items found within the class's backpack
local Backpack = self.Backpack
for i = 1, #Backpack do
local Index = Backpack[i]
Index:Clone()
Index.Parent = Player
end
-- this was the optional stuff i put in the class to show as an example
local Health, Speed = self.Health, self.Speed
local Humanoid = Player.Character:FindFirstChildWhichIsA(''Humanoid")
Humanoid.MaxHealth = Health
Humanoid.Health = Health
Humanoid.WalkSpeed = Speed
end
-- a function used in creating 'instances' (unique clones) of the class we just created.
local function CreateCharacter(data)
-- asserts are used to error if the condition inside of them isn't true.
-- in this case, we're checking if data sent to this function is a valid table.
-- if not? error and find the problem by debugging.
assert(type(data) == 'table', DATA NEEDS TO BE GIVEN)
-- any table created inside a metatable class will have it's referenced drawn back
-- to it which isn't good because we want a unique 'inventory' for each character
-- created.
data.Backpack = data.Backpack or {}
return setmetatable(data, CHARACTER_CLASS)
end
-- this is our module space
local RealCharacters = {}
return RealCharacters
Now let’s actually implement our new character class.
-- this is going to be inside the 'RealCharacters' module space
-- we are going to use that 'CreateCharacter' func we created before
local REP = game:GetService('ReplicatedStorage')
local RealCharacters = {
-- we can use "{}" instead of "()" at the end because a table
-- is the functions only argument
Example1 = CreateCharacter{
Backpack = {
REP:WaitForChild('ClassicSword')
},
Speed = 16,
Health = 250
}
}
return RealCharacters
And with that you should be done. Just remember to set the attribute to a character created inside the table; otherwise it will error.
I may have made a lot of mistakes writing this, so please exercise caution and make your own adjustments to my methodology. There is of course a lot more that can be done to make this more optimal, but I wanted to make it pretty simple. A project like this requires a lot of knowledge so don’t skip out on the learning aspect and make sure you really understand what’s being presented! I am not a good teacher by any means, but this is the best I can come up with at the moment.