英文:
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();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论