需求
通过设置参数来生成一个弧形“平台”,玩家角色可在其上移动(平台需有物理体)
方案
构建平台外观(画出平台)
使用LineRenderer
构建2D弧形“平台”
使用EdgeCollider2D
具体步骤
参数
弧半径
起点相对于原点的角度
弧角度
弧宽度
弧外观的前后显示顺序(通过设置其z坐标)
构成弧的片段的数量
算得构成圆弧的各点的位置
点的数量 = 片段数量 + 1
因期望起点是从右边中间开始,且开始是向上画弧,因此使用如下式子计算各点的x和y值
float angle = startAngle - 90;
for (int i = 0; i < pointAmount; i++)
{
currentPointPos.x = Mathf.Sin(Mathf.Deg2Rad * angle) * radius;
currentPointPos.y = Mathf.Cos(Mathf.Deg2Rad * angle) * radius;
posArray[i] = currentPointPos;
angle += angleSum / segmentAmount;
}
画弧
为了保证画出的弧是圆滑的,因此可使用如下式子,根据弧度、弧宽度、半径,算得合适的片段数量(此处认为片段的长度为弧宽度)
// 片段数量 = 弧长/片段长度, 片段长度=弧宽度
float arcLength = Mathf.Deg2Rad * angleSum * radius;
int amount =(int)(arcLength / width);
根据各点的位置,将其设置为LineRenderer的点和碰撞体的点即可,在设置时还需设置弧宽度
EdgeCollider2D的边半径(edgeRadius)应为弧宽度的1/2
// 获取点
Vector2[] posArray = GetPointsPos(startAngle, angleSum, radius, PointAmount);
lineRenderer.positionCount = PointAmount;
lineRenderer.startWidth = width;
lineRenderer.endWidth = width;
// 画弧(外观)
for (int i = 0; i < posArray.Length; i++)
{
lineRenderer.SetPosition(i, new Vector3(posArray[i].x, posArray[i].y, zValueOfLine));
}
// 重建碰撞体
edgeCollider.edgeRadius = width/2;
// 至少要有2个点才能构成一条边
if (posArray.Length > 1)
edgeCollider.points = posArray;
其他细节
实测发现设置弧的角度为360时,画出的圆有接缝,因此当要画圆时,可将角度设置为360+一个值(弧度越宽,这个值需越大)
为了实时看到修改结果,可在OnValidate()中调用画弧方法
OnValidate()被调用的时机:官方文档中对OnValidate()的描述
- 所属的脚本被载入
- 脚本的某些值在Inspector窗口中被修改时
完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// 画圆弧测试
///
[RequireComponent(typeof(LineRenderer))]
[RequireComponent(typeof(EdgeCollider2D))]
public class MyTest : MonoBehaviour
{
// 半径
public float radius = 5f;
// 起点角度
public float startAngle = 0f;
// 圆弧的角度
[Range(0, 365)]
public float angleSum = 270f;
// 圆弧的宽度
[Range(0,10)]
public float width = 0.5f;
// 外观的前后显示顺序(通过设置其z坐标)
public float zValueOfLine = 0f;
// 自动设置片段数量
public bool isAutoSetSegmentAmount = true;
// 片段数量
public int segmentAmount = 100;
private int PointAmount { get { return segmentAmount + 1; } }
private LineRenderer lineRenderer;
private EdgeCollider2D edgeCollider;
private bool isInited;
// 载入 和 上述某值发生变化时调用
void OnValidate()
{
ReInit();
}
void ReInit()
{
isInited = false;
Init();
}
void Init()
{
if (isInited)
return;
if (lineRenderer == null)
{
lineRenderer = GetComponent();
// 圆弧可以拖动
lineRenderer.useWorldSpace = false;
}
if (edgeCollider == null)
edgeCollider = GetComponent();
// 根据数据画圆
DrawArc();
isInited = true;
}
// 根据起始角度、弧度、弧半径、构成弧的各点的数量,算得各点的位置
Vector2[] GetPointsPos(float startAngle, float angleSum, float radius, int pointAmount)
{
Vector2[] posArray = new Vector2[pointAmount];
Vector2 currentPointPos = Vector2.zero;
// 计算起点的角度(-90表示从右边中间为起点开始画弧)
float angle = startAngle - 90;
// float angle = startAngle;
for (int i = 0; i < pointAmount; i++)
{
currentPointPos.x = Mathf.Sin(Mathf.Deg2Rad * angle) * radius;
currentPointPos.y = Mathf.Cos(Mathf.Deg2Rad * angle) * radius;
// currentPointPos.x = Mathf.Cos(Mathf.Deg2Rad * angle) * radius;
// currentPointPos.y = Mathf.Sin(Mathf.Deg2Rad * angle) * radius;
posArray[i] = currentPointPos;
angle += angleSum / segmentAmount;
}
return posArray;
}
// 根据弧度、弧线粗细、弧半径,计算片段的数量
int CalcSegmentAmount(float angleSum, float width, float radius)
{
// 片段数量 = 弧长/片段长度, 片段长度=弧宽度
float arcLength = Mathf.Deg2Rad * angleSum * radius;
int amount =(int)(arcLength / width);
return amount;
}
// 用linerenderer画弧,用EdgeCollider2D构建碰撞体
void DrawArc()
{
if (isAutoSetSegmentAmount)
segmentAmount = CalcSegmentAmount(angleSum, width, radius);
// 获取点
Vector2[] posArray = GetPointsPos(startAngle, angleSum, radius, PointAmount);
lineRenderer.positionCount = PointAmount;
lineRenderer.startWidth = width;
lineRenderer.endWidth = width;
// 画弧(外观)
for (int i = 0; i < posArray.Length; i++)
{
lineRenderer.SetPosition(i, new Vector3(posArray[i].x, posArray[i].y, zValueOfLine));
}
// 重建碰撞体
edgeCollider.edgeRadius = width/2;
// 至少要有2个点才能构成一条边
if (posArray.Length > 1)
edgeCollider.points = posArray;
}
}
下一步
感觉做个类似《超级马里奥银河》的2D游戏也不错,下一步需考虑准确设置角色受到的重力(吸引力)方向,并据此调整其朝向
初步的设想是从角色脚下向下发射射线,将角色重力朝向设置为射线碰触到的“地面”的法线的反方向