ID:2140473
 
(See the best response by Kaiochao.)
Hi everyone! I am really sorry to bother you but I can't figure out how I would do this.

Let's say I have a circle that covers 5x5 tiles. My player is in the middle of the circle. How can I detect if the player has moved out of the circle, but not out of the tile itself? This would all be using pixel movement.

Is this possible? I feel like I am missing something.

Thanks!

EDIT: just to clarify, if the player has also stepped out of the tile that is fine too, but I am trying to detect the precise moment a pixel of the players BOUNDS leaves the circle.
I'm not sure if there's a better way with less comparisons, but one way to check if a rectangle is not completely inside a circle is to compare the circle's radius with the distance from the center of the circle to all the corners of the player. A rectangle can't exit a circle without at least one of its corners being outside.

Tiles have nothing to do with this.
I don't really follow. Are you able to draw a circle directly on the map with code? Because I wanted to use a specific set of tiles that when placed form a circle, because I want it to look different than just a mathematically placed circle.
I guess what I'm asking is how do you define the parameters of the circles location? I'm just relearning programming, sorry if this doesn't make sense.
Best response
Positions, AKA coordinates, can be represented by a pair of numbers; one number for distance along the X-axis (east-west), and another number for the Y-axis (north-south).

Positions on the map are called "world coordinates". Conventionally, the bottom-left pixel of the map is the world coordinate (1, 1). The pixel to the east of (1, 1) is (2, 1), and the pixel to the north of (1, 1) is (1, 2).

There are many ways to get world coordinates. A common way is to convert from movable atom's positional variables: x, y, step_x, step_y, bound_x, and bound_y. (z is left out, as it doesn't inherently affect 2D positioning, but should still be accounted for)

If you have an object as far southwest as possible, its bottom-left corner will be at (1, 1). If it takes a step 32 pixels to the east, its bottom-left corner will end up at (33, 1).

When you "create the circle", all you're really doing is storing the coordinates of the center of the circle, as well as the radius of the circle. So, assuming you have variables for the center position, and radius of the circle, you can check if a given point is inside the circle using Euclidean distance:
// (global proc)
// slight bit of math ahead
proc/is_point_in_circle(TestX, TestY, CircleCenterX, CircleCenterY, CircleRadius)
var
// "d" for difference or delta or displacement
dx = CircleCenterX - TestX
dy = CircleCenterY - TestY
return dx * dx + dy * dy <= CircleRadius * CircleRadius


With my Absolute Positions library, you can get the world coordinates of any atom on the map.
// (where src is some atom)
var
// lower-left corner
lower_x = Px()
lower_y = Py()

// upper-right corner
upper_x = Px(1)
upper_y = Py(1)


To check if an atom is completely inside a circle, you can use those corner coordinates as the test coordinates in the above is_point_in_circle() proc:
// (global proc)
proc/is_rectangle_completely_in_circle(LowerX, LowerY, UpperX, UpperY, CircleCenterX, CircleCenterY, CircleRadius)
return is_point_in_circle(LowerX, LowerY, CircleCenterX, CircleCenterY, CircleRadius) \
&& is_point_in_circle(UpperX, LowerY, CircleCenterX, CircleCenterY, CircleRadius) \
&& is_point_in_circle(UpperX, UpperY, CircleCenterX, CircleCenterY, CircleRadius) \
&& is_point_in_circle(LowerX, UpperY, CircleCenterX, CircleCenterY, CircleRadius)

atom/proc/IsCompletelyInCircle(CircleCenterX, CircleCenterY, CircleRadius)
return is_rectangle_completely_in_circle(Px(), Py(), Px(1), Py(1), CircleCenterX, CircleCenterY, CircleRadius)


Of course, to check if an atom has just moved outside of the circle, you'd probably have to check after each movement:
atom/movable/Move(Loc, Dir, StepX, StepY)
. = ..()
if(is_in_circle && !is_rectangle_completely_in_circle(...))
is_in_circle = FALSE
// Stepped out of the circle!


And to make everything just a bit easier to work with, you can use a more object-oriented approach:
rectangle
var lower_x, lower_y, upper_x, upper_y

New(LowerX, LowerY, UpperX, UpperY)
lower_x = LowerX
lower_y = LowerY
upper_x = UpperX
upper_y = UpperY

circle
var center_x, center_y, radius

New(CenterX, CenterY, Radius)
center_x = CenterX
center_y = CenterY
radius = Radius

proc
ContainsPoint(X, Y)
var dx = center_x - X, dy = center_y - Y
return dx * dx + dy * dy <= radius * radius

atom
proc
GetRectangle()
return new /rectangle (Px(), Py(), Px(1), Py(1))

proc
is_rectangle_completely_in_circle(rectangle/Rectangle, circle/Circle)
return Circle.ContainsPoint(Rectangle.lower_x, Rectangle.lower_y) \
&& Circle.ContainsPoint(Rectangle.upper_x, Rectangle.lower_y) \
&& Circle.ContainsPoint(Rectangle.upper_x, Rectangle.upper_y) \
&& Circle.ContainsPoint(Rectangle.lower_x, Rectangle.upper_y)


With this, you can just store a /circle instance to reuse whenever you step:
mob/player
var
tmp
circle/my_circle

verb
make_circle()
my_circle = new /circle (Cx(), Cy(), world.icon_size * 5 / 2)

Move(Loc, Dir, StepX, StepY)
. = ..()
if(. && my_circle && !is_rectangle_completely_in_circle(GetRectangle(), my_circle))
// Started to exit my_circle.

Of course, drawing the circle is a completely different matter.
I really appreciate the write up man, this is going to take me a long time to process but I really really appreciate it, thank you :)