Attach Camera to Player

This commit is contained in:
pelpanagiotis
2026-02-09 21:58:30 +02:00
parent 9bcb47d8f6
commit aa248cda7e
10 changed files with 222 additions and 108 deletions

View File

@@ -20,6 +20,16 @@ public class CharacterMovement : NetworkBehaviour
bool lastSentWalking;
bool lastSentRunning;
// assign this in the player prefab to an empty child transform positioned where the camera should sit (e.g. head)
public Transform cameraMount;
// camera follow settings
Camera mainCamera;
Vector3 cameraLocalOffsetStable = new Vector3(0f, 2f, -4f); // fallback offset (local)
public float followSpeed = 5f;
public float idleFollowSpeed = 0.5f; // slower smoothing while idle (keeps camera stable)
public Vector3 cameraLookOffset = new Vector3(0f, 1.5f, 0f); // where camera looks relative to player root
void Awake()
{
input = new PlayerInput();
@@ -56,6 +66,32 @@ public class CharacterMovement : NetworkBehaviour
HandleRotation();
}
// Camera follow should be in LateUpdate so it follows the final character pose for the frame
void LateUpdate()
{
if (!isLocalPlayer) return;
if (mainCamera == null) return;
// Desired world position based on the stable local offset (relative to player root)
Vector3 desiredWorld = transform.TransformPoint(cameraLocalOffsetStable);
if (movementPressed)
{
// while moving: follow the player (smooth)
mainCamera.transform.position = Vector3.Lerp(mainCamera.transform.position, desiredWorld, Time.deltaTime * followSpeed);
}
else
{
// while idle: keep camera stable behind the player (don't follow micro animation bobbing)
// Update only XZ to remain behind, preserve camera's current Y (height) to avoid vertical bob
Vector3 idleTarget = new Vector3(desiredWorld.x, mainCamera.transform.position.y, desiredWorld.z);
mainCamera.transform.position = Vector3.Lerp(mainCamera.transform.position, idleTarget, Time.deltaTime * idleFollowSpeed);
}
// Always look at the player (with an offset so we look near the torso/head)
mainCamera.transform.LookAt(transform.position + cameraLookOffset);
}
void HandleRotation()
{
Vector3 currentPosition = transform.position;
@@ -142,11 +178,46 @@ public class CharacterMovement : NetworkBehaviour
{
Debug.Log("Local player started: " + netId);
input.CharacterControls.Enable();
// Setup camera for local player (do not parent to animated mount to avoid animation bobbing)
mainCamera = Camera.main;
if (mainCamera == null)
{
Debug.LogWarning("No Camera.main found in scene to attach to player.");
return;
}
// compute a stable local offset based on cameraMount, but use player root as the reference so animated child motion is ignored
if (cameraMount != null)
{
cameraLocalOffsetStable = transform.InverseTransformPoint(cameraMount.position);
}
else
{
// fallback to a sensible offset if no mount assigned
cameraLocalOffsetStable = new Vector3(0f, 2f, -4f);
Debug.LogWarning("cameraMount not assigned on player prefab. Using fallback offset.");
}
// unparent camera so we control world position directly
mainCamera.transform.SetParent(null, true);
// ensure only the local player's camera has an active AudioListener
var audio = mainCamera.GetComponent<AudioListener>();
if (audio != null) audio.enabled = true;
}
public override void OnStopLocalPlayer()
{
input.CharacterControls.Disable();
// optionally disable audio listener to avoid duplicates
if (mainCamera != null)
{
var audio = mainCamera.GetComponent<AudioListener>();
if (audio != null) audio.enabled = false;
}
mainCamera = null;
}
// Called on the server when client issues the command

View File

@@ -124,7 +124,7 @@ public class Player : NetworkBehaviour
if (nameTagPrefab != null)
{
nameTagInstance = Instantiate(nameTagPrefab, transform);
nameTagInstance.transform.localPosition = new Vector3(0f, nameTagHeight, 0f);
nameTagInstance.transform.localPosition = new Vector3(0f, 2.0f, 0f);
nameTagInstance.transform.localRotation = Quaternion.identity;
// try to find either a TextMesh or legacy TextMesh in the prefab
nameTagTextMesh = nameTagInstance.GetComponentInChildren<TextMesh>();
@@ -134,7 +134,7 @@ public class Player : NetworkBehaviour
// create a simple 3D text (TextMesh) so no extra UI package is required
nameTagInstance = new GameObject("NameTag");
nameTagInstance.transform.SetParent(transform, false);
nameTagInstance.transform.localPosition = new Vector3(0f, nameTagHeight, 0f);
nameTagInstance.transform.localPosition = new Vector3(0f, 2.0f, 0f);
nameTagTextMesh = nameTagInstance.AddComponent<TextMesh>();
nameTagTextMesh.alignment = TextAlignment.Center;

View File

@@ -34,7 +34,9 @@ namespace QuickStart
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = new Vector3(0, 0, 0);
floatingInfo.transform.localPosition = new Vector3(0, -0.3f, 0.6f);
// Move the floatingInfo (player name) above the player instead of below.
// Adjust Y to fit your model height (2.0f is a good starting point for standard humanoid scale).
floatingInfo.transform.localPosition = new Vector3(0, 2.0f, 0.6f);
floatingInfo.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
string name = "Player" + Random.Range(100, 999);

View File

@@ -1,5 +1,36 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &5291219971806440334
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4270643899711320435}
m_Layer: 0
m_Name: Camera Mount
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4270643899711320435
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5291219971806440334}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0.294, y: 1.145, z: -2.005}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8077244700045459333}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &8616118632631556206
PrefabInstance:
m_ObjectHideFlags: 0
@@ -62,7 +93,10 @@ PrefabInstance:
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: -8679921383154817045, guid: e0ac844e563d55c4a8ff510e5409eeae, type: 3}
insertIndex: -1
addedObject: {fileID: 4270643899711320435}
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: e0ac844e563d55c4a8ff510e5409eeae, type: 3}
insertIndex: -1
@@ -105,6 +139,7 @@ MonoBehaviour:
syncDirection: 0
syncMode: 0
syncInterval: 0
cameraMount: {fileID: 4270643899711320435}
--- !u!65 &2994849115125833078
BoxCollider:
m_ObjectHideFlags: 0
@@ -193,3 +228,5 @@ MonoBehaviour:
syncMode: 0
syncInterval: 0
playerName:
nameTagPrefab: {fileID: 0}
nameTagHeight: 0.2