在上一部分,我们已经可以将物品传送至正确的位置。
【Unity】关于《传送门》复刻的学习,物品传送-CSDN博客https://blog.csdn.net/weixin_44058587/article/details/138785358?spm=1001.2014.3001.5502在这一部分,我们将实现将传送门发送至墙面。
创建测试地图
首先,向大家推荐ProBuilder插件,该插件可以简单进行建模操作,搭建简易的测试地图。通过该插件创建自己的测试场景。
还没有接触过ProBuilder插件的同学,推荐阅读下面这篇文章后,搭建独属于自己的测试场景。
Portal脚本添加功能
为传送门分别添加outline进行区分,这里简单使用RenderQueue完成outline,相关shader如下。
Shader "Portals/Outline"
{
Properties
{
_OutlineColour("Outline Colour", Color) = (1, 1, 1, 1)
_MaskID("Mask ID", Int) = 1
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"Queue" = "Geometry+2"
}
Stencil
{
Ref 0
Comp equal
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
uniform float4 _OutlineColour;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _OutlineColour;
}
ENDCG
}
}
}
为传送门添加Outline效果,如下。
接着为Portal脚本添加关于Outline设置的相关代码,初始化传送门的颜色。
public class Portal : MonoBehaviour
{
//****省略上文Portal字段
[SerializeField,Header("Outline设置")]
private Renderer outlineRenderer;
[SerializeField]
private Color portalColor;
private void Start()
{
PlacePortal(wallCollider, transform.position, transform.rotation);
SetColour(portalColor);
}
//****省略上文Portal其他方法
public void SetColour(Color color)
{
material.SetColor("_Colour", color);
outlineRenderer.material.SetColor("_OutlineColour", color);
}
}
在Portal脚本中添加传送门放置方法。
public void PlacePortal(Collider wallCollider, Vector3 pos, Quaternion rot)
{
this.wallCollider = wallCollider;
transform.position = pos;
transform.rotation = rot;
transform.position -= transform.forward * 0.001f;
}
创建PortalPlacement脚本
新建PortalPlacement脚本,完成放置传送门位置和角度的计算。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CameraMove))]
public class PortalPlacement : MonoBehaviour
{
[SerializeField]
private PortalPair portals;
[SerializeField]
private LayerMask layerMask;
private CameraMove cameraMove;
private void Awake()
{
cameraMove = GetComponent<CameraMove>();
}
private void Update()
{
if(Input.GetButtonDown("Fire1"))
{
FirePortal(0, transform.position, transform.forward, 250.0f);
}
else if (Input.GetButtonDown("Fire2"))
{
FirePortal(1, transform.position, transform.forward, 250.0f);
}
}
private void FirePortal(int portalID, Vector3 pos, Vector3 dir, float distance)
{
RaycastHit hit;
Physics.Raycast(pos, dir, out hit, distance, layerMask);
if(hit.collider != null)
{
if (hit.collider.tag == "Portal")
{
var inPortal = hit.collider.GetComponent<Portal>();
if(inPortal == null)
{
return;
}
var outPortal = inPortal.GetOtherPortal();
Vector3 relativePos = inPortal.transform.InverseTransformPoint(hit.point + dir);
relativePos = Quaternion.Euler(0.0f, 180.0f, 0.0f) * relativePos;
pos = outPortal.transform.TransformPoint(relativePos);
Vector3 relativeDir = inPortal.transform.InverseTransformDirection(dir);
relativeDir = Quaternion.Euler(0.0f, 180.0f, 0.0f) * relativeDir;
dir = outPortal.transform.TransformDirection(relativeDir);
distance -= Vector3.Distance(pos, hit.point);
FirePortal(portalID, pos, dir, distance);
return;
}
var cameraRotation = cameraMove.TargetRotation;
var portalRight = cameraRotation * Vector3.right;
if(Mathf.Abs(portalRight.x) >= Mathf.Abs(portalRight.z))
{
portalRight = (portalRight.x >= 0) ? Vector3.right : -Vector3.right;
}
else
{
portalRight = (portalRight.z >= 0) ? Vector3.forward : -Vector3.forward;
}
var portalForward = -hit.normal;
var portalUp = -Vector3.Cross(portalRight, portalForward);
var portalRotation = Quaternion.LookRotation(portalForward, portalUp);
portals.Portals[portalID].PlacePortal(hit.collider, hit.point, portalRotation);
}
}
}
创建PortalPair脚本,便于管理传送门,并确保拥有两个传送门。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PortalPair : MonoBehaviour
{
public Portal[] Portals { private set; get; }
private void Awake()
{
Portals = GetComponentsInChildren<Portal>();
if(Portals.Length != 2)
{
Debug.LogError("PortalPair children must contain exactly two Portal components in total.");
}
}
}
脚本设置
添加Layer用于射线检测。
PortalPair脚本,应创建空物体,并使两个传送门为其子物体。
最终效果
目前完成传送门的简单放置,放置传送门穿模问题将在后文解决。