0

I have two blocks, one is controlled by the user. When i move my block, i want the other block to follow me. I tried doing something like this

def follow():
    distance = math.hypot(abs(m.x - p.x), abs(m.y - p.y))
    angle_radians = math.atan2(abs(m.y - p.y), abs(m.x - p.x))
    if distance !=  0:
        p.y += math.sin(angle_radians)
        p.x += math.cos(angle_radians)

However, the block ends up moving in the complete opposite direction to me . Any help would be appreciated.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174

2 Answers2

0

Code works for me if I remove abs() from atan2()


import pygame
import random
import math

# --- constants --- (UPPER_CASE_NAMES)

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

FPS = 25 # for more than 220 it has no time to update screen

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

# --- classes --- (CamelCaseNames)

class Player(pygame.sprite.Sprite):

    def __init__(self, x=SCREEN_WIDTH//2, y=SCREEN_HEIGHT//2):
        super().__init__()
        self.image = pygame.image.load("image.png").convert()
        #self.rect = self.image.get_rect(x=x, y=y)
        self.rect = self.image.get_rect(centerx=x, centery=y)

    def update(self):
        #self.rect.centerx = random.randint(0, SCREEN_WIDTH)
        #self.rect.centery = random.randint(0, SCREEN_HEIGHT)
        move_x = random.randint(-15, 15)
        move_y = random.randint(-15, 15)
        self.rect.move_ip(move_x,move_y)

    def draw(self, surface):
        surface.blit(self.image, self.rect)

class Follower(Player):

    def update(self, player):
        distance = math.hypot(abs(player.rect.x - self.rect.x), abs(player.rect.y - self.rect.y))
        angle_radians = math.atan2((player.rect.y - self.rect.y), (player.rect.x - self.rect.x))

        if distance !=  0:
            self.rect.y += 5*math.sin(angle_radians)
            self.rect.x += 5*math.cos(angle_radians)        

# --- functions --- (lower_case_names)

# --- main ---

pygame.init()

screen = pygame.display.set_mode( (SCREEN_WIDTH, SCREEN_HEIGHT) )

player = Player()
follower = Follower(0, 0)

# --- mainloop ---

clock = pygame.time.Clock()

running = True
while running:

    # --- events ---
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_ESCAPE:
                running = False


    # --- changes/moves/updates ---

    if not pygame.key.get_pressed()[pygame.K_SPACE]:
        player.update()
        follower.update(player)
    # --- draws ---

    screen.fill(BLACK)

    player.draw(screen)
    follower.draw(screen)

    pygame.display.flip()

    # --- FPS ---

    ms = clock.tick(FPS)
    #pygame.display.set_caption('{}ms'.format(ms)) # 40ms for 25FPS, 16ms for 60FPS
    fps = clock.get_fps()
    pygame.display.set_caption('FPS: {}'.format(fps))

# --- end ---

pygame.quit()
furas
  • 134,197
  • 12
  • 106
  • 148
  • Thats kinda weird beacause i was reading how atan2 works and it was it can only accept absolute values –  Jan 18 '20 at 10:45
  • That won't work for a step distance of 1 and its inaccurate, because [`pygame.Rect`](https://www.pygame.org/docs/ref/rect.html) operates with integral numbers. – Rabbid76 Jan 18 '20 at 10:50
  • @Rabbid76 what distance ? I don't use any distance. And I set `5*math.sin` and `5*math.cos` to move faster and I have no problem with small values – furas Jan 18 '20 at 10:51
  • @furas your step distance is 5. (`self.rect.y += 5*math.sin(angle_radians)`). In the question the distance is 1. – Rabbid76 Jan 18 '20 at 10:52
  • @Rabbid76 yes for `1*math.sin` it doesn't work but for this value it was moving so slow that it was useless. So using `5` I automatically resolved both problems. – furas Jan 18 '20 at 10:53
  • It is too slow in your test application but not in general. Furthermore you will step over the target if the distance between `m` and `p` is < 5. – Rabbid76 Jan 18 '20 at 10:55
0

To make the algorithm work, you have to operate with floating point numbers. If m and p are pygame.Rect objects, then the algorithm won't work, pygame.Rect operates with integral numbers and the fraction part gets lost.
Note math.sin(angle_radians) and math.cos(angle_radians) is <= 1.

That means you have to store the positions of the objects in separate variables. Let's assume you have the floating point coordinates (mx, my) and (py, py)

You have to find the Unit vector from (mx, my) to (px, py).
The unit vector can be found by dividing the vector from (mx, m.y) to (px, py) by its length.
The length of a vector can be computed by the Euclidean distance.
Finally multiply the vector by a scale (step) that is not greater than the distance between the points and add it to the position. e.g:

stepDist = 1

# vector from (`mx`,  `my`) to (`px`, `py`)
dx, dy = p.y - mx, py - px

# [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance)
len = math.sqrt(dx*dx + dy*dy)

if len > 0:
    # [Unit vector](https://en.wikipedia.org/wiki/Unit_vector)
    ndx, ndy = dx/len, dy/len

    # minimum of step size and distance to target
    step = min(len, stepDist)

    # step forward
    px += ndx * step
    py += ndy * step

If a pygame.Rect object is of need, then the position of the rectangle can be set. e.g:

m.topleft = round(mx), round(my)
p.topleft = round(px), round(py)

But not you have to store the positions in (mx, my) respectively (px, py). If you would do mx, my = m.topleft respectively px, py = p.topleft, then the algorithm will break down, because the fraction component gets lost.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174