#if GRAPH_DESIGNER /// --------------------------------------------- /// Behavior Designer /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.Runtime.Tasks.Actions.TransformTasks { using Opsive.GraphDesigner.Runtime.Variables; using UnityEngine; [Opsive.Shared.Utility.Category("Transform")] [Opsive.Shared.Utility.Description("Aligns the Transform to a surface normal using raycasting. Returns Finished when aligned.")] public class AlignToSurface : TargetGameObjectAction { /// /// Specifies which axis to lock during alignment. /// public enum LockAxis { None, // No axis locked. X, // Lock X axis. Y, // Lock Y axis. Z // Lock Z axis. } [Tooltip("The raycast direction (typically down for ground alignment).")] [SerializeField] protected SharedVariable m_RaycastDirection = Vector3.down; [Tooltip("The raycast distance.")] [SerializeField] protected SharedVariable m_RaycastDistance = 10f; [Tooltip("The layer mask for raycasting.")] [SerializeField] protected LayerMask m_LayerMask = -1; [Tooltip("The alignment speed (degrees per second).")] [SerializeField] protected SharedVariable m_AlignmentSpeed = 90f; [Tooltip("The offset from the surface.")] [SerializeField] protected SharedVariable m_SurfaceOffset = 0f; [Tooltip("The axis to lock during alignment.")] [SerializeField] protected LockAxis m_LockAxis = LockAxis.None; [Tooltip("The agent has arrived when the angle difference is less than this value (in degrees).")] [SerializeField] protected SharedVariable m_ArrivedAngle = 1f; private Vector3 m_SurfaceNormal; private Vector3 m_SurfacePoint; /// /// Aligns to the surface. /// /// The status of the action. public override TaskStatus OnUpdate() { var raycastDir = m_RaycastDirection.Value.normalized; var ray = new Ray(transform.position, raycastDir); if (Physics.Raycast(ray, out RaycastHit hit, m_RaycastDistance.Value, m_LayerMask)) { m_SurfaceNormal = hit.normal; m_SurfacePoint = hit.point; // Calculate target rotation to align with surface normal. var targetRotation = Quaternion.FromToRotation(Vector3.up, m_SurfaceNormal); // Lock axis if specified. if (m_LockAxis != LockAxis.None) { var currentEuler = transform.eulerAngles; var targetEuler = targetRotation.eulerAngles; switch (m_LockAxis) { case LockAxis.X: targetEuler.x = currentEuler.x; break; case LockAxis.Y: targetEuler.y = currentEuler.y; break; case LockAxis.Z: targetEuler.z = currentEuler.z; break; } targetRotation = Quaternion.Euler(targetEuler); } // Rotate towards target rotation. var maxDegreesDelta = m_AlignmentSpeed.Value * Time.deltaTime; transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, maxDegreesDelta); // Position offset from surface. if (m_SurfaceOffset.Value != 0f) { transform.position = m_SurfacePoint + m_SurfaceNormal * m_SurfaceOffset.Value; } // Check if arrived. var angleDifference = Quaternion.Angle(transform.rotation, targetRotation); if (angleDifference < m_ArrivedAngle.Value) { transform.rotation = targetRotation; return TaskStatus.Success; } } return TaskStatus.Running; } } } #endif