Files
Test-Multiplayer/Assets/Scripts/PlayerScript.cs
T
pelpanagiotisandClaude Opus 5.5 5fc9ebf1e6 Add practice range: shooting, AK47, intro cinematic, dressed warehouse scene
- PortalTestDestination practice range: Valorant-style hitscan rifle (recoil,
  spray/bloom, reload, controller support), debug HUD, practice-dummy targets
  with head/body hitboxes, hit/death/respawn animations, procedural SFX/FX
- AK47 model (Quaternius, CC0) held via Animator IK, with fire/reload/equip anims
- Intro cinematic (camera shots, title card, target beam-in, GO hand-off)
- Scene dressing editor pass: CC0 textures, props, warehouse roof, lighting and
  post-processing; custom stylized skybox shader
- CharacterMovement: camera recoil, shoulder offset, cinematic control lock
- Unity CLI pipeline package; plus in-progress chat/Rive/player changes

Asset credits: Assets/Models/Props/PROPS_CREDITS.txt (3 CC-BY 3.0 props),
Assets/Textures/Range/TEXTURES_LICENSE.txt, Assets/Resources/*/..._LICENSE.txt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:56:35 +03:00

229 lines
9.2 KiB
C#

using Mirror;
using UnityEngine;
namespace QuickStart
{
public class PlayerScript : NetworkBehaviour
{
public TextMesh playerNameText;
public GameObject floatingInfo;
[Header("Camera")]
public float cameraDistance = 4.5f;
public float cameraPivotHeight = 1.6f;
public float cameraPitchDefault = 15f;
public float cameraPitchMin = -10f;
public float cameraPitchMax = 65f;
public float mouseSensitivity = 2.5f;
private float cameraYaw;
private float cameraPitch;
private bool wantsCursorLocked = true;
private Material playerMaterialClone;
[SyncVar(hook = nameof(OnNameChanged))]
public string playerName;
[SyncVar(hook = nameof(OnColorChanged))]
public Color playerColor = Color.white;
void OnNameChanged(string _Old, string _New)
{
playerNameText.text = playerName;
}
void OnColorChanged(Color _Old, Color _New)
{
playerNameText.color = _New;
playerMaterialClone = new Material(GetComponent<Renderer>().material);
playerMaterialClone.color = _New;
GetComponent<Renderer>().material = playerMaterialClone;
}
public override void OnStartLocalPlayer()
{
// Camera is NOT parented to the player anymore: it follows the player's position
// every frame (see LateUpdate/UpdateCameraTransform) but its rotation is driven
// independently by the mouse, so turning the player body no longer snaps the
// camera around with it.
Camera.main.transform.SetParent(null, true);
cameraYaw = transform.eulerAngles.y;
cameraPitch = cameraPitchDefault;
UpdateCameraTransform();
wantsCursorLocked = true;
ApplyCursorLockState();
// 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);
Color color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));
CmdSetupPlayer(name, color);
}
[Command]
public void CmdSetupPlayer(string _name, Color _col)
{
// player info sent to server, then server updates sync vars which handles it on all clients
playerName = _name;
playerColor = _col;
}
/*
PSEUDOCODE / PLAN (detailed):
1. If this is not the local player:
- Keep existing behavior: make the floatingInfo look at the main camera every frame.
- Return early so non-local players do not process input or move.
2. If this is the local player:
- Read keyboard input from the Unity Input System (preferred):
- Use UnityEngine.InputSystem.Keyboard.current to check key states.
- Map WASD (and arrow keys as optional) to movement:
- W or UpArrow -> move forward (+z)
- S or DownArrow -> move backward (-z)
- A or LeftArrow -> rotate left (-y)
- D or RightArrow -> rotate right (+y)
- Movement magnitudes:
- Rotation speed: 110 degrees per second (same as original)
- Forward/back speed: 4 units per second (same as original)
- Multiply by Time.deltaTime for frame-rate independence.
- If the Input System is not available (Keyboard.current == null), fall back to the legacy Input.GetAxis calls to preserve behavior on setups that haven't enabled the new system.
- Apply transform.Rotate for yaw (y-axis) using computed rotation value.
- Apply transform.Translate for forward/back movement in local Z using computed move value.
3. Keep the method minimal and efficient:
- Avoid allocations in Update.
- Use direct boolean checks (isPressed) on the keyboard.
- Preserve original movement multipliers and semantics.
*/
void Update()
{
Debug.Log("Update");
if (!isLocalPlayer)
{
// make non-local players run this
if (Camera.main != null)
floatingInfo.transform.LookAt(Camera.main.transform);
return;
}
// If the chat input is focused, skip movement input to avoid moving while typing.
// Still keep the floating info facing the camera for readability.
if (ChatUI.IsInputFieldFocused)
{
if (Camera.main != null)
floatingInfo.transform.LookAt(Camera.main.transform);
return;
}
float rotationDelta = 0f;
float forwardDelta = 0f;
var kb = UnityEngine.InputSystem.Keyboard.current;
if (kb != null)
{
// Rotation: A/D or Left/Right arrows
if (kb.aKey.isPressed || kb.leftArrowKey.isPressed) rotationDelta = -1f;
if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) rotationDelta = 1f;
// Forward/Backward: W/S or Up/Down arrows
if (kb.wKey.isPressed || kb.upArrowKey.isPressed) forwardDelta = 1f;
if (kb.sKey.isPressed || kb.downArrowKey.isPressed) forwardDelta = -1f;
rotationDelta *= Time.deltaTime * 110.0f; // degrees per second
forwardDelta *= Time.deltaTime * 4.0f; // units per second
}
else
{
// Fallback to legacy input if the new Input System isn't available
rotationDelta = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
forwardDelta = Input.GetAxis("Vertical") * Time.deltaTime * 4.0f;
}
transform.Rotate(0f, rotationDelta, 0f, Space.Self);
transform.Translate(0f, 0f, forwardDelta, Space.Self);
}
// Camera follow/look runs in LateUpdate so it reacts to the final position for the
// frame and isn't a frame behind the player's own Update movement.
void LateUpdate()
{
if (!isLocalPlayer) return;
var kb = UnityEngine.InputSystem.Keyboard.current;
if (kb != null && kb.escapeKey.wasPressedThisFrame)
{
if (ChatUI.IsInputFieldFocused)
{
ChatUI.Instance?.ExitChatFocus();
wantsCursorLocked = true;
}
else
{
wantsCursorLocked = !wantsCursorLocked;
}
}
// Re-assert the lock state every frame rather than trusting Cursor.lockState to
// have stuck: the Editor Game view (and some platforms) silently drop the lock on
// focus loss, and gating mouse-look on reading it back meant one dropped lock
// permanently stopped the camera from responding to the mouse.
bool shouldLockNow = wantsCursorLocked && !ChatUI.IsInputFieldFocused;
ApplyCursorLockState(shouldLockNow);
if (shouldLockNow && Application.isFocused)
{
float mouseX;
float mouseY;
var mouse = UnityEngine.InputSystem.Mouse.current;
if (mouse != null)
{
Vector2 delta = mouse.delta.ReadValue();
mouseX = delta.x * mouseSensitivity * Time.deltaTime;
mouseY = delta.y * mouseSensitivity * Time.deltaTime;
}
else
{
// Fallback to legacy input if the new Input System isn't available
mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
}
cameraYaw += mouseX;
cameraPitch = Mathf.Clamp(cameraPitch - mouseY, cameraPitchMin, cameraPitchMax);
}
UpdateCameraTransform();
}
void ApplyCursorLockState() => ApplyCursorLockState(wantsCursorLocked);
void ApplyCursorLockState(bool locked)
{
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !locked;
}
// Orbits the camera around a pivot above the player at a fixed distance, using
// cameraYaw/cameraPitch — never the player's own transform.rotation.
void UpdateCameraTransform()
{
var cam = Camera.main;
if (cam == null) return;
Quaternion orbitRotation = Quaternion.Euler(cameraPitch, cameraYaw, 0f);
Vector3 pivot = transform.position + Vector3.up * cameraPivotHeight;
cam.transform.SetPositionAndRotation(
pivot + orbitRotation * new Vector3(0f, 0f, -cameraDistance),
orbitRotation);
}
}
}