-
-
Notifications
You must be signed in to change notification settings - Fork 66
Add WaypointPath trait #865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
private def waypointsWithRadius( | ||
waypoints: List[Vector2], | ||
radius: Double, | ||
loop: Boolean | ||
): List[Vector2] = | ||
val list = | ||
if loop then | ||
waypoints | ||
.appended(waypoints.head) | ||
else waypoints | ||
|
||
val distances = list | ||
.foldLeft(List.empty[Vector2]): | ||
case (acc, v2) => | ||
acc.lastOption match | ||
case Some(v1) => | ||
val fullDistance = v1.distanceTo(v2) | ||
val cappedDistance = (fullDistance - radius).max(0.0) | ||
val newV2 = lerpVector2(v1, v2, cappedDistance / fullDistance) | ||
acc :+ newV2 | ||
case None => List(v2) | ||
if loop then distances.tail.dropRight(1).prepended(distances.last) | ||
else distances |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how useful this is. The idea is that if the waypoints aren't precise locations but general positions and when a character with a large sprite gets close enough to it, it considers it visited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how this feels in practice, but generally I think that's a very sensible approach. Waiting for things to land perfectly something is a headache and usually not worth the hassle!
Might be worth making this configurable though? How close is close enough? You have a few 'config' options already, if you add any more, consider creating a config object:
WaypointPathConfig(pathRadius: Radians, loop: Boolean, requiredProximity: Vector2)
object WaypointPathConfig:
val initial: WaypointPathConfig = WaypointPathConfig(Radians.zero, false, Vector2.one)
requiredProximity
is probably a poor name, it could also be a function that lets the user supply a proximity test - but that's proably overkill for the first cut.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an example of it in the sandbox that hopefully illustrates how it works.
@davesmith00000 not sure this is what you had in mind but added a sandbox example of it working with timelines. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's great! 🔥
I've peppered it with comments that are mostly about bringing it inline with the rest of Indigo's style. Up to you if you feel like doing the work, I can always merge and refine afterwards.
Either way, the effort is much appreciated. 🙏
import scala.annotation.tailrec | ||
|
||
trait WaypointPath: | ||
def waypoints: List[Vector2] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I shall forever wonder if this is the right thing to have done, but for consistency, Vector2
should probably be Vertex
. They're almost identical, but a vertex is a position on a graph / space where vector2 is an expression of direction. LineSegment
uses Vertex
, I am open to counter arguments. 😄
final case class LineSegment(start: Vertex, end: Vertex) derives CanEqual: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, can we replace List
with Batch
? Batch
is much faster and used everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you mention this because I just realised yesterday that there is both Vertex
and Vector2
and I wasn't sure what was the difference 😅 I'll change both.
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
private def waypointsWithRadius( | ||
waypoints: List[Vector2], | ||
radius: Double, | ||
loop: Boolean | ||
): List[Vector2] = | ||
val list = | ||
if loop then | ||
waypoints | ||
.appended(waypoints.head) | ||
else waypoints | ||
|
||
val distances = list | ||
.foldLeft(List.empty[Vector2]): | ||
case (acc, v2) => | ||
acc.lastOption match | ||
case Some(v1) => | ||
val fullDistance = v1.distanceTo(v2) | ||
val cappedDistance = (fullDistance - radius).max(0.0) | ||
val newV2 = lerpVector2(v1, v2, cappedDistance / fullDistance) | ||
acc :+ newV2 | ||
case None => List(v2) | ||
if loop then distances.tail.dropRight(1).prepended(distances.last) | ||
else distances |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how this feels in practice, but generally I think that's a very sensible approach. Waiting for things to land perfectly something is a headache and usually not worth the hassle!
Might be worth making this configurable though? How close is close enough? You have a few 'config' options already, if you add any more, consider creating a config object:
WaypointPathConfig(pathRadius: Radians, loop: Boolean, requiredProximity: Vector2)
object WaypointPathConfig:
val initial: WaypointPathConfig = WaypointPathConfig(Radians.zero, false, Vector2.one)
requiredProximity
is probably a poor name, it could also be a function that lets the user supply a proximity test - but that's proably overkill for the first cut.
indigo/indigo-extras/src/main/scala/indigoextras/waypoints/WaypointPath.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. I've reviewed the demo - thanks for doing that. 🙏
Issue #804
Simply create a path like:
and in either the updateModel or present methods one can get the expected position with:
where
at
is the relative position in the path. So 0.0 is the beginning of the path and 1.0 is the end. If looping is enabled values above 1.0 wrap around and values lower than 0.0 also wrap around but in reverse.It can be easily integrated in timelines like: