Skip to content

Commit

Permalink
Fix spectator camera handling
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoco007 committed May 22, 2024
1 parent 273b304 commit 465bfbf
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 16 deletions.
68 changes: 52 additions & 16 deletions Source/CustomAvatar/Player/GameEnvironmentObjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

using System;
using System.Reflection;
using CustomAvatar.Configuration;
using CustomAvatar.Logging;
using CustomAvatar.Rendering;
Expand All @@ -28,6 +30,10 @@ internal class GameEnvironmentObjectManager : IInitializable
private const string kEnvironmentObjectPath = "/Environment";
private const string kSpectatorObjectPath = "/SpectatorParent";

internal static readonly Type kBeatLeaderCameraControllerType = Type.GetType("BeatLeader.Replayer.ReplayerCameraController, BeatLeader");
internal static readonly Type kBeatLeaderOriginComponentType = Type.GetType("BeatLeader.Replayer.ReplayerExtraObjectsProvider, BeatLeader");
internal static readonly FieldInfo kBeatLeaderCameraField = kBeatLeaderCameraControllerType?.GetField("_camera", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

private readonly DiContainer _container;
private readonly ILogger<GameEnvironmentObjectManager> _logger;
private readonly Settings _settings;
Expand Down Expand Up @@ -74,28 +80,58 @@ public void Initialize()
_logger.LogWarning($"{kEnvironmentObjectPath} not found!");
}

// ScoreSaber replay spectator camera
HandleScoreSaberSpectatorCamera();
HandleBeatLeaderSpectatorCamera();
}

private void HandleScoreSaberSpectatorCamera()
{
var spectatorParent = GameObject.Find(kSpectatorObjectPath);

if (spectatorParent != null)
if (spectatorParent == null)
{
// "SpectatorParent" has position room adjust applied but not rotation
var avatarParent = new GameObject("AvatarParent");
Transform avatarParentTransform = avatarParent.transform;
avatarParentTransform.localRotation = _beatSaberUtilities.roomRotation;
avatarParentTransform.SetParent(spectatorParent.transform, false);
return;
}

Camera spectatorCamera = spectatorParent.GetComponentInChildren<Camera>();
Camera spectatorCamera = spectatorParent.GetComponentInChildren<Camera>();

if (spectatorCamera != null)
{
_container.InstantiateComponent<CustomAvatarsMainCameraController>(spectatorCamera.gameObject);
}
else
{
_logger.LogWarning($"Spectator camera not found!");
}
if (spectatorCamera == null)
{
return;
}

Transform origin = new GameObject("Origin").transform;
Transform playerSpace = spectatorParent.transform;

// assuming roomCenter and roomRotation won't change while spectating
var inverseRotation = Quaternion.Inverse(_beatSaberUtilities.roomRotation);
origin.SetLocalPositionAndRotation(inverseRotation * -_beatSaberUtilities.roomCenter, inverseRotation);
origin.SetParent(playerSpace, false);

SpectatorCameraController spectatorCameraController = _container.InstantiateComponent<SpectatorCameraController>(spectatorCamera.gameObject);
spectatorCameraController.origin = origin;
spectatorCameraController.playerSpace = playerSpace;
}

private void HandleBeatLeaderSpectatorCamera()
{
if (kBeatLeaderCameraControllerType == null || kBeatLeaderOriginComponentType == null || kBeatLeaderCameraField == null)
{
return;
}

var controller = (Component)_container.TryResolve(kBeatLeaderCameraControllerType);
var originComponent = (Component)_container.TryResolve(kBeatLeaderOriginComponentType);

if (controller == null || originComponent == null)
{
return;
}

var camera = (Camera)kBeatLeaderCameraField.GetValue(controller);
SpectatorCameraController spectatorCameraController = _container.InstantiateComponent<SpectatorCameraController>(camera.gameObject);
spectatorCameraController.origin = originComponent.transform;
spectatorCameraController.playerSpace = spectatorCameraController.origin.Find("CenterAdjust");
}
}
}
130 changes: 130 additions & 0 deletions Source/CustomAvatar/Rendering/SpectatorCameraController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Beat Saber Custom Avatars - Custom player models for body presence in Beat Saber.
// Copyright © 2018-2024 Nicolas Gnyra and Beat Saber Custom Avatars Contributors
//
// This library is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CustomAvatar.Avatar;
using CustomAvatar.Configuration;
using CustomAvatar.Logging;
using CustomAvatar.Player;
using UnityEngine;
using Zenject;

namespace CustomAvatar.Rendering
{
[DisallowMultipleComponent]
internal class SpectatorCameraController : MonoBehaviour
{
internal Transform playerSpace;
internal Transform origin;

private ILogger<SpectatorCameraController> _logger;
private Settings _settings;
private ActivePlayerSpaceManager _activePlayerSpaceManager;
private ActiveOriginManager _activeOriginManager;
private ActiveCameraManager _activeCameraManager;

private Camera _camera;

private void Awake()
{
_camera = GetComponent<Camera>();
}

private void OnEnable()
{
AddToPlayerSpaceManager();
}

[Inject]
[SuppressMessage("CodeQuality", "IDE0051", Justification = "Used by Zenject")]
private void Construct(ILogger<SpectatorCameraController> logger, Settings settings, ActivePlayerSpaceManager activePlayerSpaceManager, ActiveOriginManager activeOriginManager, ActiveCameraManager activeCameraManager)
{
_logger = logger;
_settings = settings;
_activePlayerSpaceManager = activePlayerSpaceManager;
_activeOriginManager = activeOriginManager;
_activeCameraManager = activeCameraManager;
}

private void Start()
{
// prevent errors if this is instantiated via Object.Instantiate
if (_logger == null)
{
Destroy(this);
return;
}

_settings.cameraNearClipPlane.changed += OnCameraNearClipPlaneChanged;

UpdateCameraMask();

AddToPlayerSpaceManager();
}

private void OnDestroy()
{
if (_settings != null)
{
_settings.cameraNearClipPlane.changed -= OnCameraNearClipPlaneChanged;
}

RemoveFromPlayerSpaceManager();
}

private void OnCameraNearClipPlaneChanged(float value)
{
UpdateCameraMask();
}

// TODO: add a setting to show/hide player avatar in replays
private void UpdateCameraMask()
{
_logger.LogInformation($"Setting avatar culling mask and near clip plane on '{_camera.name}'");

int mask = _camera.cullingMask | AvatarLayers.kAlwaysVisibleMask;

// FPFC basically ends up being a 3rd person camera
if (Environment.GetCommandLineArgs().Contains("fpfc"))
{
mask |= AvatarLayers.kOnlyInThirdPersonMask;
}
else
{
mask &= ~AvatarLayers.kOnlyInThirdPersonMask;
}

_camera.cullingMask = mask;
_camera.nearClipPlane = _settings.cameraNearClipPlane;
}

private void AddToPlayerSpaceManager()
{
_activePlayerSpaceManager?.Add(playerSpace);
_activeOriginManager?.Add(origin);
_activeCameraManager?.Add(_camera);
}

private void RemoveFromPlayerSpaceManager()
{
_activePlayerSpaceManager?.Remove(playerSpace);
_activeOriginManager?.Remove(origin);
_activeCameraManager?.Remove(_camera);
}
}
}

0 comments on commit 465bfbf

Please sign in to comment.