MarioKart fused with Monkey Ball type of game

Hey all! I have successfully injected my own code into the Roblox studio tour gyro balls for the new game I am making that is like mario kart and monkey ball fused together.
The code I had altered is within the Occupant script and gave it a new module script for “abilties” players can get. It works very nicely but I am still very basic minded when it comes to scripting so I would like some feedback on what can be improved and better organized or made more efficient.

local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local enterBall = require(script.enterBall)
local exitBall = require(script.exitBall)
local abilities = require(script.ability)

local ball = script.Parent.Parent.Parent
local driverSeat = ball.DriverSeat
local enterPrompt = ball.EnterPrompt
local exitPrompt = ball.ExitPrompt
local abilityPrompt = ball.AbilityPrompt
local sounds = ball.DriverSeat.Sounds
local fx = ball.DriverSeat
local basepart = ball.Exterior.CollisionBall
local abilnumber = ball.DriverSeat.AbilityType
-- If Ball speed is less than this value and occupant exists, exit prompt will appear
local EXIT_PROMPT_SPEED_THRESHOLD = 1
local MIN_TIME_BEFORE_EXIT_PROMPT_SHOWN = 2
local exitPromptConnection

-- Get any "disable exiting ball" parts
local disableExitBallBoundsParts = CollectionService:GetTagged("DisableExitingBall")

-- Listen for conditions that change whether the exit prompt is enabled
local function trackExitPromptEnabled(): RBXScriptConnection
	local connection = RunService.Heartbeat:Connect(function()
		-- check if ball is in the disable exit prompt bounds
		local isBallInDisableExitPromptBounds = false
		for _, disableExitBallBoundsPart in disableExitBallBoundsParts do
			local partsInBounds =
				Workspace:GetPartBoundsInBox(disableExitBallBoundsPart:GetPivot(), disableExitBallBoundsPart.Size)
			for _, part in partsInBounds do
				if part:IsDescendantOf(ball) then
					isBallInDisableExitPromptBounds = true
					break
				end
			end
		end

		-- check if ball speed is slow enough to exit the ball
		local isBallSpeedLow = false
		local ballVelocity = ball.PrimaryPart.AssemblyLinearVelocity
		if ballVelocity.Magnitude < EXIT_PROMPT_SPEED_THRESHOLD then
			isBallSpeedLow = true
		end

		-- enable exit prompt if ball spped is slow enough and ball is not in the disable exit prompt bounds
		if isBallSpeedLow and not isBallInDisableExitPromptBounds then
			exitPrompt.Enabled = true
		else
			exitPrompt.Enabled = false
		end
	end)
	return connection
end

local function onOccupantChanged()
	local humanoid = driverSeat.Occupant
	enterPrompt.Enabled = humanoid == nil
	-- Exit prompt will initially be false regardless of the occupancy state
	exitPrompt.Enabled = false

	if humanoid then
		-- Delay allows player to explore controlling the ball without immediately being prompted to exit
		task.delay(MIN_TIME_BEFORE_EXIT_PROMPT_SHOWN, function()
			exitPromptConnection = trackExitPromptEnabled()
		end)
	else
		if exitPromptConnection then
			exitPromptConnection:Disconnect()
			exitPromptConnection = nil
		end
	end
end

------The code I injected below-------------
basepart.Touched:Connect(function(other)
	if other.Name == "MysteryBox" and driverSeat.abilityget.Value == false then
		local humanoid = driverSeat.Occupant
		driverSeat.abilityget.Value = true
		sounds["Space Chimes"]:Play()
		fx.GeneralAbilities.Stars.Enabled = true
		task.wait(4)
		sounds["Enemy Gadget - Brawl Stars SFX"]:Play()
		driverSeat.abilityget.Value = true
		task.wait(1)
		fx.GeneralAbilities.Stars.Enabled = false
		local number = math.random(1,5)
		task.wait()
		abilnumber.Value = number
		if number == 1 then
			abilityPrompt.ObjectText = "You got: Overload Ability!"
		elseif number == 2 then
			abilityPrompt.ObjectText = "You got: Damage Ring Ability!"
		elseif number == 3 then
			abilityPrompt.ObjectText = "You got: Fire Ball Ability!"
		elseif number == 4 then
			abilityPrompt.ObjectText = "You got: Ice Cube Ability!"
		elseif number == 5 then
			abilityPrompt.ObjectText = "You got: SUPER Ability!"
		end
		abilityPrompt.Enabled = true
			
	
	end
end)

local function initialize()
	driverSeat:GetPropertyChangedSignal("Occupant"):Connect(onOccupantChanged)
	enterPrompt.Triggered:Connect(enterBall)
	exitPrompt.Triggered:Connect(exitBall)
	abilityPrompt.Triggered:Connect(abilities)
end

initialize()

As I don’t see a lot of action here, here are my quick thoughts:
It doesn’t seem like something that is terribly complex and so it shouldn’t warrant much time optimizing (at least for runtime performance).

However, instead of

if number == 1 then
	abilityPrompt.ObjectText = "You got: Overload Ability!"
elseif number == 2 then
	abilityPrompt.ObjectText = "You got: Damage Ring Ability!"
...

you could store the messages in a list so you would have

local messages = {"You got: hello", "You got: world"}
...
abilityPrompt.ObjectText = messages[number]

and then you could choose number to be from 1 to length of messages (rather than hard-coding 5)

The advantage to this (in my opinion of course) is that it keeps that code simpler, especially as the number of abilities grow. You also might be able to pull the messages list and related out into a different script that could do more of actual ability itself (model, sounds, even the action maybe, et c.), under the idea of do one thing and do it well. For example, the ability part / model could be stored similarly (and then you just do something like abilityModels[number]). It would make it easier to make new abilities and easier to find where stuff is.

Important note though: premature optimization is much worse than dealing with a clunky system, I think as long as your code works, it’ll get you pretty far and use this as an idea if you feel it is getting too much.

Other than that, small nitpick items with naming:
it seems you / the example are using camelCase so abilityget could be abilityGet or to indicate that it is a boolean: hasAbility
similarly, abilnumber, as you write out ability fully in some places so to be consistent: abilityNumber

Hope this helps, and let me know if I am wrong or can explain anything better (again, if your code works, that is the most important part, then make it work well, then make it fast)

1 Like

As soon as I get home I will try it! I didn’t you could do such a thing with your suggestion of messages. I am still learning :rofl:. In terms of the naming conventions I will fix those to be better matching and etc.

I do have a question however if you able to help. I had changed the function for the ability portion recently as I had equipped the boxes with a boolvalue to determine if they were active or not. The new check system put in ended up resulting in about 2 of 10 boxes to work.

The boxes do work, when they touch a part of the vehicle they set their boolvalue to false and go ‘invisible’ for a short time before going back to normal. That part works. I included print() checks to see what part of the script is getting stuck because it isn’t returning any errors, however yes the function works. But it is getting stuck with getting the boolvalue from the parts it touches even though like I said, it works for 2 out of 10 boxes. I’ll follow up with the updated code:

Here’s the updated portion. I’m just not sure what I am doing wrong.

basepart.Touched:Connect(function(other)
	if other.Name == ("MysteryBox") and driverSeat.abilityget.Value == false then
		print("Passed Initial Check")
		local boxval = other:findFirstChild("Active")
		if boxval.Value == true then
			print("Boxval Passed")
			driverSeat.abilityget.Value = true
			sounds["Space Chimes"]:Play()
			fx.GeneralAbilities.Stars.Enabled = true
			task.wait(4)
			sounds["Enemy Gadget - Brawl Stars SFX"]:Play()
			driverSeat.abilityget.Value = true
			task.wait()
			fx.GeneralAbilities.Stars.Enabled = false
			local number = math.random(1,7)
			task.wait()
			abilnumber.Value = number
			if number == 1 then
				abilityPrompt.ObjectText = "You got: Overload Ability!"
			elseif number == 2 then
				abilityPrompt.ObjectText = "You got: Damage Ring Ability!"
			elseif number == 3 then
				abilityPrompt.ObjectText = "You got: Fire Ball Ability!"
			elseif number == 4 then
				abilityPrompt.ObjectText = "You got: Ice Cube Ability!"
			elseif number == 5 then
				abilityPrompt.ObjectText = "You got: SUPER Ability!"
			elseif number == 6 then
				abilityPrompt.ObjectText = "You got: Blizzard Ability!"
			elseif number == 7 then
				abilityPrompt.ObjectText = "You got: Bomb Ability!"
			end
			abilityPrompt.Enabled = true
		else
			print("Boxval Failed")
		end	
	
	end
end)

Also I had inputted the list that you suggested into both the abilityPrompt and as a way to call abilities. It took me a bit to understand how to call it to action but i figured it out!

local abilityNumber = number.Value
	local ability = {overloadAbility, damageRingAbility, fireballAbility, icecubeAbility, superAbility, blizzardAbility, bombAbility
	}
	local powerUp = ability[abilityNumber]
	powerUp()

I can’t spot anything right off the top for that code, I can check it out in more depth fairly soon when I have a chance but in the mean time here are a few things to try:

  1. I’d try to see why the boxes are different? Are they manually copied and pasted (therefore allowing for a change to be made in some but not other) or are they spawned in from a script? Are there any naming conflicts? These are just to get you started, I’d try to think of other ways they could be different and how that affects your script.
  2. You have the print statements in, which ones are triggering? Without knowing, and seeing how the code looks fine, maybe the BoolValues are never set true? (And indeed, where are they set true?)
  3. Although it probably isn’t needed for this, if you want, you can figure out how to use the debugger (Roblox Studio Debugging). You can set breakpoints in the script and step through to see why it doesn’t trigger.

Nice work on getting the items in the list. There is a whole level of abstraction using object oriented programming (OOP) if you want. Again, I think it would be overkill, but it never hurts to learn something. There are many guides and discussions, but doing a quick search: DevForum OOP Guide and inside is linked the detailed External Lua OOP Guide (this link is external to lua.org). Again, this is more complex and overkill, so I’m just putting it here for the future as a place to start.

A few other notes:

  1. When doing tests for boolean values, you can just do if someBooleanVariable as if statements are just looking for a boolean value and xx == true evaluates to true or false which is all xx could be anyways (if that makes any sense)
    In your case to be specific: you’d still need boolValue.Value but just not boolValue.Value == true.
    Again, a nitpick, but it is often good style (and it reads nicely, e. g. if not hasAbility.Value)…
  2. For the lists containing the ability functions, the ones containing the messages, et c. I often put those at the top with the other constants (and in a style so you know they are mean to be edited by hand and not in code). I do this so that I can easily see what the parameters are to my script: once it works, I only care to tweak/add/remove something, and the first place to look is in the constants, so make that as easy as possible and lower the chance of breaking code.

This was a bit rushed, so let me know if I can fix anything or if I missed something, hope it helps.

I will gladly look into those guides!

  • And about the boolvalue and code, the values do change from true to false.
  • The hit boxes, scripts and values are all the same,
  • The hit boxes are manually copied and pasted, what’s the difference? is there some sort hidden attribute when it gets manually copied?
  • With the print() erroressages I can see that it passes the first argument every time:
if other.Name == ("MysteryBox") and driverSeat.abilityget.Value == false then
		print("Passed Initial Check")

But the line after, is where it’s getting stuck:

local boxval = other:findFirstChild("Active")
		if boxval.Value == true then
			print("Boxval Passed")

In this case when the box gets hit by the players vehicle, it assigns a local boxval to the boolvalue within the box named Active. And if it’s true, it passes. But like said it works 2 out of 10 times on a specific box and no other boxes even if the other boxes are just duplicates. Finding information on getting values from a hit object is very scarce lol.
Would setting the if argument to " if boxval.Value ~= false then" work better? Or would scripting in the boxes to be cloned into their positions be beneficial?
Sorry if I am all over the place in this reply, I’m trying to make sure I answer every question with the most information I can! Please do take your time responded, I am in no rush :stuck_out_tongue_closed_eyes:

You’re all good, I tend to write a lot so no need to address it all at once. In terms of the manually copying and pasting, its not that the copying is a problem per se rather that its possible to modify some and not others (possibly explaining why some boxes work and others don’t). Here’s my general thought process:

  1. It triggers and finds the BoolValue for all of the mystery boxes, so it can’t be something easy like a different name (or anything else that would cause an error)
  2. We know that the BoolValue is false (from the printed results)
  3. So they are false when they should be true

If the above logic is correct, the issue probably lies in some other piece of code. So we can then ask:

  1. What sets those BoolValues to be true?
  2. If you manually set them all true, then start the game and test the boxes, what happens?
  3. As you debug that code: what do you want that logic to be and does that work?

I’m mainly trying to work starting from where we know something doesn’t happen (the code you have above) back to every part that affects it.

My mental model of the problem assumes a structure similar to this:
Ball

  • Ball
    • Above script (handling hitting boxes)
  • Box
    • Active (the BoolValue that isn’t triggering)
    • Box reset script (this handles setting Active to true somehow) ← this script is what I’ve made this post about

Hopefully this points you in a general right way, but I’d look for any scripts that deal with the boxes and look at them.

So the script in the boxes, does trigger the Active boolvalue. The script is set up so when a ball hits the box the active value gets set to false so someone else can’t use it. Then after a few seconds, the value goes back to being true. I’ve sat there hitting the boxes over and over and they do work and go from true to false like they are suppose to.
What I am thinking is am I doing the second argument check right?

local boxval = other:findFirstChild("Active")
		if boxval.Value == true then
			print("Boxval Passed")

Does this even make sense? It’s always that bit of code that is getting skipped. So it’s been making me flustered as to why. I’ll take a look at the boxes again and I’ll see if there’s something I am missing.

But you have been a huge help and given great insight to things I wouldn’t even know to start with, so thank you!

It took me a lot of trial and error and websearching information but I have finally found the fix!

basepart.Touched:Connect(function(other)

	local touchedBoolValue = other:FindFirstChild("Active") 		
	if touchedBoolValue then
			local boolValueState = touchedBoolValue.Value 
	if other.Name == ("MysteryBox") and boolValueState == true and driverSeat.abilityget.Value == false then
	

Update: between the confusion the script was in with detecting the Active bool value, I also set the detector in the ball to the outer most part that would hit it first and it works like a charm now