Unity 3D radar help needed

I am trying to create a radar system in Unity similar to the one in Elite Dangerous, with some icon denoting the object’s position and a line to denote elevation. The current code does not compute the position of the icon sprite and the line endpoints properly. The icon sprite’s position seems to barely change, only the scaling, and the base point of the line, which is meant to be level with the player, is instead set to the position of the position of the radar main object. All radar pings are parented to a radar window Image object.

public void ProcessTarget(IObject target)
{
    var distance = Vector3.Distance(transform.position, target.Position);
    if (distance > radarRange) return;
    var (image, line) = radarPings[target];
    //image is a UI image, line is a line renderer
    image.transform.localScale = GlobalConfig.RadarPingIconSizeCoefficient * 
        Mathf.Clamp01(1 - (distance / radarRange)) * Vector3.one;//not sure if I should keep this

    image.transform.parent.localPosition = Vector3.Lerp(Vector3.zero, 
        (target.Position - transform.position) / distance * radarDisplayRadius, 
        Mathf.Clamp01(distance / radarRange));//this is meant to compute the icon position on the radar window


    //the base position should be level with the player, but with the (local) x and z coordinates of the icon, to draw the line
    var basePosition = new Vector3(image.transform.parent.localPosition.x, 0, 
        image.transform.parent.localPosition.z);
    line.SetPosition(0, image.transform.parent.TransformPoint(basePosition));
    line.SetPosition(1, image.transform.parent.position);
}

I did one of these for my Jetpack Kurt game.

Not to trivialize it, but i’m sure you just have a computation bug to track down.

The key for me while debugging was to emplace actual static enemies at known distances and elevations above / below my Y plane so that I could iterate and find my bugs about computing height above disc, distance to each ring, etc.

Mine was simpler because Y+ was always up. If you are truly able to rotate in any orientation in space, that’s just one additional inverse transform point before you consider the offsets from the player.

I also cheated and had a separate camera orbiting the disc and all the dots and lines above / lines below, which let me ignore rotation around the Y, but that too is just one extra rotation multiply before you start processing the point offsets.

All that said, this is the main core projection loop is this snippet, with a few comments:

		Vector3 playerPos = Game1.pt1c.transform.position;

		// drive camera orientation
		CameraSwivel.rotation = Quaternion.Euler( 0,
			Game1.pt1c.transform.eulerAngles.y, 0);
			
		// drive updating all the blips
		foreach( RadarBlipDefinition rbd in rbds)
		{
			// target has been destroyed or captured
			if (!rbd.Source)
			{
				rbd.Blip.gameObject.SetActive (false);
				rbd.Dot.gameObject.SetActive (false);
				rbd.LR.enabled = false;
				continue;
			}

			// find the offset from player
			Vector3 offset = rbd.Source.position - playerPos;

			// on the disc plane or too far away?
			Vector3 XZOffset = offset;
			XZOffset.y = 0;
			if (XZOffset.sqrMagnitude > NominalMaxRange * NominalMaxRange)
			{
				rbd.Blip.gameObject.SetActive (false);
				rbd.Dot.gameObject.SetActive (false);
				rbd.LR.enabled = false;
				continue;
			}

			rbd.Blip.gameObject.SetActive (true);
			rbd.Dot.gameObject.SetActive (true);
			rbd.LR.enabled = true;

			// square root used to make near y==0 more visibly changed
			offset.y = Mathf.Sign( offset.y) *
				Mathf.Sqrt ( Mathf.Abs( offset.y));

			offset.y *= VerticalExaggeration;

			offset *= RangeMultiplier;

			offset += Center.position;

			// this is the pip actually on the plane (X)
			rbd.Blip.position = offset;

			// this is the riser line
			rbd.LR.SetPosition (0, offset);
			offset.y = transform.position.y;
			rbd.LR.SetPosition (1, offset);

			// this is the dot at the end of the riser line
			rbd.Dot.position = offset;
		}