How do you make a part vibrate?

Basic explanation:

Use a loop to change the CFrame using math.sin along the x axis like so

while wait() do -- whichever you decide
    part.CFrame = CFrame.new(math.sin(2), math.sin(1), math.sin(2))
end

Many different ways of achieving this. Experiment until you find one you like.

2 Likes

Why do we have to use math.sin() specifically there?

Is it required to do this? or can we just use normal numbers, why not other math functions too?

I’m not sure why you are using math.sin() here and we could use math.tan and math.cos too? since we could just get the normal CFrame numbers of the location, why specifically math.sin()?

Also worth noting that math.sin() returns the number in radians.

And please everyone avoid using while wait() do because it’s bad practice go to this to know why:

The While-Wait-Do Idiom, by cntkillme: Addressing while wait() loops

Instead you can use RunService’s Heartbeat Event as an alternative.

2 Likes

Use TweenService

local TweenService = game:GetService("TweenService")
local TweenSettings = TweenInfo.new(0.01, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, true, 0)

local function CreateVibration(Part, Duration)
    for i = 1, Duration do
        local RandomX = math.random()
        local RandomY = math.random()
        local RandomZ = math.random()
        --math.random by default gets a number between 0 and 1
        local VibrationVector = Part.Position + Vector3.new(RandomX, RandomY, RandomZ)

        local Tween = TweenService:Create(Part, TweenSettings, {Position = VibrationVector})
        Tween:Play()
        Tween.Completed:Wait()
    end
end

CreateVibration(workspace.Part, math.huge)
1 Like

As I said,

while wait() do is not bad practice. It is the same as while true do.

1 Like

Please read the thread by @colbert2677 which explains why you shouldn’t use while wait() do that I also linked in my previous post, it’s bad practice and you should always avoid using it.

You could use :GetPropertyChangedSignal() or the .Changed Event or RunService’s Events as an alternative, you are spreading misinformation and encouraging people to always use a bad practice which are both very bad…

1 Like

I read it long ago and it clearly states theres little no difference.
In my experience while true do often bugs and delays the thread in roblox lua.
Stop calling it bad practice, it isn’t.

The biggest misinformation is you repeating that it is.

1 Like

No it isn’t. It isn’t “bad” practice, but it’s inefficient.

It was proven that doing

while true do
    wait()
end

was faster than doing

while wait() do
end

I’ve done this experiment for you - I ran the following code:

local sT, c = tick(), 1
while wait() do
	c = c + 1
	if c > 100 then
		break
	end
end

print(tick() - sT)

sT, c = tick(), 1
while true do
	wait()
	c = c + 1
	if c > 100 then
		break
	end
end

print(tick() - sT)

Here are my results:

A - 4.8323 seconds
B - 3.3330 seconds

By doing it correctly, you’re shaving well over a second of off this loop. And this compounds - if it was a loop of a thousand, I’d save probably to the order of 10 seconds.

I don’t know what on earth you mean by “while true do bugs” - I’ve seen no evidence of this ever.


Also, why are you doing math.sin ? - I don’t see any use of static trigonometry here - you’re also not changing the rotation of the part (just the position), and since CFrame is an absolute (world) position, and you’re not doing any random usage, your code won’t function at all


@DragRacer31 Here is some code which will do what you want

local Generator = Random.new()
local OriginalCFrame = part.CFrame
while true do
    wait(0.1)
    part.CFrame = OriginalCFrame * CFrame.new(Generator:NextNumber(), Generator:NextNumber(), Generator:NextNumber())
end

It uses a Random number generator to provide random vibrations around from the part’s position - but it won’t slowly drift away as it remembers the original part’s Coordinates.

4 Likes

In my own experience while true has delayed for me, when I use while wait() it went smoothly so I’m not sure

Turns out that, whilst for larger values of n the difference is statistically insignificant (variance ±0.01), the initial values for while wait() fluctuate / spike (statistically significant). It falls roughly between the O(log(n)) and O(n) time function (not ideal, not too bad either).

Honestly, the difference is minor - and using wait() at all is generally seen as bad practice (see Avoiding wait() and why or “Using Wait Wisely” by Erik Cassel). That’s why I didn’t say it’s bad practice.

In this particular case though, using wait seems unavoidable - or at least the most convenient. RunService:BindToRenderStepped might be a better solution though.

1 Like

These results highly fluctuate and are dependant on the game, not to mention it also depends on how you use it.
At times i got ~3.7 seconds with while true do wait() and ~3.3 seconds with while wait() do.
I’m sorry, but did you make sure that your results are stable by running it several times?

I also ran this:

local sT, c = tick(), 1
while wait() and c < 100 do
	c = c + 1
end

print(tick() - sT)

and

local sT, c = tick(), 1
while true and c < 100 do
    wait()
    c = c + 1
end

print(tick() - sT)

and while wait() won by approximately 0.4 seconds every time.

This is a very bad and unstable example; there isn’t enough stable proof that while wait() do is indeed slower.

In my opinion arguing about which looping method is better is child-like and barely has any impact on the game, unless there is evident proof against such.
So far I have only seen one reason against while wait() do, which is that people often do

while wait() do
    if x > y then
        break
    end
end

instead of

while x < y do
    wait()
end

but by going with this argument, didnt you just contradict yourself by not doing as such?

1 Like

Yes - I ran the script three times on a linear increase and had the same results. The initial spike was replicated each time. What matters isn’t the exact time (which will change according to processor demand), but the trend line.

It is conceivable that the spike is only happening on my computer - however I found that it only happened with the while wait() and not while true each time. This lead me to believe it meets the threshold for statistical significance.

I’m arguing about this as a technicality. I’ve already suggested better alternatives to using while wait() or while true loops, as these are not good practice to implement in most situations.

My guess as to the issue is compile-time optimisation - the Lua compiler can’t simplify the instruction for the loop to be an infinite loop, instead it has to compare the function call from wait() each time to establish if the loop should continue, causing that variance.

2 Likes

while true do is compiled as a single jump whilst while wait() do is compiled as a test and a jump, which can thus lead to some minor drawbacks at the start, but other than that there’s nothing else.

From my understanding there is a difference between yielding and checking return contents. Doing a comparison operation every iteration will add up.

while (wait() == true) do -- has to establish wait() is truthy

vs

while true do

At the assembly level, I assume that the top one can be optimised to be jmp, whilst the comparison will have to perform a branch comparison?

I’ll have to actually compile it to bytecode to understand if this is the case, but I have the evidence (see above) that this is likely, as well a plausible hypothesis. It doesn’t even matter except at great scale or accuracy.

image

Imagine it like a diagram;
the first graph starts at origo (zero) and goes from there - thats while true do.
the second graph starts at a higher value than origo due to test, but it still increases by the same amount - thats while wait() do.

I barely know anything regarding VM instructions, so this is probably incorrect.
Don’t take my word for it :slightly_smiling_face:

while true vs while wait() is kind of moot here, since using a while loop to animate a part is already a bad practice. While loops will not resume if any error is thrown from within the loop body. This is a common source of bugs where a developer notices something in their game “just stops working after a while”. Your main game loops should also never be in infinite while loops, for the same reason.

The best practice is to update the part vibration on a RunService event, not in any kind of loop, because this will continue working for the life of the server (or client session).

1 Like

It will be rough. math.random() is white noise, so it’s not modeling continuous motion, it’s giving your part a position as a probability function (uniform along some line segment in space, in the case of my example). But… Roblox is only rendering (normally) at 60 Hz, so a single sine function with a frequency significantly above 30 Hz is going to look pretty much random too, especially if it’s not a multiple of 30 Hz and in sync with rendering. If you want the direction of motion, and the sinusoidal nature of the vibration to be perceivable, the vibration frequency should be well below 30 Hz. If you want vibration to be maximum (30 Hz), just use a boolean to toggle between two positions every other frame. Vibrations faster than 30 Hz are not possible to show at a 60 Hz rendering rate for the obvious reason that alternating between two positions is the fastest discretely-representable periodic oscillation.

2 Likes

Does that mean that Random.new():NextInteger() is better to use than math.random() or something or are they the same?

The new Random class is generally better for game development, primarily because it gives you control over seeding it and knowing that only your code is using it, guarantee you still don’t get with math.random().

But, your example is not how you want to use it. Random.new() constructs a new random number generator instance, something you certainly don’t want to do for every call to NextInteger or NextNumber. Performance of that would be terrible, in both speed and memory usage, and you would not even be getting the random generator performance of the Random class, because you’d only be pulling one value from each, so your randomness would only be as good as whatever is being used to seed the Random instance, not the generator itself! You’re meant to make one instance and re-use it like this:

local RNG = Random.new(someSeed)
local randomInt = RNG:NextInteger()
local randomFloat = RNG:NextNumber()
local anotherInt = RNG:NextInteger()
...
etc

You should only make one Random.new() call for each independent random number stream you need.

2 Likes

what if I wanted to vibrate it along its orientation?

You adjust the orientation property instead of the CFrame/Position property.