Проект сделан в рамках технического задания.
Задача - сделать 2D прототип, где персонаж может перемещаться вокруг прямоугольной платформы, плавно огибая углы. Грань платформы всегда является "землей" для персонажа и он не может с нее упасть. Также персонаж должен уметь прыгать.
Демонстрация:
showcase.mp4
Для реализации перемещения были выбраны Unity Splines. Замкнутый сплайн используется как путь, по которому перемещается персонаж.
Точки генерируются по часовой стрелке:
Реализация
{
if ( cornerPoints < 2 ) throw new System . ArgumentOutOfRangeException ( nameof ( cornerPoints ) ) ;
int pointsCount = Arcs * cornerPoints ;
Vector2 [ ] points = new Vector2 [ pointsCount ] ;
float hx = size . x * 0.5f ;
float hy = size . y * 0.5f ;
// Base corners (no offset)
Vector2 tl = position + new Vector2 ( - hx , hy ) ;
Vector2 tr = position + new Vector2 ( hx , hy ) ;
Vector2 br = position + new Vector2 ( hx , - hy ) ;
Vector2 bl = position + new Vector2 ( - hx , - hy ) ;
float step = 1f / ( cornerPoints - 1 ) ;
// Top-right (π/2 - 0)
for ( int i = 0 ; i < cornerPoints ; i ++ )
{
float angle = Mathf . Lerp ( Mathf . PI / 2f , 0f , step * i ) ;
points [ i ] = tr + new Vector2 (
cornerRadius * Mathf . Cos ( angle ) ,
cornerRadius * Mathf . Sin ( angle )
) ;
}
// Bottom-right (0 - −π/2)
for ( int i = 0 ; i < cornerPoints ; i ++ )
{
float angle = Mathf . Lerp ( 0f , - Mathf . PI / 2f , step * i ) ;
points [ cornerPoints + i ] = br + new Vector2 (
cornerRadius * Mathf . Cos ( angle ) ,
cornerRadius * Mathf . Sin ( angle )
) ;
}
// Bottom-left (−π/2 - −π)
for ( int i = 0 ; i < cornerPoints ; i ++ )
{
float angle = Mathf . Lerp ( - Mathf . PI / 2f , - Mathf . PI , step * i ) ;
points [ cornerPoints * 2 + i ] = bl + new Vector2 (
cornerRadius * Mathf . Cos ( angle ) ,
cornerRadius * Mathf . Sin ( angle )
) ;
}
// Top-left (π - π/2)
for ( int i = 0 ; i < cornerPoints ; i ++ )
{
float angle = Mathf . Lerp ( Mathf . PI , Mathf . PI / 2f , step * i ) ;
points [ cornerPoints * 3 + i ] = tl + new Vector2 (
cornerRadius * Mathf . Cos ( angle ) ,
cornerRadius * Mathf . Sin ( angle )
) ;
}
return points ;
}
Для удобства интерполяции в начало добавляется новая точка, которая будет центром пути (t=0)
Реализация
public OrbPath ( Rect rect , PathSettings settings )
{
float cornerRadius = settings . CornerRadius ;
int cornerPoints = settings . CornerPoints ;
if ( cornerRadius < 0 ) throw new System . ArgumentOutOfRangeException ( nameof ( cornerRadius ) ) ;
if ( cornerPoints < 2 ) throw new System . ArgumentOutOfRangeException ( nameof ( cornerPoints ) ) ;
Vector2 position = rect . position ;
Vector2 size = rect . size ;
Vector2 [ ] points = RoundedRectUtility . GetRoundRectPoints ( position , size , cornerRadius , cornerPoints ) ;
_spline = new Spline ( points . Length + 1 , closed : true ) ;
// Extra point that will become 0t of a spline for easy evaluation
Vector2 startPoint = position + new Vector2 ( 0f , size . y * 0.5f + settings . CornerRadius ) ;
AddPoint ( startPoint ) ;
foreach ( Vector2 point in points )
{
AddPoint ( point ) ;
}
_perimeter = _spline . GetLength ( ) ;
}
В результате получается сплайн, выглядящий следующим образом:
Использование Splines позволяет реализовывать дополнительные настройки, а также работать со встроенным редактором.
Для данной задачи это одно из множества подходящих решений и, наверное, одно из самых удобных в движке Unity.
Более оптимизированым решением будет использование простой геометрии:
Определить, к какому сегменту относится t или линейная позиция - к грани или дуге.
Рассчитать точку на грани или дуге соответственно.