【问题标题】:Fast moving objects 2D game (Unity3d)快速移动物体2D游戏(Unity3d)
【发布时间】:2015-04-10 12:58:50
【问题描述】:

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

我花了大约 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

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

【问题讨论】:

  • 很抱歉,但我不清楚确切的问题是什么。你能澄清一下吗?
  • 帽子的底部是触发边缘对撞机。当保龄球落入其中时,它会起作用。当保龄球与帽子处于同一水平时,我缓慢移动帽子,触发器不起作用。但是如果我快速移动帽子(就像在视频上一样),那么它就可以工作......我认为这是因为 Unity 没有时间渲染对撞机帽子。帽子喜欢传送并被触发。而且我不知道如何解决它。

标签: performance unity3d collision-detection rigid-bodies


【解决方案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;
    }
}

【讨论】:

    【解决方案2】:

    这是我重写的这个脚本的 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;
        }
    
    
    }
    

    如果它适合你,请告诉我。

    谢谢, 李丹

    【讨论】:

    • 为什么会这样? OP 的代码有什么变化?错误是什么?您可能想要扩展您的答案,而不仅仅是发布代码。
    • 这两个类都是从 DontGoThroughThings 迁移而来的。它们之间有太多不同之处,我不知道为什么作者的脚本不起作用。我知道我非常努力地工作以正确迁移它(大约一年前)。我所知道的是我的解决了完全相同的问题,我希望它会帮助他。
    • 也许我做错了什么?我为“帽子”添加了这个脚本。 Сhose Layer 遮盖除“hat”和“ignore raycast”之外的所有内容。我将碰撞检测设置为“连续”。我尝试不同的最小穿透来进行惩罚。在 unity 5 和 unity 4.6.3 中使用此脚本
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-27
    相关资源
    最近更新 更多