By this logic, the middle NPC would be closest just because the other two lines are diagonal (the magnitude is from the centre of the wall to the players)

Ideally, I want each player’s distance to the wall to be counted in a straight line from their forward vector:

Can’t you just cast a ray in that direction, then subtract the starting position with the point where the ray intersects with the wall?

For example:

local RayResult = workspace:RayCast(StartPos, Direction, Params) -- Params should have whitelist and have the wall in the whitelist
if RayResult then
local Magnitude = (StartPos - RayResult.Position).Magnitude -- Gets the magnitude of the starting position and the position the ray intersected with the wall
end

Else, hmm, this is a hard problem. Ok, i will try to help u. Why not try to cast a ray like @VegetationBush said? But you are using the WorldSpace so that it will not be affected from the player rotation. First try with a wall:

--Variables
local Player = YourPlayer
local Character = Player.Character
local HRP:BasePart = Character.HumanoidRootPart
--While Player is unrotated, at spawn if u want to say it
local VectorToWall:Vector3 = (TargetWall.Position - MiddlePart.Position).Unit --A unit vector direction
--Now using @VegetationBush tip
local RayResult = workspace:RayCast(HRP.Position, (HRP.Position+VectorToWall)*300, Params) -- Params should have whitelist and have the wall in the whitelist
if RayResult then
local Magnitude = (RayResult.Position - HRP.Position).Magnitude -- Gets the Magnitude between Raycasted point (the wall) and the Player
end

If the player is in the other direction just use the dot product
(

)
Or, if you dont want, then make the same calculations but the other way and pick the lowest magnitude (to check if at what wall the Player is):

--Variables
local Player = YourPlayer
local Character = Player.Character
local HRP:BasePart = Character.HumanoidRootPart
--While Player is unrotated, at spawn if u want to say it
local VectorToWallA:Vector3 = (TargetWallA.Position - MiddlePart.Position).Unit --A unit vector direction
local VectorToWallB:Vector3 = (TargetWallB.Position - MiddlePart.Position).Unit
--Now using @VegetationBush tip
local MagnitudeA:number
local MagnitudeB:number
local MagnitudeToUse:number
local RayResultA = workspace:RayCast(HRP.Position, (HRP.Position+VectorToWallA)*300, Params) -- Params should have whitelist and have the walls in the whitelist
local RayResultB = workspace:RayCast(HRP.Position, (HRP.Position+VectorToWallB)*300, Params) -- Params should have whitelist and have the walls in the whitelist
if RayResultA then
MagnitudeA = (RayResultA.Position - HRP.Position).Magnitude -- Gets the Magnitude between Raycasted point (the wall) and the Player
end
if RayResultB then
MagnitudeB = (RayResultB.Position - HRP.Position).Magnitude
end
if MagnitudeB < MagnitudeA then --If Magnitude B is lower as MagnitudeA, use MagnitudeA
MagnitudeToUse = MagnitudeB
elseif MagnitudeA < MagnitudeB then --Else, Magnitude A ist the lowest
MagnitudeToUse = MagnitudeA
end

I don’t have experience with anything of this type, but couldn’t you simply create multiple different walls, and determine the distance from each wall? Therefore not having as much of a difference.

I’d Raycast from each player and get the magnitude to the intersection rather than to the wall’s position.

You know which direction to raycast in if the wall is the entire pitch width, players are restricted to the pitch, and you know which end you’re checking against.

local direction = ( WallPos - PitchCenterPos ) * Vector3.new( 1, 0, 1 )

The multiplier removes any vertical component that may appear as a result of differences in height between the pitch centre and the wall centre.

Make the Ray length around the length of the pitch, and use a collision group just for those walls to increase efficiency and avoid other things being picked up by the Ray. You’ll need a collision group that is set only to collide with itself and not with any other group incl. Default, I’ve assumed the name Walls.

You can then raycast from each HRP in the newly found direction - multiply the direction vector by at least 2 to ensure full pitch coverage.

local params = RaycastParams.new()
params.CollisionGroup = 'Walls'
local raycastResult = workspace:Raycast( HRP.Position, direction * 2, params )

The result can then be used to get the distance for each player, and then you’d simply go with the smallest.

local distanceToWall = ( raycastResult.Position - HRP.Position ).Magnitude

If this is just 1 wall you are using, then that makes it much easier. I’m assuming that’s the case and that the object is rotated exactly as a multiple of 90(0, 90, 180, 270, etc). If this is the case, then you can use the Pythagorean Theorem formula to calculate the magnitude:

We’re trying to calculate the blue side of the triangle, the pink dot is the position of the wall, the wall lines up with the x axis.

local function FindAngle(X, Z) -- Finds the angle of dotted line and orange line
return math.atan2(X, Z) * 180 / math.pi
end
local PlayerPosition = Vector2.new(math.abs(Player.Position.X), math.abs(Player.Position.Z))
local XLength = math.abs(Wall.Position.X) - math.abs(Player.Position.X) -- Finds the length of dotted lines
local ZDiagonalLength = (PlayerPosition - Vector2.new(math.abs(Wall.Position.X), math.abs(Wall.Position.Z))).Magnitude -- Finds the length of orange line
local Length = ZDiagonalLength * math.sin(FindAngle(XLength, ZDiagonalLength)) -- Finds the angle of the blue line
print(Length)

Edit: @Alvin_Blox, I made a mistake in my code, did an extra unnecessary step the broke the code, now it works.

If you are curious, here are the resources I used:

No need to raycast to solve this problem - you can treat this as a problem of finding the shortest distance between a point (player position) and a plane (the wall). This means you can just use vector projection / the dot product. I made a diagram which should hopefully help:

offset • normal means the dot product of offset and normal distance is what you are solving for: the distance from a player/point to the wall

I wouldn’t necessarily say that it’s giving you bad results, just that it’s giving you the euclidean distance from the centre points of each object.

For your case, since there’s only max 2 static walls orientated to world axis, you could take the worldspace position without orientation and check the delta along the axis of the pitch to the wall.i.e.

local a, b = 5, -15 -- wall.Position.x/z, player.Position.x/z
a, b = math.max(a, b), math.min(a, b)
local d = math.abs(a - b)
print(d) --> 20

The distance will (essentially) be the same regardless of player orientation since it’ll once again be calc’d from the centre point