【Unity】关于《传送门》复刻的学习,物品传送

在上一部分,我们已经可以在场景中正确渲染传送门中的图像(暂未实现传送门内图像的递归渲染)。【Unity】关于《传送门》复刻的学习,简单渲染-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_44058587/article/details/138604309?spm=1001.2014.3001.5501

在这一部分,我们将实现进入传送门内物品的传送。

关于物品的传送

首先,使用碰撞检测判断进入传送门内的物品是否可以被传送。其次,在可被传送物品在进入传送门的同时,应该关闭传送门背后墙体与可传送物品间的碰撞。最后,通过对物品传送后的位置、旋转和运动方向进行计算并赋予物品从而实现效果。

计算物品传送后的位置、旋转和运动方向

物品传送后的位置、旋转和运动方向,简单示意如图。

 将物品进入传送门时的位置、旋转和运动方向旋转180°,就可以计算物品传送后物品所对应的位置、旋转和运动方向。

PortalableObject脚本

现在,创建脚本并命名为PortalableObject,并为脚本添加依赖。可传送的物品一定需要渲染同时要实现物理特性,因此可被传送的物品应该添加与渲染和物理相关的依赖。

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class PortalableObject : MonoBehaviour
{

}

 接着定义rigidbody、collider、进入传送门次数、进入的传送门、离开的传送门和半圈的旋转便于之后的计算。(Portal脚本将在下文解释)

    private int inPortalCount = 0;
    
    private Portal inPortal;
    private Portal outPortal;    

    private new Rigidbody rigidbody;
    protected new Collider collider;
 //定义半圈的旋转
    private static readonly Quaternion halfTurn = Quaternion.Euler(0.0f, 180.0f, 0.0f);
    protected virtual void Awake()
    {
        rigidbody = GetComponent<Rigidbody>();
        collider = GetComponent<Collider>();
    }

编写 SetIsInPortal方法,确定进入、离开的传送门和墙面的碰撞体。

    public void SetIsInPortal(Portal inPortal, Portal outPortal, Collider wallCollider)
    {
        this.inPortal = inPortal;
        this.outPortal = outPortal;
        //忽略物品和墙体的碰撞
        Physics.IgnoreCollision(collider, wallCollider);

        ++inPortalCount;
    }

 编写 ExitPortal方法,恢复墙面和物品间的碰撞。

    public void ExitPortal(Collider wallCollider)
    {
        Physics.IgnoreCollision(collider, wallCollider, false);
        --inPortalCount;
    }

编写Warp方法,计算物品传送后的位置、旋转和运动方向。

    public virtual void Warp()
    {
        var inTransform = inPortal.transform;
        var outTransform = outPortal.transform;

        // 计算物品的Pos
        Vector3 relativePos = inTransform.InverseTransformPoint(transform.position);
        relativePos = halfTurn * relativePos;
        transform.position = outTransform.TransformPoint(relativePos);

        // 计算物品的Rot
        Quaternion relativeRot = Quaternion.Inverse(inTransform.rotation) * transform.rotation;
        relativeRot = halfTurn * relativeRot;
        transform.rotation = outTransform.rotation * relativeRot;

        // 计算物品的运动方向
        Vector3 relativeVel = inTransform.InverseTransformDirection(rigidbody.velocity);
        relativeVel = halfTurn * relativeVel;
        rigidbody.velocity = outTransform.TransformDirection(relativeVel);

        // 替换进出传送门的引用
        var tmp = inPortal;
        inPortal = outPortal;
        outPortal = tmp;
    }

 PortalableObject脚本的编写暂时已经完成。

Portal脚本

创建脚本并命名为Portal,该脚本将通过碰撞检测(Trigger)实现对物品是否可以传送进行判断。首先,为Portal脚本定义所需要的变量和属性。


[RequireComponent(typeof(BoxCollider))]
public class Portal : MonoBehaviour
{
    //另一传送门
    [SerializeField]
    private Portal otherPortal;
    //墙面的碰撞体
    [SerializeField]
    private Collider wallCollider;
    //传送门是否被放置
    [SerializeField]
    private bool isPlaced;
    //存储被传送的物品
    private List<PortalableObject> portalObjects = new List<PortalableObject>();

    private Material material;
    private new Renderer renderer;
    private new BoxCollider collider;

    private void Awake()
    {
        collider = GetComponent<BoxCollider>();
        renderer = GetComponent<Renderer>();
        material = renderer.material;
    }

     public Portal GetOtherPortal()
    {
        return otherPortal;
    }

    public void SetMaskID(int id)
    {
        material.SetInt("_MaskID", id);
    }
    /// <summary>
    /// 当前传送门是否被渲染
    /// </summary>
    /// <returns></returns>
    public bool IsRendererVisible()
    {
        return renderer.isVisible;
    }
}

 上篇文章中对于传送门相关属性的调用也可以改为如上脚本中的属性。想更改的同学就自己修改和添加,我就不过多赘述了。

通过Trigger检测进入传送门的物体。

  private void OnTriggerEnter(Collider other)
    {
        //通过碰撞检测判断进入传送门的物品能否被传送
        var obj = other.GetComponent<PortalableObject>();
        if (obj != null)
        {
            portalObjects.Add(obj);
            obj.SetIsInPortal(this, otherPortal, wallCollider);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        //通过碰撞检测判断离开传送门的物品能否被传送
        var obj = other.GetComponent<PortalableObject>();

        if(portalObjects.Contains(obj))
        {
            portalObjects.Remove(obj);
            obj.ExitPortal(wallCollider);
        }
    }

最后,在update中更新可传送物品的位置、旋转和运动方向。

    private void Update()
    {
        for (int i = 0; i < portalObjects.Count; ++i)
        {
            Vector3 objPos = transform.InverseTransformPoint(portalObjects[i].transform.position);
            //根据物品相对于传送门的Pos,判断物品是否进入传送门
            if (objPos.z > 0.0f)
            {
                //计算物品传送后的各个属性
                portalObjects[i].Warp();
            }
        }
    }

 场景搭建测试

简单调整上一章中已经搭建完成的场景,并新建一个Cube为其添加PortalableObject脚本,为先前的两个传送门添加BoxCollider、Portal脚本,调整传送门BoxCollider的大小并勾选isTrigger。

当前效果如下。

创建复制体

当前已经可以实现物品的传送,但是仔细观察可以发现如下问题。

在物品传送的过程中,因为传送门摄像机近视锥剔除了还未完全离开传送门的物体,所以我们可以在进行传送时创建传送中物体的复制体,从而避免上述的问题出现。

首先,在PortalableObject脚本中添加对应的变量,并修改部分代码。

public class PortalableObject : MonoBehaviour
{
    //***省略前文变量***
    private GameObject cloneObject;
    protected virtual void Awake()
    {
        cloneObject = new GameObject();
        cloneObject.SetActive(false);
        var meshFilter = cloneObject.AddComponent<MeshFilter>();
        var meshRenderer = cloneObject.AddComponent<MeshRenderer>();

        meshFilter.mesh = GetComponent<MeshFilter>().mesh;
        meshRenderer.materials = GetComponent<MeshRenderer>().materials;
        cloneObject.transform.localScale = transform.localScale;

        //***省略前文***
    }
    private void LateUpdate()
    {
        //在LateUpdate更新复制体的位置等属性
        if (inPortal == null || outPortal == null)
        {
            return;
        }

        if (cloneObject.activeSelf && inPortal.IsPlaced() && outPortal.IsPlaced())
        {
            var inTransform = inPortal.transform;
            var outTransform = outPortal.transform;

            Vector3 relativePos = inTransform.InverseTransformPoint(transform.position);
            relativePos = halfTurn * relativePos;
            cloneObject.transform.position = outTransform.TransformPoint(relativePos);

            Quaternion relativeRot = Quaternion.Inverse(inTransform.rotation) * transform.rotation;
            relativeRot = halfTurn * relativeRot;
            cloneObject.transform.rotation = outTransform.rotation * relativeRot;
        }
        else
        {
            cloneObject.transform.position = new Vector3(-1000.0f, 1000.0f, -1000.0f);
        }
    }
    public void SetIsInPortal(Portal inPortal, Portal outPortal, Collider wallCollider)
    {
        //***省略前文***
        cloneObject.SetActive(true);
    }
    
    public void ExitPortal(Collider wallCollider)
    {
        //***省略前文***
        if (inPortalCount == 0)
        {
            cloneObject.SetActive(false);
        }
    }
}

增加复制体后效果。

 后续

还未完成发射传送门、传送门内画面迭代和玩家的传送等功能。

写了这么多点个赞吧,同学。

  • 39
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity 2D中的传送门代码需要分为两部分:传送门进入和传送门出来。 传送门进入: 首先,在场景中放置两个传送门用的“Collider”(例如Box Collider 2D)。 在传送门上,可以通过添加“Tag”(例如“Portal”)和“Layer”来设置传送门。 创建一个脚本(例如“PortalEnter.cs”),并将其附加到玩家控制的对象上。 在该脚本中,可以使用以下代码来检测是否进入了传送门: ``` void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("Portal")) { //传送门的代码 } } ``` 传送门出来: 与传送门进入不同,传送门出来需要在另一个场景中创建另一个传送门并在脚本中设置其传送目的地。 在传送门的目的地,同样需要创建一个脚本(例如“PortalExit.cs”),并将其附加到玩家控制的对象上。 在该脚本中,可以使用以下代码来检测是否从传送门出来: ``` void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("Portal")) { //传送门的代码 } } ``` 传送门的代码: 首先,需要获取场景中的另一个传送门: ``` GameObject exitPortal = GameObject.FindGameObjectWithTag("Portal"); ``` 然后,可以通过以下代码将玩家传送传送门的目的地: ``` player.transform.position = exitPortal.transform.position; ``` 完整代码示例: PortalEnter.cs ``` using UnityEngine; public class PortalEnter : MonoBehaviour { GameObject exitPortal; void Start() { exitPortal = GameObject.FindGameObjectWithTag("Portal"); } void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("Portal")) { Debug.Log("Enter portal!"); PlayerControl player = other.gameObject.GetComponent<PlayerControl>(); player.isTransporting = true; player.transform.position = exitPortal.transform.position; } } } ``` PortalExit.cs ``` using UnityEngine; public class PortalExit : MonoBehaviour { GameObject enterPortal; void Start() { enterPortal = GameObject.FindGameObjectWithTag("Portal"); } void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.CompareTag("Player")) { Debug.Log("Exit portal!"); PlayerControl player = other.gameObject.GetComponent<PlayerControl>(); if (player.isTransporting) { player.isTransporting = false; } } } } ``` 需要注意的是,以上代码只是传送门的简单示例,还需要根据具体情况进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值