I want to achieve creating parts that play music whenever you touch them. Currently it works, however there are some inconsistencies.
The issues are:
Sometimes, whenever leaving and entering the hitbox, the music will not play.
Whenever jumping, it will call the TouchEnded event, which will stop the music
I have been considering Region3’s, however, you will need to detect them through while loops or RunService.Heartbeat. This will be performance heavy whenever having multiple loops.
Code
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local resources = ReplicatedStorage.Resources
local musicHandler = require(resources.MusicHandler)
local touchUtil = require(resources.TouchUtil)
local CharacterHitbox = {}
local currentSoundPlaying
function CharacterHitbox.DetectedMusicHitbox(hitbox)
local oldCurrentSoundPlaying = currentSoundPlaying
local success, err = pcall(function()
currentSoundPlaying = musicHandler.GetAudio(hitbox.Name)
end)
if not success and oldCurrentSoundPlaying then
print("no success")
oldCurrentSoundPlaying:Stop()
oldCurrentSoundPlaying = currentSoundPlaying
currentSoundPlaying = nil
end
if oldCurrentSoundPlaying == currentSoundPlaying then
print("the same audio")
return
end
if currentSoundPlaying then
print("play!")
currentSoundPlaying:Play()
end
end
function CharacterHitbox.Init()
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local rootPart = character:WaitForChild("UpperTorso") or character:WaitForChild("Torso")
local function isPointInPart(point, part)
local relPos = part.CFrame:PointToObjectSpace(point)
local multiplier = 0.5
return math.abs(relPos.X) < part.Size.X * multiplier and math.abs(relPos.Y) < part.Size.Y * multiplier and math.abs(relPos.Z) < part.Size.Z * multiplier
end
rootPart.Touched:Connect(
touchUtil.TouchedDebounce(1, function(hit)
local pointInPart = isPointInPart(rootPart.Position, hit)
print(isPointInPart(rootPart.Position, hit))
if CollectionService:HasTag(hit, "MusicHitbox") then
if isPointInPart(rootPart.Position, hit) then
CharacterHitbox.DetectedMusicHitbox(hit)
elseif not isPointInPart(rootPart.Position, hit) then
CharacterHitbox.DetectedMusicHitbox(nil)
end
end
end)
)
end
return CharacterHitbox
Clarifications
TouchUtil is a class that handles Touched events and TouchEnded events. All of this works.
MusicHandler is a module that plays, pauses, and stops music. That also works, too.
MusicHitboxHandler.Init is called. You should not worry about that
I would like to mention that TouchEnded is very unreliable and has been discussed all around there is a thread somewhere about the same thing you are trying to achieve, let me find it for you
It does mention the use of Region3 and I understand your concern of Performance,
Instead of Region3 use Magnitude instead and don’t use multiple loops
What I would do is Simply check when the player is moving then check if the Player is near an Area enough, once you know they are in an area you don’t have to check for every area only check for the same area until they leave that area then find which new area they are closets to.
Or you could use Touched and Magnitude instead or with Region3, many ways of achieving the same thing but you gotta be smart about it.
Hey I’ve thought of that too but I’m not particularly familiar with Vector Math so I couldn’t give him the Formula but I like your method a lot and I strongly recommend using it!
I actually want to ask a question. Do I call this function in the Touched event?
EDIT: I called the function in the Touched event. However, the music stopping whenever the character jumps is still a problem
Could make a check in the function for whether the music should end to see whether the player is jumping via Humanoid.Jump’s Boolean value. Though, you might want to pair that alongside checking whether their position is still within your box in case they like jumping to get in and out.
You call this in a loop actually. I would probably do it something like this:
while true do
local isInPart = false
for _, limb in ipairs(character:GetChildren()) do
if limb:IsA("BasePart") and isPointInPart(limb.Position, hitbox) then
isInPart = true
break
end
end
if isInPart and not music.Playing then
music:Play()
elseif not isInPart and music.Playing then
music:Stop()
end
-- wait function here
end
A bunch of optimizations can be done here to run this function less frequently or on less parts, but that will be your adventure.
Edit: fixed it, now it shouldnt turn on and off a bunch for no reason.
You could also try checking the distance between the part and the player… for example:
if (((math.abs(Player.Character.PrimaryPart.Position.X) - math.abs(Part.Position.X)) <= Part.Size.X/2) and (((math.abs(Player.Character.PrimaryPart.Position.Y) - math.abs(Part.Position.Y)) <= 5) and (((math.abs(Player.Character.PrimaryPart.Position.Z) - math.abs (Part.Position.Z)) <= Part.Size.Z/2) then
This will check if the character’s position is above (technically below too) the part. The variables ‘Part’ and ‘Player’ will need to be set while using this. Using the GetPropertyChangedSignal event would work well with this to reduce lag.
Edit: you may want to increase the required distance required or the player might end up having to stand directly in the center of the part.
I have remade the code. It works so much better than the older one, however, there is still one problem.
The isPointInPart function seems to give a false positive whenever the player goes outside of the hitbox. Whenever the character walks back in, now the TouchEnded part would fire. However, the Touched part would fire since they go back inside the part.
(I changed the original post to the new code)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local resources = ReplicatedStorage.Resources
local musicHandler = require(resources.MusicHandler)
local touchUtil = require(resources.TouchUtil)
local CharacterHitbox = {}
local currentSoundPlaying
function CharacterHitbox.DetectedMusicHitbox(hitbox)
local oldCurrentSoundPlaying = currentSoundPlaying
local success, err = pcall(function()
currentSoundPlaying = musicHandler.GetAudio(hitbox.Name)
end)
if not success and oldCurrentSoundPlaying then
print("no success")
oldCurrentSoundPlaying:Stop()
oldCurrentSoundPlaying = currentSoundPlaying
currentSoundPlaying = nil
end
if oldCurrentSoundPlaying == currentSoundPlaying then
print("the same audio")
return
end
if currentSoundPlaying then
print("play!")
currentSoundPlaying:Play()
end
end
function CharacterHitbox.Init()
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local rootPart = character:WaitForChild("UpperTorso") or character:WaitForChild("Torso")
local function isPointInPart(point, part)
local relPos = part.CFrame:PointToObjectSpace(point)
local multiplier = 0.5
return math.abs(relPos.X) < part.Size.X * multiplier and math.abs(relPos.Y) < part.Size.Y * multiplier and math.abs(relPos.Z) < part.Size.Z * multiplier
end
rootPart.Touched:Connect(
touchUtil.TouchedDebounce(1, function(hit)
local pointInPart = isPointInPart(rootPart.Position, hit)
print(isPointInPart(rootPart.Position, hit))
if CollectionService:HasTag(hit, "MusicHitbox") then
if isPointInPart(rootPart.Position, hit) then
CharacterHitbox.DetectedMusicHitbox(hit)
elseif not isPointInPart(rootPart.Position, hit) then
CharacterHitbox.DetectedMusicHitbox(nil)
end
end
end)
)
end
return CharacterHitbox
Another solution which would require a lot less code would be having a transparent, noncollidable part over the other part. This new part would sense when the player touches it and would have the height the player jumps to. Of course, Part.Touched does not work with noncollidable parts, so you would have to use Part:getTouchingParts().
I would use a transparent noncollidable part within the section that you want the player to hear the music. Keep the .Touched and .TouchEnded, and make sure you add debounce.
The .Touched will work with non-collidable parts, just make sure it’s anchored.
If you’re problem is related to the Touched and TouchEnded events, @nooneisback wrote this script a month back that, basically, works as a pseudo Touched and TouchEnded event.
You can edit it to make it so it works for multiple parts (Personally, I did this by checking which out of a group of parts was the closest to the player and then setting it so it would check if the player was touching the part, though that’s probably not the most efficient way, it works)
If you want an explanation as to what this script actually does, the below contains just that.