ExtendRealityLtd/Tilia.Trackers.PseudoBody.Unity

Force Airborne ability

HugoCortell opened this issue · 16 comments

Is your feature request related to a problem?

Not quite, but forcing grounding could help with the issue below.
#45 (comment)

Describe the solution you'd like

I would like to have a way to force the pseudo body to get its source of truth for movement from rigidbody.
A use case is to apply force/pushback when player enters/exits a force field (or gets hit by an explosion). Currently, if player is grounded, add force will not do anything.

It would also be great if it could be reversed, the ability to "force grounding" could come useful in places such as steep ramps.

Describe alternatives you've considered

I have attempted to use Ethan's script, but sadly it had no effect as far as I could tell.

using Tilia.Trackers.PseudoBody;
using UnityEngine;

// Code produced by the VRTK team, thanks Ethan. Modified for compactness.
public class PseudoGroundedOverwrite : PseudoBodyProcessor
{
    [SerializeField] private bool forceAirborne = false;
    public bool ForceAirborne { get => forceAirborne; set => forceAirborne = value; }

    protected override bool CheckIfCharacterControllerIsGrounded()
    {
        if (ForceAirborne) {return false;}
        else {return base.CheckIfCharacterControllerIsGrounded();}
    }
}

The bool would not become false once turned true, but it also had no effect on grounding.

Additional context

Here is the code I use to push back the player, iv tested it with physics balls and it worked quite well.

private void OnTriggerEnter(Collider col)
    {
        if (col.tag == "Player")
        {
            // Apply force pushback
            col.GetComponent<PseudoGroundedOverwrite>().ForceAirborne = true;

            // CHECK ANGLE AND EXECUTE PUSHBACK BASED ON RESULTS.
            if (_ParentScript.gameObject.transform.eulerAngles.y == 0)
            {col.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(transform.position.x, transform.position.y, col.transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}
            else if (_ParentScript.gameObject.transform.eulerAngles.y == 90)
            {col.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(col.transform.position.x, transform.position.y, transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}
            else if (_ParentScript.gameObject.transform.eulerAngles.z == 90) // NOW ITS UP-N-DOWN
            {col.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(transform.position.x, col.transform.position.y, transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}

            if (_ParentScript._IsDebugEnabled == true){Debug.Log("<color=red>LASER DEBUG:</color> PLAYER TOUCHED LASER, PUSHBACK: " + _ParentScript._PushBack);}

            // Deal damage??
        }
}

Its just AddExplosionForce, but made to work with the way the psuedobody is set up.

you turn the ForceAirborne to true in OnTriggerEnter,
you'll turn it to false in OnTriggerExit.
you should manage that boolean yourself, it of course will not turn off by itself

you turn the ForceAirborne to true in OnTriggerEnter,
you'll turn it to false in OnTriggerExit.
you should manage that boolean yourself, it of course will not turn off by itself

using UnityEngine;

public class KillLaserTrigger : MonoBehaviour
{
    public KillLaser _ParentScript;
    [SerializeField] private int _EntitiesWithinBounds = 0;

    protected GameObject _OutsideTriggerReference = null;

    private void Start() {InvokeRepeating("ParentalCheckRecalculation", 1.0f, 0.45f);} // Esentially its a selective update. Kind of neat.

    private void OnTriggerEnter(Collider col)
    {
        if (col.tag == "Player")
        {
            // Generate reference
            _OutsideTriggerReference = col.gameObject;

            // Apply force pushback
            col.GetComponent<PseudoGroundedOverwrite>().ForceAirborne = true;
            Invoke ("GeneratePushBack", Time.deltaTime); // Calls after one frame, ensures force airborne has fully processed.

            // Deal damage??
        }

        // [ALWAYS AT THE END] - This way, the world is not accounted for.
        if (col.tag == "Player" || col.tag == "PhysInteractable") {if (_ParentScript._IsDebugEnabled == true){_EntitiesWithinBounds++; Debug.Log(col);}} // Wait this might not be working right... Check later!
    }

    private void GeneratePushBack()
    {
        // CHECK ANGLE AND EXECUTE PUSHBACK BASED ON RESULTS.
        if (_ParentScript.gameObject.transform.eulerAngles.y == 0)
        {_OutsideTriggerReference.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(transform.position.x, transform.position.y, _OutsideTriggerReference.transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}
        else if (_ParentScript.gameObject.transform.eulerAngles.y == 90)
        {_OutsideTriggerReference.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(_OutsideTriggerReference.transform.position.x, transform.position.y, transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}
        else if (_ParentScript.gameObject.transform.eulerAngles.z == 90) // NOW ITS UP-N-DOWN
        {_OutsideTriggerReference.GetComponent<PlayerRigidbodyRedirector>()._Rigidbody.AddExplosionForce(_ParentScript._PushBack, new Vector3(transform.position.x, _OutsideTriggerReference.transform.position.y, transform.position.z), Mathf.Infinity, 1f, ForceMode.Impulse);}

        if (_ParentScript._IsDebugEnabled == true){Debug.Log("<color=red>LASER DEBUG:</color> PLAYER TOUCHED LASER, PUSHBACK: " + _ParentScript._PushBack);}

        _OutsideTriggerReference = null;
    }

    private void OnTriggerExit(Collider col) 
    {
        if (col.tag == "Player") {col.GetComponent<PseudoGroundedOverwrite>().ForceAirborne = false;}

        // [ALWAYS AT THE END]
        if (col.tag == "Player" || col.tag == "PhysInteractable") {if (_ParentScript._IsDebugEnabled == true){_EntitiesWithinBounds++; Debug.Log(col);}}
        if (_EntitiesWithinBounds == 0) // Ensures one last check
        {
            Invoke("ParentalRecalculation", 0.2f);
            Invoke("ParentalRecalculation", 0.65f);
        }
    }

    // Why are there two? Because that way I can use invoke to skip the check when necessary (The raycast is called too soon if it runs in real time)
    // Is it wasteful? Yes. Is it necessary? Also yes.
    private void ParentalCheckRecalculation() {if (_EntitiesWithinBounds != 0){ParentalRecalculation();}}
    private void ParentalRecalculation() => _ParentScript.RecalculateDistance();
}

I see, I thought the psuedo body would overwrite that if it found new ground.
I have set it up exactly as it should be, even taking a frame or so to ensure all changes are processed, but sadly I cant say its working.

image
the Moment Process of pseudo body is 'Fixed Update'
so i'm not sure the Invoke deltaTime can make sure the internal state is ready for it to add force or not.
try 0.1f or 0.2f.

also, you may use a Coroutine and check for
yield return new WaitUntil(() => _OutsideTriggerReference.GetComponent<PseudoGroundedOverwrite>().Interest == PseudoBodyProcessor.MovementInterest.RigidbodyUntilGrounded);

image
i have found that even if ForceAirborne for a while, the Interest does not change to RigidbodyUntilGrounded
i'll debug it further.

image
it was just me forgot to fill in the MomentProcess after removing the original one

image
the Interest changed no problem.

image
image
my force field works. notice that the pseudo body is affected by gravity, so your force has to overcome it while adding enough horizontal force.
in your code it is only 1f upwards, i doubt it cannot even overcome the ground friction too.

Hey there!
I managed to get it to work thanks to the screenshot you posted.
I realized that the custom psuedo body processor was supposed to replace the original one. I had not done that.

image

It now works (with constant force, have not tested it with lasers yet).
I fear removing the original script might cause issues with updates, should I, now that iv confirmed that it works, remove the custom script and wait for an update that adds this feature to the original script?

as this might not be the best solution/feature, this remains a custom feature to your own disperse.
you can keep the original disabled, no problem.

Understood, thank you.

Even though it works, the player has lost all collision.
I cant walk up stairs or ramps, I go through walls and I cant interact with triggers.
(Edit apparently now I can go up ramps for some reason, nothing was changed).
image
All the references are still there and I did not disable any other component, what should I do?

it does have collision. see the collider as shown below.
image

however i don't know how to 'push back' the headset and controllers
@thestonefox may know about this

I made a video showcasing the issue I am having, hopefully it will help illustrate my problem.
https://gofile.io/d/XJm3ZB

I probably set up something wrong.

image
remember to set the Internal reference
at the bottom

Oh my goodness I feel so stupid. THANK YOU!!
I was panicking so hard that I might have just done goofed.

A bit of a side question, why is the "force airborne" not the default? What are the benefits of not being airborne all the time?
I am legitimately curious because I left it on by mistake and noticed no significant difference.

Forcing airborne with a trigger on all rams fixes #45 (comment), though it feels a bit of a "bethesda" fix.

Anyways, ill close this issue after I get an answer to my curious question, I am incredibly happy you guys were able to help me with this.

you can leave it Open, as it is tagged 'To Do'.

when 'Force Airborne'=false, it uses the default behavior of the pseudo body. So it is default to false to not change the original behavior.

when 'Force Airborne'=true, it means the body will always become a rigidbody. If you think your game behave exactly what you wanted in this mode, you can actually just attach a rigidbody+collider to your trackedAlias or playAreaAlias and that's it.

That sounds quite enticing but I think ill stick to the pseudo body because I like the collision detection (for the head position) and the auto-sizing of the player based on its actual height.