You did mention the unfortunate thing about having “FindObjectOfType”, which is fine to have in certain cases (if used very sparcely and if you KNOW that it does exist).
I make my GameManager (Singleton) hold a Observer on Character (MonoBehaviour) and bind every ui component to it. In your case, it would be something like this in the end:
> public class PlayerUI : MonoBehaviour
> {
> [SerializeField] GunUI gunUI;
> private Character _character; // This is a weird example on when to use it.
>
> Awake() => GameManager.Instance.OnCharacterAssigned(CharacterAssigned);
> private void CharacterAssigned(Character character) => _character = character;
> public void RedrawUI() => gunUI.RedrawUI(_character.gun);
> }