3

我找到官方统一培训https://www.youtube.com/watch?v=D5MqLcO6A8g 发现bug。(看分数)

我花了大约 2 天的时间来修复它,但失败了。我找到“DontGoThroughThings”脚本并尝试重写以在 2D 中使用。又失败了)

请帮我!

这是重写脚本:

public LayerMask layerMask; //make sure we aren't in this layer 
public float skinWidth; //probably doesn't need to be changed 

private float minimumExtent; 
private float partialExtent; 
private float sqrMinimumExtent; 
private Vector2 previousPosition; 
private Rigidbody2D myRigidbody; 


//initialize values 
void Awake() 
{ 
    myRigidbody = GetComponent<Rigidbody2D>(); 
    previousPosition = myRigidbody.position;
    minimumExtent = Mathf.Min(Mathf.Min(GetComponent<Collider2D>().bounds.extents.x, GetComponent<Collider2D>().bounds.extents.y)); 
    partialExtent = minimumExtent * (1.0f - skinWidth); 
    sqrMinimumExtent = minimumExtent * minimumExtent; 
} 

void FixedUpdate() 
{ 
    //have we moved more than our minimum extent? 
    Vector2 movementThisStep = myRigidbody.position - previousPosition; 
    float movementSqrMagnitude = movementThisStep.sqrMagnitude;

    if (movementSqrMagnitude > sqrMinimumExtent) 
    { 
        float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);
        //RaycastHit2D hitInfo; 

        //check for obstructions we might have missed 
        if (Physics2D.Raycast(previousPosition, movementThisStep, movementMagnitude, 0, layerMask.value)) 
            myRigidbody.position = (movementThisStep/movementMagnitude)*partialExtent;
        Debug.DrawLine(myRigidbody.position, myRigidbody.position - previousPosition, Color.green);
    } 

    previousPosition = myRigidbody.position; 
}

这是统一包https://www.dropbox.com/s/a3n1dalbc1k0k42/Hat%20Trick.unitypackage?dl=0

PS对不起我的英语,谢谢你的帮助!!

4

2 回答 2

1

解释

Unity 中的连续碰撞检测不使用光线投射。结果,非常快速移动(和/或相对较小)的物体(我们现在称之为那种物体projectile)仍然穿过物体而不会检测到碰撞。著名的DontGoThroughThings组件为 3D 修复了该问题。有一些不一致之处,但是如果您知道自己在做什么,它就可以完成工作。

这是我对它的 2D 改编。

我添加了一些功能,使其对不擅长编码或游戏物理的每个人都更加用户友好。

如何使用它

  1. 将此组件添加到快速移动的对象中,它们将始终OnTriggerEnter2D在击中某物时触发事件。
  2. 您也可以选择发送不同的自定义消息(通过更改MessageName变量)。由于下面解释的警告,我实际上建议这样做。
  3. 发送消息是脚本的主要用例。它不会神奇地使射弹在物理意义上正确运行。
  4. triggerTarget变量确定它是否将消息发送给自己(如果您将命中处理脚本附加到弹丸),发送到被击中的对象(如果您将命中处理附加到应该被弹丸击中的对象) ),或两者的任何变体。
  5. 与原始版本不同,此脚本还允许在冲击时施加力,这可以通过momentumTransferFraction变量进行调整。当两个物体发生碰撞时,所产生的力是两个物体之间动量(质量乘以速度)传递的结果。我这样做的方式非常简陋,并且缺少很多促成因素,但是让射弹在撞击时推动物体就足够了。就像在现实世界中一样,您的弹丸越快或越重,施加的力就越大。

还有一些注意事项(其中大部分也适用于原始版本)

  1. 仅适用于非常快速移动的物体。你使用的越少越好,因为它比正常的碰撞检测在计算上要昂贵得多。
  2. 虽然碰撞检测更准确,但碰撞分辨率只是非常初级。它不如物理引擎默认的效果。
  3. 在当前版本中,总是事后检测到碰撞。这就是为什么在记录碰撞时您可能会看到射弹已经穿过对象。我想在不久的将来解决这个问题。
  4. 如果你在子弹或其他形式的射弹等物体上使用它,在第一次击中后基本上停止工作,你可以设置momentumTransferFraction为 1 让子弹物理推动物体(通过将所有动量应用到第一个击中的物体上)而不让子弹被击中影响了自己。
  5. 出于某种原因,您不能仅针对一个对象禁用默认碰撞检测。这意味着,如果您非常(不)幸运,并且碰巧通过 Unity 的默认碰撞检查记录了碰撞,您可能会OnTriggerEnter2D在同一个对象上多次开火,或者(如果 collider 不是触发器)在命中时施加力目标(除了这个脚本施加的目标)。但是,由于这会有些随机且非常不一致,因此除了打开IsTrigger在您的射弹对撞机上,我建议使用自定义消息名称来处理射弹冲击。这样,默认碰撞检测随机检测到的碰撞不会有任何意外的副作用 [请记住,默认碰撞检测与这些类型的对象不一致是添加此脚本的实际原因]。仅供参考:从 Unity 5 开始,防止默认碰撞检测的唯一两种方法是IgnoreCollisionIgnoreLayerCollision

代码

using UnityEngine;
using System.Collections;
using System.Linq;


/// <summary>
/// 2D adaption of the famous DontGoThroughThings component (http://wiki.unity3d.com/index.php?title=DontGoThroughThings).
/// Uses raycasting to trigger OnTriggerEnter2D events when hitting something.
/// </summary>
/// <see cref="http://stackoverflow.com/a/29564394/2228771"/>
public class ProjectileCollisionTrigger2D : MonoBehaviour {
    public enum TriggerTarget {
        None = 0,
        Self = 1,
        Other = 2,
        Both = 3
    }

    /// <summary>
    /// The layers that can be hit by this object.
    /// Defaults to "Everything" (-1).
    /// </summary>
    public LayerMask hitLayers = -1;

    /// <summary>
    /// The name of the message to be sent on hit.
    /// You generally want to change this, especially if you want to let the projectile apply a force (`momentumTransferFraction` greater 0).
    /// If you do not change this, the physics engine (when it happens to pick up the collision) 
    /// will send an extra message, prior to this component being able to. This might cause errors or unexpected behavior.
    /// </summary>
    public string MessageName = "OnTriggerEnter2D";

    /// <summary>
    /// Where to send the hit event message to.
    /// </summary>
    public TriggerTarget triggerTarget = TriggerTarget.Both;

    /// <summary>
    /// How much of momentum is transfered upon impact.
    /// If set to 0, no force is applied.
    /// If set to 1, the entire momentum of this object is transfered upon the first collider and this object stops dead.
    /// If set to anything in between, this object will lose some velocity and transfer the corresponding momentum onto every collided object.
    /// </summary>
    public float momentumTransferFraction = 0;

    private float minimumExtent;
    private float sqrMinimumExtent;
    private Vector2 previousPosition;
    private Rigidbody2D myRigidbody;
    private Collider2D myCollider;


    //initialize values 
    void Awake()
    {
        myRigidbody = GetComponent<Rigidbody2D>();
        myCollider = GetComponents<Collider2D> ().FirstOrDefault();
        if (myCollider == null || myRigidbody == null) {
            Debug.LogError("ProjectileCollisionTrigger2D is missing Collider2D or Rigidbody2D component", this);
            enabled = false;
            return;
        }

        previousPosition = myRigidbody.transform.position;
        minimumExtent = Mathf.Min(myCollider.bounds.extents.x, myCollider.bounds.extents.y);
        sqrMinimumExtent = minimumExtent * minimumExtent;
    }

    void FixedUpdate()
    {
        //have we moved more than our minimum extent? 
        var origPosition = transform.position;
        Vector2 movementThisStep = (Vector2)transform.position - previousPosition;
        float movementSqrMagnitude = movementThisStep.sqrMagnitude;

        if (movementSqrMagnitude > sqrMinimumExtent) {
            float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);

            //check for obstructions we might have missed 
            RaycastHit2D[] hitsInfo = Physics2D.RaycastAll(previousPosition, movementThisStep, movementMagnitude, hitLayers.value);

            //Going backward because we want to look at the first collisions first. Because we want to destroy the once that are closer to previous position
            for (int i = 0; i < hitsInfo.Length; ++i) {
                var hitInfo = hitsInfo[i];
                if (hitInfo && hitInfo.collider != myCollider) {
                    // apply force
                    if (hitInfo.rigidbody && momentumTransferFraction != 0) {
                        // When using impulse mode, the force argument is actually the amount of instantaneous momentum transfered.
                        // Quick physics refresher: F = dp / dt = m * dv / dt
                        // Note: dt is the amount of time traveled (which is the time of the current frame and is taken care of internally, when using impulse mode)
                        // For more info, go here: http://forum.unity3d.com/threads/rigidbody2d-forcemode-impulse.213397/
                        var dv = myRigidbody.velocity;
                        var m = myRigidbody.mass;
                        var dp = dv * m;
                        var impulse = momentumTransferFraction * dp;
                        hitInfo.rigidbody.AddForceAtPosition(impulse, hitInfo.point, ForceMode2D.Impulse);

                        if (momentumTransferFraction < 1) {
                            // also apply force to self (in opposite direction)
                            var impulse2 = (1-momentumTransferFraction) * dp;
                            hitInfo.rigidbody.AddForceAtPosition(-impulse2, hitInfo.point, ForceMode2D.Impulse);
                        }
                    }

                    // move this object to point of collision
                    transform.position = hitInfo.point;

                    // send hit messages
                    if (((int)triggerTarget & (int)TriggerTarget.Other) != 0 && hitInfo.collider.isTrigger) {
                        hitInfo.collider.SendMessage(MessageName, myCollider, SendMessageOptions.DontRequireReceiver);
                    }
                    if (((int)triggerTarget & (int)TriggerTarget.Self) != 0) {
                        SendMessage(MessageName, hitInfo.collider, SendMessageOptions.DontRequireReceiver);
                    }
                }
            }
        }

        previousPosition = transform.position = origPosition;
    }
}
于 2016-02-09T17:22:41.293 回答
0

这是我重写的这个脚本的 2D 版本(对于 Unity 4.6):

using UnityEngine;
using System.Collections;

public class DontGoThroughThings : MonoBehaviour
{
    public delegate void CollidedDelegate(Collider2D collider);
    public event CollidedDelegate Collided;

    public LayerMask layerMask; //make sure we aren't in this layer 
    public float skinWidth = 0.1f; //probably doesn't need to be changed 

    private float minimumExtent;
    private float partialExtent;
    private float sqrMinimumExtent;
    private Vector2 previousPosition;
    private Rigidbody2D myRigidbody;



    //initialize values 
    void Awake()
    {
        myRigidbody = rigidbody2D;
        previousPosition = myRigidbody.transform.position;
        minimumExtent = Mathf.Min(BoundsOf(collider2D).extents.x, BoundsOf(collider2D).extents.y);
        partialExtent = minimumExtent * (1.0f - skinWidth);
        sqrMinimumExtent = minimumExtent * minimumExtent;
    }

    void FixedUpdate()
    {
        //have we moved more than our minimum extent? 
        Vector2 movementThisStep = (Vector2)myRigidbody.transform.position - previousPosition;
        float movementSqrMagnitude = movementThisStep.sqrMagnitude;

        if (movementSqrMagnitude > sqrMinimumExtent)
        {
            float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);

            //check for obstructions we might have missed 
            RaycastHit2D[] hitsInfo = Physics2D.RaycastAll(previousPosition, movementThisStep, movementMagnitude, layerMask.value);

            //Going backward because we want to look at the first collisions first. Because we want to destroy the once that are closer to previous position
            for (int i = hitsInfo.Length-1; i >= 0; i--)
            {
                var hitInfo = hitsInfo[i];
                if (hitInfo && hitInfo.rigidbody != rigidbody2D)
                {
                    if (Collided != null)
                    {
                        Collided(hitInfo.collider);
                    }
                }
            }
        }

        previousPosition = myRigidbody.transform.position;
    }

    // compute bounds in local space
    public static Bounds BoundsOf(Collider2D collider) {
        var bounds = new Bounds();

        var bc = collider as BoxCollider2D;
        if (bc) {
            var ext = bc.size * 0.5f;
            bounds.Encapsulate(new Vector3(-ext.x, -ext.y, 0f));
            bounds.Encapsulate(new Vector3(ext.x, ext.y, 0f));
            return bounds;
        }

        var cc = collider as CircleCollider2D;
        if (cc) {
            var r = cc.radius;
            bounds.Encapsulate(new Vector3(-r, -r, 0f));
            bounds.Encapsulate(new Vector3(r, r, 0f));
            return bounds;
        }


        // others :P
        //Debug.LogWarning("Unknown type "+bounds);

        return bounds;
    }

    // return bounds in world space
    public static Bounds BoundsColliders(GameObject obj) {
        var bounds = new Bounds(obj.transform.position, Vector3.zero);

        var colliders = obj.GetComponentsInChildren<Collider2D>();
        foreach(var c in colliders) {
            var blocal = BoundsOf(c);
            var t = c.transform;
            var max = t.TransformPoint(blocal.max);
            bounds.Encapsulate(max);
            var min = t.TransformPoint(blocal.min);
            bounds.Encapsulate(min);
        }

        return bounds;
    }


}

请让我知道它是否适合您。

谢谢,李丹

于 2015-04-10T14:46:31.543 回答