Initial commit
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class GhostManagerV2 : MonoBehaviour
|
||||
{
|
||||
private static GhostManagerV2 instance;
|
||||
public static GhostManagerV2 getInstance() => instance;
|
||||
|
||||
[SerializeField] private UnityEvent onClearEvent;
|
||||
|
||||
private BlockEditContext _ctx;
|
||||
private bool _isDragged;
|
||||
private bool _isJustPlaced;
|
||||
|
||||
// managers
|
||||
private RayInteractor rayInteractor;
|
||||
private SnapSystem snappingSystem;
|
||||
private Environment environment;
|
||||
private CommandHandler commandHandler;
|
||||
private SocketIterator socketIterator;
|
||||
private BaseInputProvider inputProvider;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance == null) instance = this;
|
||||
else Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
rayInteractor = RayInteractor.getInstance();
|
||||
snappingSystem = SnapSystem.getInstance();
|
||||
environment = Environment.getInstance();
|
||||
commandHandler = CommandHandler.getInstance();
|
||||
socketIterator = SocketIterator.getInstance();
|
||||
inputProvider = InputProviderManager.getInstance()?.getCurrentInputProvider();
|
||||
|
||||
environment.onBlockAddedEvent.AddListener(placed => AfterCreation(placed.gameObject));
|
||||
|
||||
inputProvider.OnPlace.AddListener(OnPlace);
|
||||
inputProvider.OnDraggingEnd.AddListener(OnPlaceWithDragging);
|
||||
inputProvider.OnRotate.AddListener(OnRotate);
|
||||
inputProvider.OnCancel.AddListener(ClearGhostData);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_ctx == null || !_ctx.IsActive) return;
|
||||
|
||||
UpdateSurfaceTracking();
|
||||
|
||||
snappingSystem.UpdateSockets(_ctx.SurfacePoint, _ctx.GhostTransform);
|
||||
|
||||
SocketPoint targetSocket = snappingSystem.GetClosestSocket();
|
||||
SocketPoint mySocket = socketIterator.GetCurrentActiveSocket();
|
||||
bool compatible = targetSocket != null &&
|
||||
(mySocket == null || targetSocket.CanAccept(mySocket));
|
||||
|
||||
_ctx.SetFrameState(
|
||||
_ctx.SurfacePoint,
|
||||
_ctx.SurfaceNormal,
|
||||
mySocket,
|
||||
compatible ? targetSocket : null);
|
||||
|
||||
// Build movement context and run strategy
|
||||
var movCtx = BuildMovementContext();
|
||||
_ctx.CurrentStrategy?.UpdateMovement(ref movCtx);
|
||||
_ctx.SetRotationAxis(movCtx.RotationAxis);
|
||||
|
||||
// Validate and give feedback
|
||||
_ctx.IsValidPlacement = _ctx.Validator?.IsValid(_ctx) ?? true;
|
||||
if (_ctx.IsValidPlacement)
|
||||
_ctx.VisualFeedback?.OnValid(_ctx);
|
||||
else
|
||||
_ctx.VisualFeedback?.OnInvalid(_ctx);
|
||||
}
|
||||
|
||||
|
||||
public void setDrag(bool drag) => _isDragged = drag;
|
||||
|
||||
private void OnPlace()
|
||||
{
|
||||
if (InputModeManager.Is(InputMode.UI)) return;
|
||||
if (_ctx == null || !_ctx.IsActive || !_ctx.IsValidPlacement) return;
|
||||
if (_isJustPlaced) return;
|
||||
|
||||
snappingSystem.Clear();
|
||||
_ctx.PlacementHandler?.Commit(_ctx);
|
||||
|
||||
_isJustPlaced = true;
|
||||
StartCoroutine(ResetPlaced());
|
||||
ClearGhostDataInternal(false);
|
||||
}
|
||||
|
||||
private void OnPlaceWithDragging()
|
||||
{
|
||||
if (_isDragged) OnPlace();
|
||||
}
|
||||
|
||||
private void OnRotate()
|
||||
{
|
||||
_ctx.RotationHandler?.performRotation(_ctx);
|
||||
}
|
||||
|
||||
|
||||
public void SetupBlock(BlockData blockData = null)
|
||||
{
|
||||
if (blockData == null && _ctx?.GhostObject != null)
|
||||
{
|
||||
if (!InputModeManager.Has(InputMode.ObjectDragging))
|
||||
InputModeManager.Push(InputMode.ObjectDragging);
|
||||
_ctx.GhostObject.SetActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ctx?.GhostObject != null)
|
||||
Destroy(_ctx.GhostObject);
|
||||
|
||||
if (blockData == null) { _ctx = null; return; }
|
||||
|
||||
if (!InputModeManager.Has(InputMode.ObjectDragging))
|
||||
InputModeManager.Push(InputMode.ObjectDragging);
|
||||
|
||||
GameObject prefab = blockData.blockPrefab;
|
||||
GameObject ghost = Instantiate(prefab, rayInteractor.GetHitPosition(), prefab.transform.rotation);
|
||||
SetLayerRecursively(ghost, LayerMask.NameToLayer("Ignore Raycast"));
|
||||
|
||||
_ctx = BuildContext(ghost, blockData, false, null, Vector3.zero, Quaternion.identity);
|
||||
_ctx.PlacementHandler = new NewBlockPlacementHandler(commandHandler);
|
||||
_ctx.CurrentStrategy = new FreeMoveStrategy();
|
||||
}
|
||||
|
||||
public void SetupExistingBlock(GameObject sourceObject, IGhostMovementStrategy strategy = null)
|
||||
{
|
||||
if (sourceObject == null) return;
|
||||
|
||||
if (_ctx?.GhostObject != null && !_ctx.IsEditingExisting)
|
||||
Destroy(_ctx.GhostObject);
|
||||
|
||||
sourceObject.SetActive(false);
|
||||
GameObject ghost = sourceObject;
|
||||
|
||||
// Disconnect all sockets so it can freely snap to new locations while moving
|
||||
Block block = ghost.GetComponent<Block>();
|
||||
if (block != null)
|
||||
{
|
||||
SocketManagerChecker checker = FindObjectOfType<SocketManagerChecker>();
|
||||
if (checker != null) checker.DisconnectAllSockets(block);
|
||||
}
|
||||
|
||||
ghost.SetActive(true);
|
||||
SetLayerRecursively(ghost, LayerMask.NameToLayer("Ignore Raycast"));
|
||||
|
||||
_ctx = BuildContext(
|
||||
ghost,
|
||||
sourceObject.GetComponent<Block>()?.getBlockData(),
|
||||
true,
|
||||
sourceObject,
|
||||
sourceObject.transform.position,
|
||||
sourceObject.transform.rotation);
|
||||
|
||||
_ctx.PlacementHandler = new ExistingBlockPlacementHandler(commandHandler);
|
||||
_ctx.CurrentStrategy = strategy ?? new FreeMoveStrategy();
|
||||
|
||||
if (!InputModeManager.Has(InputMode.ObjectDragging))
|
||||
InputModeManager.Push(InputMode.ObjectDragging);
|
||||
}
|
||||
|
||||
public void SetMovementStrategy(IGhostMovementStrategy strategy)
|
||||
{
|
||||
if (_ctx != null) _ctx.CurrentStrategy = strategy ?? new FreeMoveStrategy();
|
||||
}
|
||||
|
||||
public bool HasActiveGhost() => _ctx != null && _ctx.IsActive;
|
||||
public bool isGhost(GameObject obj) => _ctx?.GhostObject == obj;
|
||||
public Transform GhostTransform => _ctx?.GhostTransform;
|
||||
|
||||
public void ClearGhost()
|
||||
{
|
||||
if (_ctx?.GhostObject != null) _ctx.GhostObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void ClearGhostData()
|
||||
{
|
||||
if (InputModeManager.Is(InputMode.UI)) return;
|
||||
ClearGhostDataInternal(true);
|
||||
}
|
||||
|
||||
private void ClearGhostDataInternal(bool isCancel)
|
||||
{
|
||||
if (InputModeManager.Has(InputMode.ObjectDragging))
|
||||
InputModeManager.Pop(InputMode.ObjectDragging);
|
||||
|
||||
if (_ctx != null)
|
||||
{
|
||||
if (isCancel && _ctx.IsEditingExisting)
|
||||
_ctx.PlacementHandler?.Cancel(_ctx);
|
||||
else if (!_ctx.IsEditingExisting && _ctx.GhostObject != null)
|
||||
Destroy(_ctx.GhostObject);
|
||||
}
|
||||
|
||||
_ctx = null;
|
||||
|
||||
snappingSystem?.Clear();
|
||||
socketIterator?.ClearActiveContainer();
|
||||
onClearEvent.Invoke();
|
||||
}
|
||||
|
||||
public void onClearAddListener(UnityAction action) => onClearEvent.AddListener(action);
|
||||
public void setupBlock(BlockData blockData = null) => SetupBlock(blockData);
|
||||
public void clearGhost() => ClearGhost();
|
||||
public void clearGhostData() => ClearGhostData();
|
||||
|
||||
/// <summary>
|
||||
/// Commits the current ghost unconditionally (no validity check).
|
||||
/// Mirrors GhostManager.ForcePlace() for editor/test tooling.
|
||||
/// </summary>
|
||||
public void ForceCommit()
|
||||
{
|
||||
if (_ctx == null || !_ctx.IsActive) return;
|
||||
snappingSystem.Clear();
|
||||
_ctx.PlacementHandler?.Commit(_ctx);
|
||||
ClearGhostDataInternal(false);
|
||||
}
|
||||
|
||||
public void TryCommitOrCancel()
|
||||
{
|
||||
if (_ctx == null || !_ctx.IsActive) return;
|
||||
|
||||
if (_ctx.IsValidPlacement)
|
||||
{
|
||||
snappingSystem.Clear();
|
||||
_ctx.PlacementHandler?.Commit(_ctx);
|
||||
ClearGhostDataInternal(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearGhostDataInternal(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BlockEditContext BuildContext(
|
||||
GameObject ghost, BlockData blockData,
|
||||
bool isExisting, GameObject source,
|
||||
Vector3 originalPos, Quaternion originalRot)
|
||||
{
|
||||
var ctx = new BlockEditContext
|
||||
{
|
||||
GhostObject = ghost,
|
||||
BlockData = blockData,
|
||||
IsEditingExisting = isExisting,
|
||||
SourceObject = source,
|
||||
OriginalPosition = originalPos,
|
||||
OriginalRotation = originalRot,
|
||||
Validator = new DefaultBlockValidator(),
|
||||
VisualFeedback = new DefaultVisualFeedback(),
|
||||
SnapBehavior = new DefaultSnapBehavior(),
|
||||
RotationHandler = new DefaultRotationHandler()
|
||||
};
|
||||
|
||||
GrabInteractable grab = ghost.GetComponent<GrabInteractable>();
|
||||
if (grab != null)
|
||||
{
|
||||
ctx.AlignToSurfaceNormal = grab.alignToSurfaceNormal;
|
||||
ctx.SnapRotation = grab.snapRotation;
|
||||
ctx.FollowSpeed = grab.followSpeed;
|
||||
ctx.HeightOffset = grab.heightOffset;
|
||||
ctx.ValidTint = grab.validTint;
|
||||
ctx.InvalidTint = grab.invalidTint;
|
||||
ctx.AttachPoint = grab.GetAttachPoint();
|
||||
if (!isExisting) Destroy(grab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.AttachPoint = new GameObject("attachPoint").transform;
|
||||
ctx.AttachPoint.SetParent(ghost.transform);
|
||||
}
|
||||
|
||||
ctx.Block = ghost.GetComponent<Block>();
|
||||
ctx.JointBlock = ghost.GetComponent<JointBlock>();
|
||||
ctx.Container = ghost.GetComponent<SocketContainer>();
|
||||
|
||||
if (ctx.Container != null && ctx.Container.GetSocketCount() > 0)
|
||||
socketIterator.SetActiveContainer(ctx.Container);
|
||||
else
|
||||
socketIterator.ClearActiveContainer();
|
||||
|
||||
ctx.GhostRenderers = ghost.GetComponentsInChildren<Renderer>().Where(x => x.gameObject.layer != LayerMask.NameToLayer("Gizmos")).ToArray();
|
||||
foreach (var r in ctx.GhostRenderers)
|
||||
foreach (var mat in r.materials)
|
||||
{
|
||||
if (mat.HasProperty("_Color"))
|
||||
{
|
||||
mat.color = new Color(mat.color.r, mat.color.g, mat.color.b, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private GhostMovementContext BuildMovementContext() => new GhostMovementContext(
|
||||
ghostTransform: _ctx.GhostTransform,
|
||||
surfacePoint: _ctx.SurfacePoint,
|
||||
surfaceNormal: _ctx.SurfaceNormal,
|
||||
mySocket: _ctx.MySocket,
|
||||
targetSocket: _ctx.TargetSocket,
|
||||
attachPoint: _ctx.AttachPoint,
|
||||
followSpeed: _ctx.FollowSpeed,
|
||||
heightOffset: _ctx.HeightOffset,
|
||||
alignToSurfaceNormal: _ctx.AlignToSurfaceNormal,
|
||||
snapRotation: _ctx.SnapRotation);
|
||||
|
||||
private void UpdateSurfaceTracking()
|
||||
{
|
||||
if (rayInteractor.GetHitInfo(out RaycastHit hitInfo))
|
||||
_ctx.SetFrameState(hitInfo.point, hitInfo.normal.normalized, _ctx.MySocket, _ctx.TargetSocket);
|
||||
else
|
||||
_ctx.SetFrameState(_ctx.SurfacePoint, Vector3.up, _ctx.MySocket, _ctx.TargetSocket);
|
||||
}
|
||||
|
||||
private IEnumerator ResetPlaced()
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
_isJustPlaced = false;
|
||||
}
|
||||
|
||||
private void AfterCreation(GameObject blockObj)
|
||||
{
|
||||
if (blockObj == null) return;
|
||||
if (blockObj.GetComponent<BaseInteractable>() == null && blockObj.layer != LayerMask.NameToLayer("Ignore Interaction"))
|
||||
blockObj.AddComponent<BaseInteractable>();
|
||||
SetLayerRecursively(blockObj, LayerMask.NameToLayer("Interactable"));
|
||||
}
|
||||
|
||||
private void SetLayerRecursively(GameObject obj, int layer, Func<Transform, bool> Method)
|
||||
{
|
||||
if (Method(obj.transform))
|
||||
obj.layer = layer;
|
||||
foreach (Transform child in obj.transform)
|
||||
SetLayerRecursively(child.gameObject, layer, Method);
|
||||
}
|
||||
|
||||
private void SetLayerRecursively(GameObject obj, int layer)
|
||||
{
|
||||
SetLayerRecursively(obj, layer, x =>
|
||||
{
|
||||
return x.gameObject.layer != LayerMask.NameToLayer("Gizmos");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user