If you are making a square dice the best way (in my opinion) to determine the top would be with CFrames and RayCasting. Here’s a script I whipped up to demonstrating my method

local Dice = -- Your Dice Part
local WidthOfDice = -- the Width of your dice
WidthOfDice = WidthOfDice + 0.1
repeat
wait()
until Dice.Velocity == Vector3.new(0,0,0)
local CastedDirection = "NONE"
for i = 1, 6 do
local rayOr = Dice.Position
local rayDir
if i == 1 then
CastedDirection = "UP"
rayDir = Dice.CFrame.UpVector * WidthOfDice/2
elseif i == 2 then
CastedDirection = "DOWN"
rayDir = Dice.CFrame.UpVector * (-1*WidthOfDice/2)
elseif i == 3 then
CastedDirection = "FRONT"
rayDir = Dice.CFrame.LookVector * WidthOfDice/2
elseif i == 4 then
CastedDirection = "BACK"
rayDir = Dice.CFrame.LookVector * (-1*WidthOfDice/2)
elseif i == 5 then
CastedDirection = "RIGHT"
rayDir = Dice.CFrame.RightVector * WidthOfDice/2
elseif i == 6 then
CastedDirection = "LEFT"
rayDir = Dice.CFrame.RightVector * (-1*WidthOfDice/2)
end
local RayPara = RaycastParams.new()
RayPara.FilterDescendantsInstances = {Dice}
RayPara.FilterType = Enum.RaycastFilterType.Blacklist
local raycast = workspace:Raycast(rayOr,rayDir,RayPara)
if raycast then
local hit = raycast.Instance
if hit.Name == "Top" then -- Change this to whatever the name of your ground is called
if CastedDirection == "BACK" then
-- Front of the Dice
end
if CastedDirection == "RIGHT" then
-- Left of the Dice
end
if CastedDirection == "LEFT" then
-- Right of the Dice
end
if CastedDirection == "FRONT" then
-- Back of the Dice
end
if CastedDirection == "DOWN" then
-- Top of the Dice
end
if CastedDirection == "UP" then
-- Bottom of the Dice
end
end
end
end

There is probably a better method out there but this one works (apologies for poor formatting)

Sounds like you might need to use CFrame lookvector. Look vector gets you the forward direction of a part. If the lookvector is pointing downwards, then you know the opposite side is the top. If it is pointing to the right, then you’ll need to take into account the dice orientation and then see which side it is facing. This method would probably take a little while to figure out (Maths)

Alternatively, you could make the dice have 6 invisible parts, representing each side. Then you can get the dice position + a Y offset, then do a raycast downwards towards the dice. Whatever invisible part it hits would be the number on top of the dice. This method is similar to what @ cjhatter is doing

You’ll want to look at the die’s CFrame.RightVector, .UpVector, and .LookVector Assuming it’s a 6-sided die, also check the negative versions of each vector.

This creates 6 vectors for each of the faces of the 6-sided die. Next,
check the y component of each vector. The vector which has the greatest y component is the face pointed up.

Incidentally, for the math geeks, this is taking the dot product of the up vector Vector3.new(0, 1, 0) with each of the 6 vectors and seeing which one is projects more onto it.

You can use this to get the top face of a 6 sided die:

function GetTop(Die)
local upVec = Vector3.new(0, 1, 0)
local maxDotValue, maxDotNormalId
for _, normalId in ipairs(Enum.NormalId:GetEnumItems()) do
local vec = Vector3.FromNormalId(normalId)
local diceVecInWorldSpace = Die.CFrame:VectorToWorldSpace(vec)
local dotValue = upVec:Dot(diceVecInWorldSpace)
if not maxDotValue or dotValue > maxDotValue then
maxDotValue, maxDotNormalId = dotValue, normalId
end
end
return maxDotNormalId.Name
end

The thing here, forgot to mention, is that it’s going to be 13 dices, and it will be, weirdly enough but justified too, 300 dices later on.

@cjhatter Also comes into here. If I’m doing 300 dices do you think this would be a good idea to do for all 300 dices? maybe if the server did all the computing, but it sounds like berserk processing (I would be abusing the servers pretty much).

It wasn’t my solution, I’m trying to find the source, but it was posted on the devforum a long time ago. Once I do I’m going to update my post to clear the confusion.

Could you explain the mathematics of how this works? I understand that whatever the biggest dot value is, is the top of the dice. But I don’t understand why the biggest dot value means it’s the top of the dice. I don’t fully understand what :dot does, despite trying to learn about it online.

I edited the source in (it took a minute to find because I originally found it 2 years ago). Also, the original source has comments explaining it:

local function GetTop()
-- world-space up direction to compare again
local upVec = Vector3.new(0, 1, 0)
-- vars to find maximum
local maxDotValue, maxDotNormalId
-- loop through all possible faces
for _, normalId in ipairs(Enum.NormalId:GetEnumItems()) do
-- get object-space direction for this face
local vec = Vector3.FromNormalId(normalId)
-- get world-space direction for this face
local diceVecInWorldSpace = workspace.Box.Dice.CFrame:VectorToWorldSpace(vec)
-- Dot gets acos(angle) between upVec and diceVecInWorldSpace
-- acos(angle) is a value between 1 (same direction) and -1 (opposite directions)
-- common values you need to know here:
-- 1: same direction entirely
-- bigger than 0: same side
-- 0: perpendicular directions
-- smaller than 0: opposite sides
-- -1: opposite direction entirely
local dotValue = upVec:Dot(diceVecInWorldSpace)
if not maxDotValue or dotValue > maxDotValue then
maxDotValue, maxDotNormalId = dotValue, normalId
end
end
return maxDotNormalId.Name
end

If you still need help understanding I can provide more assistance, but the comments were done nicely