Initial commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// fallback to collision validator
|
||||
/// </summary>
|
||||
public class DefaultBlockValidator : IBlockValidator
|
||||
{
|
||||
public bool IsValid(BlockEditContext ctx)
|
||||
{
|
||||
IValidator v = ctx.GhostObject.GetComponent<IValidator>();
|
||||
return v != null ? v.IsValidPlacement(ctx.GhostObject) : true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dd246c8854b6164584d9a3f53ce4e45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class DefaultRotationHandler : IRotateHandler
|
||||
{
|
||||
private List<float> anglesToRotate = new List<float>();
|
||||
private List<float> addedAngles = new List<float>();
|
||||
private int currentAngleIndex = 0;
|
||||
|
||||
public DefaultRotationHandler()
|
||||
{
|
||||
// add regular angles
|
||||
anglesToRotate.Add(0);
|
||||
anglesToRotate.Add(45);
|
||||
anglesToRotate.Add(90);
|
||||
anglesToRotate.Add(135);
|
||||
anglesToRotate.Add(180);
|
||||
anglesToRotate.Add(225);
|
||||
anglesToRotate.Add(270);
|
||||
anglesToRotate.Add(315);
|
||||
}
|
||||
public void performRotation(BlockEditContext ctx)
|
||||
{
|
||||
if (ctx != null && ctx.IsActive)
|
||||
{
|
||||
float previousAngle = anglesToRotate[currentAngleIndex];
|
||||
|
||||
currentAngleIndex++;
|
||||
currentAngleIndex %= anglesToRotate.Count;
|
||||
|
||||
float currentAngle = anglesToRotate[currentAngleIndex];
|
||||
float deltaAngle = currentAngle - previousAngle;
|
||||
|
||||
Vector3 pivotPoint = ctx.GhostTransform.position;
|
||||
if (ctx.TargetSocket != null && ctx.MySocket != null)
|
||||
{
|
||||
pivotPoint = ctx.MySocket.transform.position;
|
||||
}
|
||||
else if (ctx.AttachPoint != null)
|
||||
{
|
||||
pivotPoint = ctx.AttachPoint.position;
|
||||
}
|
||||
|
||||
applyRotation(ctx.GhostTransform, pivotPoint, ctx.RotationAxis, deltaAngle);
|
||||
}
|
||||
}
|
||||
public void addAngle(float angle)
|
||||
{
|
||||
if (addedAngles.Contains(angle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// normalize the angle (0 - 360)
|
||||
if (angle >= 0)
|
||||
{
|
||||
angle = angle % 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (angle < 0)
|
||||
{
|
||||
angle += 360;
|
||||
}
|
||||
}
|
||||
int currentSize = anglesToRotate.Count;
|
||||
// add it to list
|
||||
for (int i = 0; i < anglesToRotate.Count; i++)
|
||||
{
|
||||
if (angle < anglesToRotate[i])
|
||||
{
|
||||
anglesToRotate.Insert(i, angle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (currentSize == anglesToRotate.Count) // it's the largest element in the whole rotations
|
||||
{
|
||||
anglesToRotate.Add(angle);
|
||||
}
|
||||
|
||||
addedAngles.Add(angle);
|
||||
}
|
||||
|
||||
public void removeAngle(float angle)
|
||||
{
|
||||
anglesToRotate.Remove(angle);
|
||||
addedAngles.Remove(angle);
|
||||
}
|
||||
|
||||
// should be called when changing the object to be snapped to
|
||||
public void clearAngles()
|
||||
{
|
||||
foreach (float angle in addedAngles)
|
||||
{
|
||||
anglesToRotate.Remove(angle);
|
||||
}
|
||||
addedAngles.Clear();
|
||||
|
||||
// reset to the nearest rotation angle
|
||||
currentAngleIndex++;
|
||||
currentAngleIndex %= anglesToRotate.Count;
|
||||
}
|
||||
|
||||
private void applyRotation(Transform transform, Vector3 pivot, Vector3 axis, float angle)
|
||||
{
|
||||
transform.RotateAround(pivot, axis, angle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91311cb2805395b4487594770ab5bca2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// regulare snap performed in free move
|
||||
/// </summary>
|
||||
public class DefaultSnapBehavior : ISnapBehavior
|
||||
{
|
||||
public bool TrySnap(BlockEditContext ctx)
|
||||
{
|
||||
if (ctx.TargetSocket == null) return false;
|
||||
if (ctx.MySocket != null && !ctx.TargetSocket.CanAccept(ctx.MySocket)) return false;
|
||||
|
||||
if (ctx.MySocket != null)
|
||||
{
|
||||
// 1. Primary Alignment: Align the exact socket normals
|
||||
Vector3 myNormal = ctx.MySocket.GetNormal().normalized;
|
||||
Vector3 targetNormal = -ctx.TargetSocket.GetNormal().normalized;
|
||||
|
||||
Quaternion primaryRot = Quaternion.FromToRotation(myNormal, targetNormal);
|
||||
ctx.GhostTransform.rotation = primaryRot * ctx.GhostTransform.rotation;
|
||||
|
||||
// 2. Secondary Alignment: Align the 'Up' (or Forward) vectors to prevent random twisting/rolling
|
||||
Vector3 mySecondary = Mathf.Abs(Vector3.Dot(ctx.MySocket.transform.up, targetNormal)) < 0.9f
|
||||
? ctx.MySocket.transform.up
|
||||
: ctx.MySocket.transform.forward;
|
||||
|
||||
Vector3 targetSecondary = Mathf.Abs(Vector3.Dot(ctx.TargetSocket.transform.up, -targetNormal)) < 0.9f
|
||||
? ctx.TargetSocket.transform.up
|
||||
: ctx.TargetSocket.transform.forward;
|
||||
|
||||
// Project secondary vectors onto the flat plane between the sockets
|
||||
Vector3 projectedMySecondary = Vector3.ProjectOnPlane(mySecondary, targetNormal).normalized;
|
||||
Vector3 projectedTargetSecondary = Vector3.ProjectOnPlane(targetSecondary, targetNormal).normalized;
|
||||
|
||||
if (projectedMySecondary != Vector3.zero && projectedTargetSecondary != Vector3.zero)
|
||||
{
|
||||
// Find the twist angle needed to align the secondary axes
|
||||
float angle = Vector3.SignedAngle(projectedMySecondary, projectedTargetSecondary, targetNormal);
|
||||
// Snap to the nearest 45 degrees to allow manual 'R' rotations to persist!
|
||||
float correction = angle - Mathf.Round(angle / 45f) * 45f;
|
||||
// Rotate the ghost around its own pivot (since we haven't translated yet)
|
||||
ctx.GhostTransform.rotation = Quaternion.AngleAxis(correction, targetNormal) * ctx.GhostTransform.rotation;
|
||||
}
|
||||
|
||||
// 3. Translate the ghost so the sockets physically touch
|
||||
ctx.GhostTransform.position +=
|
||||
ctx.TargetSocket.transform.position - ctx.MySocket.transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Transform pivot = ctx.AttachPoint != null ? ctx.AttachPoint : ctx.GhostTransform;
|
||||
Vector3 pivotOffset = pivot.position - ctx.GhostTransform.position;
|
||||
ctx.GhostTransform.position = ctx.TargetSocket.transform.position - pivotOffset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f432ee5d448b4ec409cac4c0ad2b7cd4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Simple material color tint
|
||||
/// </summary>
|
||||
public class DefaultVisualFeedback : IVisualFeedback
|
||||
{
|
||||
public void OnValid(BlockEditContext ctx) => ApplyTint(ctx, ctx.ValidTint);
|
||||
public void OnInvalid(BlockEditContext ctx) => ApplyTint(ctx, ctx.InvalidTint);
|
||||
|
||||
private static void ApplyTint(BlockEditContext ctx, Color tint)
|
||||
{
|
||||
foreach (var r in ctx.GhostRenderers)
|
||||
if (r != null) r.material.color = tint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cca890b0c1294ca4b8b8eda4c5667a36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Placement handler for repositioning an already-placed block.
|
||||
/// </summary>
|
||||
public class ExistingBlockPlacementHandler : IPlacementHandler
|
||||
{
|
||||
private readonly CommandHandler _commandHandler;
|
||||
|
||||
public ExistingBlockPlacementHandler(CommandHandler commandHandler)
|
||||
{
|
||||
_commandHandler = commandHandler;
|
||||
Debug.Log("initialize the ExistingBlockPlacement");
|
||||
}
|
||||
|
||||
public void Commit(BlockEditContext ctx)
|
||||
{
|
||||
if (ctx.SourceObject == null) return;
|
||||
|
||||
_commandHandler.moveBlock(
|
||||
ctx.SourceObject.GetComponent<Block>().getBlockId(),
|
||||
ctx.OriginalPosition,
|
||||
ctx.GhostTransform.position,
|
||||
ctx.OriginalRotation,
|
||||
ctx.GhostTransform.rotation);
|
||||
// ensure the correct positionning after commiting
|
||||
RestoreObject(ctx, ctx.GhostTransform.position, ctx.GhostTransform.rotation);
|
||||
}
|
||||
|
||||
public void Cancel(BlockEditContext ctx)
|
||||
{
|
||||
if (ctx.SourceObject == null) return;
|
||||
RestoreObject(ctx, ctx.OriginalPosition, ctx.OriginalRotation);
|
||||
}
|
||||
|
||||
private void RestoreObject(BlockEditContext ctx, Vector3 pos, Quaternion rot)
|
||||
{
|
||||
ctx.SourceObject.transform.SetPositionAndRotation(pos, rot);
|
||||
RestoreMaterialAlphaAndColor(ctx.SourceObject);
|
||||
SetLayerRecursively(ctx.SourceObject, LayerMask.NameToLayer("Interactable"));
|
||||
ctx.SourceObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void RestoreMaterialAlphaAndColor(GameObject obj)
|
||||
{
|
||||
foreach (var r in obj.GetComponentsInChildren<Renderer>())
|
||||
{
|
||||
if (r.gameObject.layer == LayerMask.NameToLayer("Gizmos")) continue;
|
||||
|
||||
foreach (var mat in r.materials)
|
||||
{
|
||||
if (mat.HasProperty("_Color"))
|
||||
{
|
||||
mat.color = new Color(1f, 1f, 1f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLayerRecursively(GameObject obj, int layer, System.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");
|
||||
});
|
||||
}
|
||||
|
||||
public void Debugln()
|
||||
{
|
||||
Debug.Log("Hi I'm Existing");
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75458c1d52fce984e892d59070e8c68c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
using DependencyInjection.ScriptableObjects;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Concrete ScriptableObject anchor for IPlacementHandler.
|
||||
/// Injected at runtime so consumers always hold a stable typed reference
|
||||
/// and can react to handler swaps via the OnValueChanged event.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "GhostAnchor", menuName = "Dependency Injection/Ghost Anchor")]
|
||||
public class GhostAnchor : RuntimeAnchor<IPlacementHandler>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41e9786ef7c2c154bad698653e1506c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DependencyInjection;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// encapsulate the behaviors implementation and added to the ghost block on runtime
|
||||
/// </summary>
|
||||
public class GhostController : MonoBehaviour
|
||||
{
|
||||
[RuntimeInject]
|
||||
private IPlacementHandler placementHandler;
|
||||
[Inject]
|
||||
private IBlockValidator blockValidator;
|
||||
private ISnapBehavior snapBehavior;
|
||||
private IVisualFeedback visualFeedback;
|
||||
private IRotateHandler rotationHandler;
|
||||
private GlopalLogger logger;
|
||||
|
||||
[Inject]
|
||||
public void init(GlopalLogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (blockValidator == null)
|
||||
{
|
||||
logger.Log("Hamada msh Maugod");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Log("Laa Hamada Maugod");
|
||||
}
|
||||
if (placementHandler == null)
|
||||
{
|
||||
logger.Log("Hamada Eltany msh Maugod");
|
||||
}
|
||||
else
|
||||
{
|
||||
placementHandler.Debugln();
|
||||
}
|
||||
Invoke(nameof(check), 15);
|
||||
}
|
||||
void check()
|
||||
{
|
||||
if (placementHandler == null)
|
||||
{
|
||||
logger.Log("Hamada Eltany msh Maugod");
|
||||
}
|
||||
else
|
||||
{
|
||||
placementHandler.Debugln();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1495a92f469d22429fde76e6ac478fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Placement handler for brand-new blocks dragged from the menu.
|
||||
/// </summary>
|
||||
public class NewBlockPlacementHandler : IPlacementHandler
|
||||
{
|
||||
private readonly CommandHandler _commandHandler;
|
||||
|
||||
public NewBlockPlacementHandler(CommandHandler commandHandler)
|
||||
{
|
||||
_commandHandler = commandHandler;
|
||||
Debug.Log("initialize the NewBlockPlacement");
|
||||
|
||||
}
|
||||
|
||||
public void Commit(BlockEditContext ctx)
|
||||
{
|
||||
if (ctx.Block == null) return;
|
||||
_commandHandler.createBlock(
|
||||
ctx.Block.getBlockData()?.blockName,
|
||||
ctx.GhostTransform.position,
|
||||
ctx.GhostTransform.rotation);
|
||||
}
|
||||
|
||||
public void Cancel(BlockEditContext ctx)
|
||||
{
|
||||
// nothing for now
|
||||
}
|
||||
|
||||
public void Debugln()
|
||||
{
|
||||
Debug.Log("Hi I'm New");
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e76ae260e459c7b4f931cb9c38a0a562
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using DependencyInjection;
|
||||
using UnityEngine;
|
||||
|
||||
public class Provider : MonoBehaviour, IDependencyProvider
|
||||
{
|
||||
[Provide]
|
||||
public IBlockValidator provideValidator()
|
||||
{
|
||||
return new DefaultBlockValidator();
|
||||
}
|
||||
|
||||
[Provide]
|
||||
public IPlacementHandler providePlacementHandler()
|
||||
{
|
||||
Debug.Log("First Handler");
|
||||
return new NewBlockPlacementHandler(null);
|
||||
}
|
||||
|
||||
public void ChangeAnchorInstance()
|
||||
{
|
||||
Injector.Instance.Register<IPlacementHandler>(new ExistingBlockPlacementHandler(null));
|
||||
Debug.Log("Second Handler");
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Invoke(nameof(ChangeAnchorInstance), 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 241c3debd0aeb9840afb1b6b80fe066e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user