Jump to content

Creating a "ground-check" for a Jump button using World Intersections (Raycast)


photo

Recommended Posts

Hello, please excuse me for my lack of knowledge lol. This is my first time learning C# and I've started off following basic Unity tutorials for a player controller, but I've been struggling mainly with making an efficient jump button where there is a boolean that switches true/false on collision with the raycast. Here is my code for reference:

public class RaycastJump : Component
{
    [ShowInEditor]
        Node Bot, gChecc;
 
        [Parameter(Title = "Jump Key")]
        public Input.KEY JumpKey = Input.KEY.SPACE;
 
        [ParameterSlider(Min = 1.0f, Max = 80.0f)]
        public float JumpForce = 40.0f;
   
    Player player;
    WorldIntersection Ray;
    Unigine.Object Obj;
 
    void Init()
    {
        Visualizer.Enabled = true;
    }
 
    private void Update()
    {
        var from = Bot.WorldPosition;
        var to = gChecc.WorldPosition;
        WorldIntersectionNormal Ray = new WorldIntersectionNormal();
        World.GetIntersection(from, to, 1, Ray);
        dvec3 point = Ray.Point;
        Visualizer.RenderVector(point, to, vec4.ONE, arrow_size: 0.2f);
    }
 
    void UpdatePhysics()
    {
        if (Input.IsKeyPressed(JumpKey) && isGrounded())
                Jump(Bot.ObjectBodyRigid.GetWorldVelocity(vec3.UP));
    }
 
    private bool isGrounded()
    {
        return gChecc.GetWorldTrigger(1);
    }
 
    void Jump(vec3 Up)
    {
        Bot.ObjectBodyRigid.AddLinearImpulse(vec3.UP * JumpForce);
    }
}
Edited by ThaDoggFather
More specific (using a Boolean)
Link to comment

You have way too much going on in one place and tons of dead code there.
Let's start over.
Think of a component as an interface and/or extension on a node. 
Your problem has two parts: 1) Find Height 2) Jump If on ground
Both parts act on the same node. For Part 1, Height is a measure relative to another object - "Ground".
In your case, "ground" (prob) isn't a specific object - you can jump on anything you are standing on.
You need to detect any item below (-z) you, and in contact/intersecting with you. "Ground" isn't changed so it's just a temp value.

So, there is just one object to deal with "Bot". And we are only modifying the position of Bot's node.
We can build a generic "Jumper" component for Bot (and anything traceable to a node).
You said you wanted a ray cast approach.

The first part of the problem, "detecting below", has a basic pattern

var a_ = Position;
var b_ = a_ with { z = a_.z - detectRange };
GetIntersection(a_, b_, mask, out Intersection c_);

The simplest approach to just detecting "being on" something is to set your detect range small:

var b_ = Position with { z = a_.z - 0.01f };

If there is a surface directly below a Point "Position" and within 0.01, it is detected. 
Floats can get fuzzy with small deltas though, so it's not really a solid approach without some clamping/hysteresis.
If "Position" is a bodied object things are a little different. 
If you get "node.WorldPosition", the point you get is most likely in the mesh interior. A trace will detect the mesh surfaces.

There are lot of ways to deal with this. One is to start from a point outside the object.
"node.WorldBoundBox.minimum.z" gives us a point on the outer shell. 

Searching below is pretty common, so we can write a little node extension for that. 

internal static class UNodeExtensions {
    internal static (dvec3 source, dvec3 hit, double height, UO obj, int surface)
    DetectBelow(this UN node, double detectRange, int mask) {
        dvec3 a_ = node.WorldPosition with { z = node.WorldBoundBox.minimum.z };
        dvec3 b_ = a_ with { z = a_.z - detectRange };
        WorldIntersection c_ = new WorldIntersection();
        UO d_ = World.GetIntersection(a_, b_, mask, c_);
        return (a_, c_.Point, a_.z - c_.Point.z, d_, c_.Surface);
        }
    }

This returns some stuff for us. We're mainly after height in this case, but we can grab some stuff while we're there.
Height // Above Surface
Object, Surface // What we are above
Source, Hit // Support Visualizer etc.

Now we create a Component.
Your component runs every physics tick and uses keyboard (space) input. IsKeyPressed fires every tick.
I don't know if you meant to use IsKeyDown (one-shot), but we'll go with this. We just need to gate the input.
We can also add a little hysteresis and "thicken" the ground. As long as height is below the threshold Bot "IsGrounded".  

using System.Diagnostics;
using Unigine;
using UN = Unigine.Node;
using UO = Unigine.Object;

public class Jumper : Component {
    [ShowInEditor]
    [Parameter(Title = "Jump Key")]
    internal Input.KEY jumpKey = Input.KEY.SPACE;

    [ShowInEditor]
    [ParameterSlider(Min = 1.0f, Max = 80.0f)]
    internal float jumpForce = 5.0f;

    [ShowInEditor]
    [ParameterSlider(Min = .0f, Max = 40.0f)]
    internal double maxDetectRange = 10f;

    [ShowInEditor]
    [ParameterSlider(Min = 0.001f, Max = 1.0f)]
    internal double groundZoneHeight = 0.01f;

    [ShowInEditor]
    [ParameterSlider(Min = 0.1f, Max = 1.0f)]
    internal float deadZoneTime = 0.1f;

    public bool isGrounded => InGroundZone;
    public double Height { get; private set; } = 0;

    bool IsJump => Input.IsKeyPressed(jumpKey) && !InDeadTime;
    float deadTimeMark = 0;

    bool InDeadTime => Game.Time < deadTimeMark;
    bool InGroundZone => Height < groundZoneHeight;
    void MakeDeadTime() => deadTimeMark = Game.Time + deadZoneTime;

    void UpdatePhysics() {
        (dvec3 a_, dvec3 b_, Height, _, _) = node.DetectBelow(maxDetectRange, 0xFFFFFFF);
        if (IsJump) TryJump(jumpForce);
        Visualizer.RenderVector(a_, b_, vec4.ONE, arrow_size: 0.2f);
        Debug.WriteLine(Height);
        }

    void TryJump(float force) {
        if (!InGroundZone) return;
        MakeDeadTime();
        node.ObjectBodyRigid.AddLinearImpulse(vec3.UP * force);
        }
    }

You can put the Visualizer stuff in Main or other places

Engine.Init(args);
Visualizer.Enabled = true;
Unigine.Console.Onscreen = true;

 

You don't need all of those variables and stuff. Use the built-in parts and static classes to do to things.
Keep your components small and focused.

Test this by putting a capsule in the world and adding this component.

image.png.2c89dcb43cc72dc527725c260cd2ada5.png

  • Like 2
Link to comment

Hey thank you so much for this, I'd been trying for a while now but got stuck into making my world for a bit. I'll go over it a few times to grasp a better understanding, and if I don't get back to you then thanks a bunch!

Link to comment
×
×
  • Create New...