Need Help Spawning an Object...

So I’ve been trying to tackle this issue for months now…

I have a ship I want to spawn on the seafloor. The seafloor sprite has slopes. I want the ship to land on the seafloor and align itself properly with the slope of the seafloor. Trick is to do this without gravity or preset positions. Any way I can do this?

The way I’ve done it is to apply gravity and have gravity do the job. The problem with this is when I speed up the fish, the ship can be seen tilting itself to align with the seafloor. I do not want that to be seen. I want the ship to always be aligned by the time it is visible on screen.

The other way I’ve done it was to have a few preset positions for the ship to spawn at (with preset Y position and Z rotation). The problem with this however is (a) the seafloor moves over time as it teleports periodically, which cancels out the benefit of having preset positions, and (b) it’s not as random as I would like it to be.

Is there ANY way to just have the ship know how to align itself with the slope of the seafloor without using gravity?

Any help would be greatly appreciated!!

EDIT: This is a 2D game. I’ve read stuff about Raycast but not sure how I could use them (am a noob).

- Chris

I came across this blog a while ago - Getting the Angle of Sloped Ground Beneath a Player in Unity

Did a 5 min bodge to make it adjust the angle of a game object. Maybe it will help you

using UnityEngine;
using System;

// Finds the slope/grade/incline angle of ground underneath a CharacterController
public class SeaAngleCheck : MonoBehaviour
{

    [Header("Results")]
    public float groundSlopeAngle = 0f;            // Angle of the slope in degrees
    public Vector3 groundSlopeDir = Vector3.zero;  // The calculated slope as a vector

    [Header("Settings")]
    public bool showDebug = false;                  // Show debug gizmos and lines
    public LayerMask castingMask;                  // Layer mask for casts. You'll want to ignore the player.
    public float startDistanceFromBottom = 0.2f;   // Should probably be higher than skin width
    public float sphereCastRadius = 0.25f;
    public float sphereCastDistance = 0.75f;       // How far spherecast moves down from origin point

    public float raycastLength = 0.75f;
    public Vector3 rayOriginOffset1 = new Vector3(-0.2f, 0f, 0.16f);
    public Vector3 rayOriginOffset2 = new Vector3(0.2f, 0f, -0.16f);

    // Component reference
    private BoxCollider boxCollider;

    void Awake()
    {
        // Get component on the same GameObject
        boxCollider = GetComponent<BoxCollider>();
        if (boxCollider == null) { Debug.LogError("GroundChecker did not find a CharacterController component."); }
    }

    void FixedUpdate()
    {
        // Check ground, with an origin point defaulting to the bottom middle
        // of the char controller's collider. Plus a little higher
        if (boxCollider)
        {
            CheckGround(new Vector3(transform.position.x, transform.position.y - (boxCollider.bounds.extents.y / 2) + startDistanceFromBottom, transform.position.z));

            transform.rotation = Quaternion.Euler(0, 0, groundSlopeAngle);
        }
    }

    /// <summary>
    /// Checks for ground underneath, to determine some info about it, including the slope angle.
    /// </summary>
    /// <param name="origin">Point to start checking downwards from</param>
    public void CheckGround(Vector3 origin)
    {
        // Out hit point from our cast(s)
        RaycastHit hit;

        // SPHERECAST
        // "Casts a sphere along a ray and returns detailed information on what was hit."
        if (Physics.SphereCast(origin, sphereCastRadius, Vector3.down, out hit, sphereCastDistance, castingMask))
        {
            // Angle of our slope (between these two vectors).
            // A hit normal is at a 90 degree angle from the surface that is collided with (at the point of collision).
            // e.g. On a flat surface, both vectors are facing straight up, so the angle is 0.
            groundSlopeAngle = Vector3.Angle(hit.normal, Vector3.up);

            // Find the vector that represents our slope as well.
            //  temp: basically, finds vector moving across hit surface
            Vector3 temp = Vector3.Cross(hit.normal, Vector3.down);
            //  Now use this vector and the hit normal, to find the other vector moving up and down the hit surface
            groundSlopeDir = Vector3.Cross(temp, hit.normal);
        }

        // Now that's all fine and dandy, but on edges, corners, etc, we get angle values that we don't want.
        // To correct for this, let's do some raycasts. You could do more raycasts, and check for more
        // edge cases here. There are lots of situations that could pop up, so test and see what gives you trouble.
        RaycastHit slopeHit1;
        RaycastHit slopeHit2;
               
        // FIRST RAYCAST
        if (Physics.Raycast(origin + rayOriginOffset1, Vector3.down, out slopeHit1, raycastLength))
        {
            // Debug line to first hit point
            if (showDebug) { Debug.DrawLine(origin + rayOriginOffset1, slopeHit1.point, Color.red); }
            // Get angle of slope on hit normal
            float angleOne = Vector3.Angle(slopeHit1.normal, Vector3.up);

            // 2ND RAYCAST
            if (Physics.Raycast(origin + rayOriginOffset2, Vector3.down, out slopeHit2, raycastLength))
            {
                // Debug line to second hit point
                if (showDebug) { Debug.DrawLine(origin + rayOriginOffset2, slopeHit2.point, Color.red); }
                // Get angle of slope of these two hit points.
                float angleTwo = Vector3.Angle(slopeHit2.normal, Vector3.up);
                // 3 collision points: Take the MEDIAN by sorting array and grabbing middle.
                float[] tempArray = new float[] { groundSlopeAngle, angleOne, angleTwo };
                Array.Sort(tempArray);
                if (slopeHit1.distance > slopeHit2.distance)
                    groundSlopeAngle = tempArray[1];
                else
                    groundSlopeAngle = -tempArray[1];
            }
            else
            {
                // 2 collision points (sphere and first raycast): AVERAGE the two
                float average = (groundSlopeAngle + angleOne) / 2;
                if (slopeHit1.distance > slopeHit2.distance)
                    groundSlopeAngle = -average;
                else
                    groundSlopeAngle = average;
            }
        }
    }

    void OnDrawGizmosSelected()
    {
        if (showDebug)
        {
            // Visualize SphereCast with two spheres and a line
            Vector3 startPoint = new Vector3(transform.position.x, transform.position.y - (boxCollider.bounds.extents.y / 2) + startDistanceFromBottom, transform.position.z);
            Vector3 endPoint = new Vector3(transform.position.x, transform.position.y - (boxCollider.bounds.extents.y / 2) + startDistanceFromBottom - sphereCastDistance, transform.position.z);

            Gizmos.color = Color.white;
            Gizmos.DrawWireSphere(startPoint, sphereCastRadius);

            Gizmos.color = Color.gray;
            Gizmos.DrawWireSphere(endPoint, sphereCastRadius);

            Gizmos.DrawLine(startPoint, endPoint);
        }
    }

}

I appreciate the help, but having never used raycasts before and having basic knowledge of C#, it’s hard for me to make sense of that. I’ve looked more into raycasts and I understand how you find the slope, I’m just not sure how you align the ship to it.

In that script, groundSlopeAngle simple gives you an angle in a float.

Attach the script to a cube in your scene and move it around in the editor, you will see what happens.