Unity 3D: 制作一个简单的胶囊稳定器

huangapple go评论62阅读模式
英文:

Unity 3D: Making a simple capsule stabilizer

问题

使用UnityEngine;

public class SelfRighting : MonoBehaviour
{
    public Rigidbody rb;
    public float selfRightingForce = 10f;
    public float maxRotationAngle = 45f;

    private void OnCollisionStay(Collision collision)
    {
        if (collision.gameObject.CompareTag("地面"))
        {
            Vector3 currentRotation = transform.eulerAngles;
            float angle = Mathf.DeltaAngle(currentRotation.z, 0f);
            float rotationDirection = Mathf.Sign(angle);
            float rotationAmount = Mathf.Clamp(angle * selfRightingForce, -maxRotationAngle, maxRotationAngle);
            Vector3 rotationForce = new Vector3(0f, 0f, rotationDirection * rotationAmount);

            rb.AddTorque(rotationForce);
        }
    }
}
英文:

I'm very new to Unity, and my C# skills here are nominal at best. I have a simple player controller that I am making involving a player who is a simple capsule. I added a rigidbody to it to allow for more realistic physics, but I wanted to go beyond the basic Freeze Rotation parameters and create some sort of stabilizer that would allow the capsule to right itself using smooth rotation, kind of like a Weeble (for those of you who existed back then)

I tried asking ChatGPT to generate the code, and it generated this code snippet:

using UnityEngine;

public class SelfRighting : MonoBehaviour
{
    public Rigidbody rb;
    public float selfRightingForce = 10f;
    public float maxRotationAngle = 45f;

    private void OnCollisionStay(Collision collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            Vector3 currentRotation = transform.eulerAngles;
            float angle = Mathf.DeltaAngle(currentRotation.z, 0f);
            float rotationDirection = Mathf.Sign(angle);
            float rotationAmount = Mathf.Clamp(angle * selfRightingForce, -maxRotationAngle, maxRotationAngle);
            Vector3 rotationForce = new Vector3(0f, 0f, rotationDirection * rotationAmount);

            rb.AddTorque(rotationForce);
        }
    }
}

This resulted in the capsule tumbling across the level, head-over-heels. I pointed out this problem, and it returned this:

using UnityEngine;

public class SelfRighting : MonoBehaviour
{
    public Rigidbody Rigidbody;
    public float SelfRightingForce = 10f;
    public float MaxRotationAngle = 45f;

    private void OnCollisionStay(Collision collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            Quaternion targetRotation = Quaternion.Euler(0f, transform.rotation.eulerAngles.y, 0f);
            Quaternion currentRotation = transform.rotation;
            float angle = Quaternion.Angle(currentRotation, targetRotation);
            float rotationDirection = Mathf.Sign(angle);
            float rotationAmount = Mathf.Clamp(angle * SelfRightingForce, -MaxRotationAngle, MaxRotationAngle);
            Vector3 rotationTorque = new Vector3(0f, rotationDirection * rotationAmount, 0f);

            Rigidbody.AddTorque(rotationTorque);
        }
    }
}

This caused the capsule to spin to the floor, kind of like a top running out of momentum. Further correction attempts proved futile.

答案1

得分: 1

我曾经通过调整质心来实现类似的功能。最后我放弃了这个功能,但我仍然有这个脚本,并且记得它运行得很好,也许对你有用。

一些注意事项:

  • 这是为我的游戏中的圆柱形桶设计的,而不是胶囊。
  • 不要对我的代码太苛刻,那时我还很年轻!
  • 我不知道这个TempCenter函数有什么作用。
  • 使用说明是将此附加到你的对象上,然后应该可以使用(我想)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(MeshFilter))]
public class BarrelCenterOfMass : MonoBehaviour // keeps barrel center of mass at the bottom so barrels don't knock down as easy
{
    // 注意-这里的所有初始值都是我在以前测试时得出的。如果我以后想再次使用这个类,这些值是可以使用的

    [SerializeField] [Range(-1f, 1f)] // Serialized private because changing it at runtime does nothing (if need be I can implement a Set method so it's changeable at runtime but I don't have to recalculate relativePoint * height every frame
    private float relativePoint = -0.8f; // how far along the Y extents will the center of mass be?
    public float centerDotThreshold = 0.5f; // at Dot products below this absolute value the center of mass becomes (0, 0, 0). Otherwise it's (0, height * relativePoint, 0)
    public float tempCenterTime = 0.75f;

    private bool centered = false;
    private float localPosition = 0f;

    private Rigidbody body;
    private Transform tf;

    void Start() // Coroutine because it's gonna turn into a loop where the object's center of mass is updated every refreshRate seconds. Not every frame because that causes weird behaviors sometimes
    {
        body = GetComponent<Rigidbody>();
        tf = GetComponent<Transform>();

        localPosition = GetComponent<MeshFilter>().mesh.bounds.extents.y * tf.lossyScale.y * relativePoint; // instead of doing this calculation of height * relativePoint every frame, I just do it once here
    }

    void FixedUpdate()
    {
        #if UNITY_EDITOR

        localPosition = GetComponent<MeshFilter>().mesh.bounds.extents.y * tf.lossyScale.y * relativePoint; // just so I can tweak relativePoint in the editor and have it take effect at runtime

        #endif

        if (!centered)
        {
            // finding the dot product and rounding it to 0 of 1 so that if the dot is close enough to 0, it'll equal to 0 and otherwise it will be 1. Where the threshold is is defined by me
            var dot = RoundDot(Vector3.Dot(tf.up, Vector3.up));

            // Changing center of mass to center if the dot is 0 and the current center of mass isn't 0
            if (dot == 0f)
            {
                if (body.centerOfMass.y != 0f) // if inside if instead of using && so that if for example the first condition is met but this isn't, it won't even check the condition in the else statement
                {
                    body.centerOfMass = Vector3.zero;
                }
            }
            else if (dot == 1f) // Changing center of mass to localPosition if the dot is 1 and the center of mass isn't at localPosition (i.e. it's at 0)
            {
                if (body.centerOfMass.y != 0f)
                {
                    body.centerOfMass = Vector3.up * localPosition;
                }
            }
        }
    }

    private float RoundDot(float dot)
    {
        if (Mathf.Abs(dot) < centerDotThreshold)
        {
            return 0f;
        }

        return Mathf.Sign(dot);
    }

    public void Center()
    {
        centered = true;
        body.centerOfMass = Vector3.zero;
    }

    public void UnCenter()
    {
        centered = false;
    }

    public void TempCenter()
    {
        StopAllCoroutines();
        StartCoroutine(TempCenterCooldown());
    }

    private IEnumerator TempCenterCooldown()
    {
        Center();
        yield return new WaitForSeconds(tempCenterTime);
        UnCenter();
    }
}
英文:

I once did something like this by playing with the center of mass. In the end I scrapped the feature, but I still have the script and I remember it worked well, maybe it will be useful for you.

A few notes:

  • This was meant for barrels in my game which are cylindrical, not capsules.
  • Don't judge my code too much, I was young then!
  • I have no idea what this TempCenter function is for.
  • Usage instructions are attach this to your object and you should be good to go (I think)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(MeshFilter))]
public class BarrelCenterOfMass : MonoBehaviour // keeps barrel center of mass at the bottom so barrels don't knock down as easy
{
    // note - all the initial values here are the ones that I came to after testing this back when I was using it. If I ever want to use this class again, these values are good to go

    [SerializeField] [Range(-1f, 1f)] // Serialized private because changing it at runtime does nothing (if need be I can implement a Set method so it's changeable at runtime but I don't have to recalculate relativePoint * height every frame
    private float relativePoint = -0.8f; // how far along the Y extents will the center of mass be?
    public float centerDotThreshold = 0.5f; // at Dot products below this absolute value the center of mass becomes (0, 0, 0). Otherwise it's (0, height * relativePoint, 0)
    public float tempCenterTime = 0.75f;

    private bool centered = false;
    private float localPosition = 0f;

    private Rigidbody body;
    private Transform tf;

    void Start() // Coroutine because it's gonna turn into a loop where the object's center of mass is updated every refreshRate seconds. Not every frame because that causes weird behaviors sometimes
    {
        body = GetComponent<Rigidbody>();
        tf = GetComponent<Transform>();

        localPosition = GetComponent<MeshFilter>().mesh.bounds.extents.y * tf.lossyScale.y * relativePoint; // instead of doing this calculation of height * relativePoint every frame, I just do it once here
    }

    void FixedUpdate()
    {
        #if UNITY_EDITOR

        localPosition = GetComponent<MeshFilter>().mesh.bounds.extents.y * tf.lossyScale.y * relativePoint; // just so I can tweak relativePoint in the editor and have it take effect at runtime

        #endif

        if (!centered)
        {
            // finding the dot product and rounding it to 0 of 1 so that if the dot is close enough to 0, it'll equal to 0 and otherwise it will be 1. Where the threshold is is defined by me
            var dot = RoundDot(Vector3.Dot(tf.up, Vector3.up));

            // Changing center of mass to center if the dot is 0 and the current center of mass isn't 0
            if (dot == 0f)
            {
                if (body.centerOfMass.y != 0f) // if inside if instead of using && so that if for example the first condition is met but this isn't, it won't even check the condition in the else statement
                {
                    body.centerOfMass = Vector3.zero;
                }
            }
            else if (dot == 1f) // Changing center of mass to localPosition if the dot is 1 and the center of mass isn't at localPosition (i.e. it's at 0)
            {
                if (body.centerOfMass.y != 0f)
                {
                    body.centerOfMass = Vector3.up * localPosition;
                }
            }
        }
    }

    private float RoundDot(float dot)
    {
        if (Mathf.Abs(dot) < centerDotThreshold)
        {
            return 0f;
        }

        return Mathf.Sign(dot);
    }

    public void Center()
    {
        centered = true;
        body.centerOfMass = Vector3.zero;
    }

    public void UnCenter()
    {
        centered = false;
    }

    public void TempCenter()
    {
        StopAllCoroutines();
        StartCoroutine(TempCenterCooldown());
    }

    private IEnumerator TempCenterCooldown()
    {
        Center();
        yield return new WaitForSeconds(tempCenterTime);
        UnCenter();
    }
}

huangapple
  • 本文由 发表于 2023年6月13日 04:50:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460241.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定