2D-игра с быстро движущимися объектами (Unity3d)

Я нашел официальное обучение Unity 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; 
}

Это пакет unitypackage https://www.dropbox.com/s/a3n1dalbc1k0k42/Hat%20Trick.unitypackage?dl=0

P.S. Извините за мой английский и спасибо за помощь!!


person ud3p    schedule 10.04.2015    source источник
comment
Извините, но мне неясно, в чем именно проблема. Не могли бы вы уточнить?   -  person Bart    schedule 10.04.2015
comment
Внизу шляпы находится триггерный коллайдер. Срабатывает, когда в него падают шары для боулинга. Когда шар для боулинга находится на том же уровне, что и шляпа, и я медленно перемещаю шляпу, триггер не срабатывает. А вот если двигать шляпу быстро (как на видео), то работает... Я думаю, это из-за того, что Unity не успевает отрендерить коллайдерную шляпу. Шляпа словно телепортируется и срабатывает. И я не знаю, как это исправить.   -  person ud3p    schedule 10.04.2015


Ответы (2)


Пояснение

Непрерывное обнаружение столкновений в Unity не использует raycasting. В результате, очень быстро движущиеся (и/или сравнительно небольшие) объекты (назовем пока такие объекты projectile) по-прежнему проходят сквозь предметы без обнаружения столкновения. Известный компонент DontGoThroughThings исправляет это для 3D. Есть несколько несоответствий, но это может выполнить работу, если вы знаете, что делаете.

Вот моя 2D-адаптация.

Я добавил некоторые функции, чтобы сделать его более удобным для всех, кто не очень хорошо разбирается в программировании или игровой физике.

Как это использовать

  1. Добавьте этот компонент к своим быстро движущимся объектам, и они всегда будут вызывать событие OnTriggerEnter2D, когда бить что-то.
  2. Вместо этого вы также можете отправить другое пользовательское сообщение (изменив переменную MessageName). Я действительно рекомендую это из-за предостережения, описанного ниже.
  3. Отправка сообщений является основным вариантом использования скрипта. Это не волшебным образом заставит снаряд вести себя правильно в физическом смысле.
  4. Переменная triggerTarget определяет, отправляет ли он сообщение себе (в случае, если у вас есть скрипт обработки попадания, прикрепленный к снаряду), объекту, в который попадает (в случае, если у вас есть обработка попадания, прикрепленная к объектам, которые должны быть поражены снарядом). снаряды) или любую их вариацию.
  5. В отличие от исходной версии, этот сценарий также позволяет применять силу при ударе, что можно настроить с помощью переменной momentumTransferFraction. Когда два объекта сталкиваются, генерируемая сила является результатом передачи количества движения (массы, умноженной на скорость) между двумя объектами. То, как я это делаю, очень примитивно, и в нем отсутствует множество факторов, но этого достаточно, чтобы снаряды толкали объекты при ударе. Как и в реальном мире, чем быстрее или тяжелее ваш снаряд, тем больше сила прилагается.

Есть также некоторые предостережения (большинство из которых относится и к исходной версии)

  1. Применяйте это только к очень быстро движущимся объектам. Чем меньше вы его используете, тем лучше, потому что это немного более затратно в вычислительном отношении, чем обычное обнаружение столкновений.
  2. В то время как обнаружение столкновений является более точным, разрешение столкновений является очень рудиментарным. Это не так хорошо, как то, что делает физический движок по умолчанию.
  3. В текущей версии коллизии всегда обнаруживаются задним числом. Вот почему вы можете увидеть снаряды, прошедшие через объект к моменту регистрации столкновения. Хочу исправить это в ближайшее время.
  4. Если вы используете это на таких объектах, как пули или другие формы снарядов, которые в основном перестают работать после первого попадания, вы можете установить momentumTransferFraction в 1, чтобы позволить пуле физически толкать объект (прикладывая весь свой импульс к первому пораженному объекту) без пули. подвергается воздействию самого себя.
  5. По какой-то причине вы не можете отключить обнаружение столкновений по умолчанию только для одного объекта. Это означает, что если вам так (не)повезло, и столкновение было зарегистрировано проверками столкновений Unity по умолчанию, вы могли бы OnTriggerEnter2D выстрелить несколько раз по одному и тому же объекту или (если коллайдер не является триггером) приложить силу к поражать цели (в дополнение к той, что вызывается этим скриптом). Однако, поскольку это было бы несколько случайным и очень непоследовательным, в дополнение к включению IsTrigger на коллайдере вашего снаряда, я рекомендую использовать собственное имя сообщения для обработки ударов снарядов. Таким образом, столкновения, которые обнаруживаются случайным образом при обнаружении столкновений по умолчанию, не будут иметь каких-либо непреднамеренных побочных эффектов [помните, что обнаружение столкновений по умолчанию, непоследовательное для этих типов объектов, является фактической причиной, по которой вы добавляете этот скрипт]. К вашему сведению: начиная с Unity 5 единственными двумя способами предотвращения обнаружения столкновений по умолчанию являются IgnoreCollision и ИгнорироватьLayerCollision.

Код

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;
    }
}
person Domi    schedule 09.02.2016

Вот 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;
    }


}

Пожалуйста, дайте мне знать, если это работает для вас.

Спасибо, Лидан

person Lidan    schedule 10.04.2015
comment
Почему это работает? Что это меняет в отношении кода OP? В чем была ошибка? Возможно, вы захотите немного расширить свой ответ, а не просто публиковать код. - person Bart; 10.04.2015
comment
Оба этих класса были перенесены из DontGoThroughThings. между ними слишком много различий, и я не знаю, почему сценарий сценариста не работает. Я знаю, что очень много работал, чтобы правильно перенести его (около года назад). Все, что я знаю, это то, что мой решил ту же проблему для меня, и я надеюсь, что это поможет ему. - person Lidan; 10.04.2015
comment
Может я что-то не так делаю? Добавляю этот скрипт для шапки. Выберите Layer Mask для всего, кроме шляпы, и игнорируйте raycast. Я установил непрерывное обнаружение столкновений. Я пробую различные Min Penetration для пенальти. Используйте этот скрипт в Unity 5 и Unity 4.6.3. - person ud3p; 10.04.2015