I have two unanchored welded parts. One is the normal main part. The other is the coin. I want the coin to be welded to the main part. Which it does. However animating it going slightly up and down and fully rotating is a new issue. I just want the coin to follow its animation like it’s “anchored” but still move and everything with its main part which gets moved by physical forces.
--//============================ ANIMATION SYSTEM ============================//
do
local angle = 0
RunService.Heartbeat:Connect(function(dt)
angle = angle + ROTATION_SPEED * dt/10
local bobOffset = math.sin(dt) * BOB_AMPLITUDE * 0
for _, coin in ipairs(CollectionService:GetTagged("GreenStar")) do
coin.CFrame = coin.CFrame * CFrame.new(0, bobOffset, 0) * CFrame.Angles(0, angle, 0)
coin.Weld.C0 = coin.Parent.PrimaryPart.CFrame:ToObjectSpace(coin.CFrame) * coin.Weld.C0:Inverse()
end
end)
end
Full module
local Collectables = {}
--//============================ SERVICES & CONFIGURATION ============================//
local CollectionService = game:GetService("CollectionService")
local ServerStorage = game:GetService("ServerStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local COLLECTABLES_FOLDER = workspace:FindFirstChild("Collectables")
local COIN_TEMPLATE = ServerStorage.Collectables:WaitForChild("GreenStar")
-- Maze dimensions (must match the generator script)
local CELL_SIZE = 16
local PLANK_LENGTH_IN_CELLS = 3
-- Animation Parameters
local BOB_AMPLITUDE = 0.5
local BOB_SPEED = 1
local ROTATION_SPEED = math.rad(1)
--//============================ PRIVATE FUNCTIONS ============================//
local function onCoinTouched(otherPart, coin)
local character = otherPart.Parent
local player = character and Players:GetPlayerFromCharacter(character)
if player then
Collectables.collect(coin, player)
end
end
--//============================ PUBLIC MODULE FUNCTIONS ============================//
--- [PART 1] Calculates the center position for EVERY cell within a given block model.
-- This method is robust and works even if cells have holes instead of solid floors.
-- @param block The Model of the block/plank to find positions in.
-- @return table A table of Vector3 positions, one for each cell's center.
function Collectables.getSpawnPositionsInBlock(block)
local primaryPart = block.PrimaryPart
local plankCenter = primaryPart.Position
local plankSize = primaryPart.Size
local positions = {}
-- Determine the plank's orientation by checking its longest dimension.
local mainAxisVector
if plankSize.X > plankSize.Z then
mainAxisVector = Vector3.new(1, 0, 0)
else
mainAxisVector = Vector3.new(0, 0, 1)
end
-- The offset from the plank's absolute center to the center of the first cell in the sequence.
-- For 3 cells, this is `(3-1)/2 * 16 = 16`.
local startOffset = (PLANK_LENGTH_IN_CELLS - 1) / 2 * CELL_SIZE
-- Calculate the world-space position of the very first cell's center.
local firstCellCenter = plankCenter - mainAxisVector * startOffset
-- Loop through each of the cells in the plank.
for i = 0, PLANK_LENGTH_IN_CELLS - 1 do
-- Calculate this cell's center by moving along the main axis.
local cellCenter = firstCellCenter + mainAxisVector * (i * CELL_SIZE)
table.insert(positions, cellCenter)
end
return positions
end
--- [PART 2] Spawns a single coin at a specific world position and welds it to a block.
-- @param position The world-space Vector3 where the coin should be centered.
-- @param block The block model whose PrimaryPart the coin will be welded to.
-- @return The newly created coin instance, or nil if spawn failed.
--- [PART 2] Spawns a single coin at a specific world position and welds it to a block.
-- @param position The world-space Vector3 where the coin should be centered.
-- @param block The block model whose PrimaryPart the coin will be welded to.
-- @return The newly created coin instance, or nil if spawn failed.
function Collectables.spawnAt(position, block)
if not position or not block or not block.PrimaryPart then
return nil
end
local newCoin = COIN_TEMPLATE:Clone()
-- CRITICAL FIX 1: The coin must be unanchored to be welded to an unanchored block.
newCoin.Anchored = false
newCoin.CanCollide = false -- Also good practice
newCoin.Parent = block
-- We do not set the coin's CFrame directly, the weld will do this for us.
local weld = Instance.new("Weld")
weld.Part0 = newCoin -- The part that moves (the coin)
weld.Part1 = block.PrimaryPart -- The static reference part (the block)
weld.C0 = block.PrimaryPart.CFrame:ToObjectSpace(CFrame.new(position))
weld.Parent = newCoin
CollectionService:AddTag(newCoin, "GreenStar")
newCoin.Touched:Connect(function(otherPart)
onCoinTouched(otherPart, newCoin)
end)
return newCoin
end
--- A wrapper function that spawns a coin in every cell of a given block.
-- @param block The block model to spawn coins in.
function Collectables.spawnInBlock(block)
local spawnPositions = Collectables.getSpawnPositionsInBlock(block)
for _, pos in ipairs(spawnPositions) do
Collectables.spawnAt(pos, block)
end
end
--- Handles the server-side logic for collecting a coin.
function Collectables.collect(coin, player)
if not coin or not CollectionService:HasTag(coin, "GreenStar") then return end
CollectionService:RemoveTag(coin, "GreenStar")
local FadeTween = game:GetService("TweenService"):Create(coin, TweenInfo.new(.8, Enum.EasingStyle.Sine), {Transparency = 1})
local Sound = game.ReplicatedStorage.Sounds.CollectGreen:Clone()
Sound.Parent = coin
Sound:Play()
local Sound = game.ReplicatedStorage.Sounds.Background:Clone()
Sound.Parent = coin
Sound:Play()
FadeTween:Play()
FadeTween.Completed:Connect(function() coin:Destroy() end)
end
--//============================ ANIMATION SYSTEM ============================//
do
local angle = 0
RunService.Heartbeat:Connect(function(dt)
angle = angle + ROTATION_SPEED * dt/10
local bobOffset = math.sin(dt) * BOB_AMPLITUDE * 0
for _, coin in ipairs(CollectionService:GetTagged("GreenStar")) do
coin.CFrame = coin.CFrame * CFrame.new(0, bobOffset, 0) * CFrame.Angles(0, angle, 0)
coin.Weld.C0 = coin.Parent.PrimaryPart.CFrame:ToObjectSpace(coin.CFrame) * coin.Weld.C0:Inverse()
end
end)
end
--]]
return Collectables
How do i currently position the weld with the coin as to not fling everything?