0
\$\begingroup\$

I'm developing the A.I. of my racing game, and I'm having an unforeseen problem. I manage to get the bot to drive by itself by accelerating in the direction of an angle that is returned using the atan2 trig function considering the position of previously-defined waypoints on the track data structure. Here's the code for ai.py:

import math
import pygame

class Bot:

    def __init__(self, player, track):
        self.player = player
        self.track = track
        self.current_waypoint = 1
        self.player.states[0] = True
        self.track.choose_waypoint_path()
        self.number_of_waypoints = len(self.track.waypoint_positions.keys())
        self.get_waypoint_angle()

    def get_waypoint_angle(self):
        self.dx = self.track.waypoint_positions[self.current_waypoint][0] - self.player.car.pos[0]
        self.dy = self.track.waypoint_positions[self.current_waypoint][1] - self.player.car.pos[1]
        self.ratio = math.atan2(self.dy, self.dx)
        self.angle = -self.ratio * (180/math.pi)

    def think(self):
        if pygame.Rect(self.player.car.pos, self.player.car.dimensions).collidepoint(
            self.track.waypoint_positions[self.current_waypoint][0], 
            self.track.waypoint_positions[self.current_waypoint][1]
        ):
            self.track.choose_waypoint_path()
            self.number_of_waypoints = len(self.track.waypoint_positions.keys())
            if self.current_waypoint == self.number_of_waypoints:
                self.current_waypoint = 0
            self.current_waypoint += 1
        self.get_waypoint_angle()
        self.player.car.angle = self.angle
        self.player.car.rotate()
        self.player.move()

The thing is, when I set the car angle based on the return of the atan2 function, self.player.car.angle = self.angle, the car rotates/turns in an extremely unrealistic fashion. It works, but it means that I have to gradually steer the car, and then I face this problem. I don't know when to steer left or right, because the angle can be negative. If I do this, for example:

        if self.player.car.angle > self.new_angle:
            self.player.car.angle -= self.player.car.steering_speed
        else:
            self.player.car.angle += self.player.car.steering_speed

The bot drives to the next waypoint destination by weirdly driving in circles.

Problem

So, I know the direction in which the bot should drive, but based on its previous angle, I don't know whether it should steer left or right to reach that direction.

\$\endgroup\$
  • \$\begingroup\$ Could you perhaps add an image showing where your waypoints are along the track? It might give some insight into why your AI is spinning right round. \$\endgroup\$ – ZEKE Oct 30 '17 at 19:59
  • \$\begingroup\$ This is the data structure with the waypoints. It's the same structure as the ground ones, so it should be easy to see where is the road. The A.I. is spinning because I'm trying to steer it manually instead of just abruptly changing the angle to its new direction (As I've said, that's unrealistic, like, the car makes a 180º turn out of the blue just to follow the new waypoint. It works, but it's uggly). The best approach is to imitate a human steering gradually, but even with the new angle, it's a problem. \$\endgroup\$ – Ericson Willians Oct 30 '17 at 20:05
1
\$\begingroup\$

I'd skip the trigonometry outright and use a 2D vector trick that lets us check the left/right direction with simple addition & multiplication.

Here you'd have your car store its forward direction as a 2D unit vector, car.forward (which you might already be using to compute its velocity anyway), then:

// Compute the offset from the car to the waypoint, as you have now.
self.dx = self.track.waypoint_positions[self.current_waypoint][0] 
          - self.player.car.pos[0]
self.dy = self.track.waypoint_positions[self.current_waypoint][1]
          - self.player.car.pos[1]

// Form the dot product of this offset with the perpendicular
// to the forward vector: (forward.y, -forward.x)
self.side = self.dx * self.car.forward[1] - self.dy * self.car.forward[0]

// If this dot product is negative, the destination is on one side...
if self.side < 0:
    self.player.car.angle -= self.player.car.steering_speed
// ...otherwise it's on the other (or dead ahead if the value is close to 0)
else:
    self.player.car.angle += self.player.car.steering_speed

The signs might flip depending on the conventions of your coordinate system (y up vs y down / angles measured clockwise or counter-clockwise)

\$\endgroup\$
0
\$\begingroup\$

I suspect the problematic logic is here:

   if self.player.car.angle > self.new_angle:
        self.player.car.angle -= self.player.car.steering_speed
    else:
        self.player.car.angle += self.player.car.steering_speed

You can't tell whether to steer left or right simply due to ordinal comparison (< or >) because the angles could be close to pi, and the discontinuity there messes you up (for example, if you're currently heading 3, and the target angle is -3, this code tells you to steer clockwise (negative angle delta), whereas really you just want to steer towards 3.28 [which is equivalent to -3])

You want something more like

# angle_to_steer is now our angle difference, chosen so that it's between -pi and pi
angle_to_steer = (self.new_angle - self.player.car.angle + pi) % (2 * pi) - pi
if angle_to_steer < 0:
   self.player.car.angle -= self.player.car.steering_speed
else:
   self.player.car.angle += self.player.car.steering_speed
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.