Rather than trying to explain this complicated issue, I decided to shoot a video explaining what it is about.
How do people usually solve this kind of issue? Any clever ideas?
Rather than trying to explain this complicated issue, I decided to shoot a video explaining what it is about.
How do people usually solve this kind of issue? Any clever ideas?
Temporarily turn OFF the look at camera angling script.
Now go back and forth and pause and look in 3D. Is the plane of the canvas precisely at the vertical pole center of the character? It looks offset to me…
When the cam-look script is on, does the canvas pivot at its geometric center? Maybe that is making a pendulum swing-forward/aftward effect? Again, toggle it on/off to get more intel while looking at it from the side in the scene.
Yeah unfortunately I did all of those and it didn’t fix the issue. I think fundamentally the canvas has to be translated with respect to the perspective the camera has. Basically the canvas cannot sit on top of the character in a fixed position and have perspective adjusted positioning above the character at same time.
I fixed the issue by setting the canvas pivot to (0.5, 0) which is bottom middle. And this fixed the issue. I still don’t understand why… but it fixed it.
Does this HUD need to affect or be affected by the lighting in the scene? If not then I would have it all inside one overlay canvas. I try to avoid using 3d/world space canvas in cases where I’m not even reaping their visual benefits, makes the UI cheaper to render in that respect and definitely easier to implement.
for an Overlay HudSystem, the characters then need to send their “focal point” (where you expect players would want to focus on when they want to look at the model itself) to the hud system as worldspace. the hud system will then simply convert that worldspace focal point to screenspace and position the relevant Hud Item’s pivot to that screen space focal point. the HudItems would then move their visual elements away from their pivot so that they don’t obscure their character.
No need for an update rotation script, no need to worry about scaling issues for very far or very close Huds, no need to worry about FOV skews, no need to worry about characters/environment clipping with the HUD, and you can tweak on a per model(focal) and per HUD(pivot) offsets so that they have consistent visibility no matter where they are in the scene or which character they represent. You even have the option of keeping the Hud onscreen even if you scroll the character off-screen by simply clamping the screen position within a range
I actually thought about this approach. But wasn’t sure if it even made sense. So I opted for a simpler solution.
My “focal point” of the characters would be the top of the box containing the character model. This point won’t move even if the head of the model moves due to animations. It’s just at the top of the surrounding box.
In terms of translating the world space to the HUD canvas space, how do you do that? Is it simply the vector that represents the shortest distance between this focal point to the HUD canvas plane?
There’s a couple or ways to do it, RectTransformUtility is probably the most straight forward and simplest approach.
Vector3 focalPoint;
Canvas myCanvas;
Vector3 pivotPoint = RectTransformUtility.WorldToScreenPoint(myCanvas.worldCamera,focalPoint);
Another is manually getting the Camera’s World2Screen Matrix conversions and doing matrix maths on the transforms: It recommends an understanding of Matrix math and thus more complicated, BUT is more efficient computationally (because you can compile a series of complex matrix transforms into one simple matrix, then reuse that martix for all elements, plus it doesn’t require an actual camera) and so scales better if your Hud could have a tons and tons of elements. Its great when you want to represent a ton of UI elements from a unique perspective
(example: placing icons on a minimap using an imaginary top-down camera and Matrix calculations)
//Imagine a camera that can't move and sits at world origin facing Positive-Z.
// To make it seem like Camera is looking from above; instead of moving the imaginary camera,
// we generate a matrix that would manipulate the camera's perception of the world to be as if its looking top-down.
// thus world elements are to be Transformed, rotated 90 degrees on x-axis through origin
private static readonly Matrix4x4 worldToLocal = Matrix4x4.TRS(Vector3.zero,Quaternion.LookRotation(Vector3.up,Vector3.back),Vector3.one);
// Creates the type of View frustum used by the "camera". for this minimap example its an Orthographic camera
private Matrix4x4 localToView = Matrix4x4.Ortho(-width,width,-height,height,-depth,depth);
// when given an entity's Hud and the Transform the Hud would represent this moves the hud so that its position and rotation within its parent RectTransform is relative to the "cameras" perception of the entity in the world
private void SetIconTransform(RectTransform rectTransform,IMappableEntity entity)
{
//worldToLocal is a "transform" Matrix4x4, so it handles translation, rotation, and scale
//localToView is a "frustum" Matrix4x4, which doesn't have a concept of world space, rotations, etc.
// frustum matrices basically assume that they are always at world origin and looking straight north (positive Z)
// so for the frustum to "see" the entities we have to convert their world space to a local space relative to the frustum
var localPosition = worldToLocal.MultiplyPoint3x4(entity.GetWorldPosition); //converts world space positions to local space relative of where we want to percieve the frustum to be.
var orthoPosition = localToView.MultiplyPoint(localPosition); //converts this new local space position to "projection space" position where the origin {0,0} is the center of the screen
//projection space is normalized with {-1,-1} being bottom left corner and {1,1} being top right.
rectTransform.anchorMin = rectTransform.anchorMax = new Vector3(Mathf.Clamp01(orthoPosition.x + 0.5f),Mathf.Clamp01(orthoPosition.y + 0.5f),0); //setting Anchors and not AnchoredPosition cause Anchors is already normalized
rectTransform.anchoredPosition = Vector2.zero; //ensure that the rect transtrom is still snapped to its anchor
rectTransform.rotation = Quaternion.LookRotation(Vector3.forward,worldToLocal.MultiplyVector(entity.GetWorldRotation * Vector3.forward));
}
in this simplified example worldToLocal was a “static” matrix as the camera doesn’t move, but if you did move it the transform can be calculated on that one matrix first and then can be cheaply shared to all the Hud elements