def _bounce_one(a1, a2): # Bounces a1 dir_angle = vectors.angle(a2.pos, a1.pos) vel_angle = vectors.angle(a1.velocity) # If they are head on then we want to swivel them a little if vectors.bound_angle(dir_angle[0]+180) == vel_angle[0]: dir_angle[0] = vectors.bound_angle(dir_angle[0] + 40) # Keep trying distances further and further apart until they're # not going to be overlapping any more overlapping = True dist = vectors.total_velocity(a1.velocity) a2_rect = (a2.pos[0], a2.pos[1], a2.size[0], a2.size[1]) while overlapping: new_pos = vectors.add_vectors(a1.pos, vectors.move_to_vector( dir_angle, dist )) new_rect = (new_pos[0], new_pos[1], a1.size[0], a1.size[1]) if not geometry.rect_collision(new_rect, a2_rect, True): overlapping = False dist += 1 # Add a bit to be safe new_pos = vectors.add_vectors(a1.pos, vectors.move_to_vector( dir_angle, dist + vectors.total_velocity(a1.velocity) )) a1.pos = new_pos
def _accelerate_ai(self, target): dist = vectors.distance(self.pos, target) if dist > self.max_velocity: self.velocity = vectors.move_to_vector(vectors.angle(self.pos, target), self.max_velocity) else: self.velocity = vectors.move_to_vector(vectors.angle(self.pos, target), dist)
def generate_bullet(self, target): # Set correct origin offset_angle = vectors.bound_angle( vectors.add_vectors(self._effect_offset_angle, self.facing)) origin_pos = vectors.add_vectors( self.get_offset_pos(use_effect_offset=True), self.actor.pos) # Get actual velocity we'll be using if type(target) == list or type(target) == tuple: direction = vectors.angle(origin_pos, target) target_pos = target else: direction = vectors.angle(origin_pos, target.pos) target_pos = target.pos velocity = vectors.move_to_vector(direction, self.bullet['velocity']) velocity[2] = math_lib.calc_trajectory( 0.1, vectors.distance(origin_pos, target_pos), self.bullet['velocity']) the_bullet = bullets.Shell( pos=origin_pos, velocity=velocity, image=self.bullet['image'], size=self.bullet['size'], blast_radius=self.bullet['blast_radius'], damage=self.bullet['damage'], dissipation_func=self.bullet.get('dissipation_func', "linear"), ) self.actor.bullets.append(the_bullet)
def _bounce_one(a1, a2): # Bounces a1 dir_angle = vectors.angle(a2.pos, a1.pos) vel_angle = vectors.angle(a1.velocity) # If they are head on then we want to swivel them a little if vectors.bound_angle(dir_angle[0] + 180) == vel_angle[0]: dir_angle[0] = vectors.bound_angle(dir_angle[0] + 40) # Keep trying distances further and further apart until they're # not going to be overlapping any more overlapping = True dist = vectors.total_velocity(a1.velocity) a2_rect = (a2.pos[0], a2.pos[1], a2.size[0], a2.size[1]) while overlapping: new_pos = vectors.add_vectors(a1.pos, vectors.move_to_vector(dir_angle, dist)) new_rect = (new_pos[0], new_pos[1], a1.size[0], a1.size[1]) if not geometry.rect_collision(new_rect, a2_rect, True): overlapping = False dist += 1 # Add a bit to be safe new_pos = vectors.add_vectors( a1.pos, vectors.move_to_vector(dir_angle, dist + vectors.total_velocity(a1.velocity))) a1.pos = new_pos
def generate_bullet(self, target): # Set correct origin offset_angle = vectors.bound_angle( vectors.add_vectors(self._effect_offset_angle, self.facing) ) origin_pos = vectors.add_vectors(self.get_offset_pos(use_effect_offset=True), self.actor.pos) # Get actual velocity we'll be using if type(target) == list or type(target) == tuple: direction = vectors.angle(origin_pos, target) target_pos = target else: direction = vectors.angle(origin_pos, target.pos) target_pos = target.pos velocity = vectors.move_to_vector(direction, self.bullet['velocity']) velocity[2] = math_lib.calc_trajectory(0.1, vectors.distance(origin_pos, target_pos), self.bullet['velocity']) the_bullet = bullets.Shell( pos=origin_pos, velocity=velocity, image = self.bullet['image'], size = self.bullet['size'], blast_radius = self.bullet['blast_radius'], damage = self.bullet['damage'], dissipation_func = self.bullet.get('dissipation_func', "linear"), ) self.actor.bullets.append(the_bullet)
def _accelerate_ai(self, target): dist = vectors.distance(self.pos, target) if dist > self.max_velocity: self.velocity = vectors.move_to_vector( vectors.angle(self.pos, target), self.max_velocity) else: self.velocity = vectors.move_to_vector( vectors.angle(self.pos, target), dist)
def __init__(self, actor, ability_data={}): super(Ability, self).__init__() self.actor = actor self.facing = [0, 0] self.set_stats(ability_data) self.charge = self.required_charge self._effect_offset_distance = vectors.distance(self.effect_offset) self._effect_offset_angle = vectors.angle(self.effect_offset) self._img_offset_distance = vectors.distance(self.image_offset) self._img_offset_angle = vectors.angle(self.image_offset)
def __init__(self, actor, ability_data={}): super(Ability, self).__init__() self.actor = actor self.facing = [0,0] self.set_stats(ability_data) self.charge = self.required_charge self._effect_offset_distance = vectors.distance(self.effect_offset) self._effect_offset_angle = vectors.angle(self.effect_offset) self._img_offset_distance = vectors.distance(self.image_offset) self._img_offset_angle = vectors.angle(self.image_offset)
def can_use(self, target=None, **kwargs): """Called to see if the ability can be used""" if self.charge < self.required_charge: return False # Check aim if type(target) in (list, tuple): target_facing = vectors.angle(self.actor.pos, target) else: target_facing = vectors.angle(self.actor.pos, target.pos) if not self.check_aim(target_facing): return False return True
def run_ai(self): if self.micro_orders == []: cmd, pos, target = self.current_order else: cmd, pos, target = self.micro_orders[0] self._attack_ai() if cmd == "stop" or cmd == "hold position": self.velocity = [0,0,0] if target == 0: self.next_order() elif cmd == "move" or cmd == "reverse": dist = vectors.distance(self.pos, pos) self.velocity = vectors.move_to_vector(vectors.angle(self.pos, pos), self.max_velocity) if dist <= vectors.total_velocity(self.velocity): self.pos = pos self.velocity = [0,0,0] self.next_order() else: raise Exception("No handler for cmd %s (pos: %s, target: %s)" % (cmd, pos, target))
def test_angle_single(self): vals = ( # XY Tests ([4, -4, 0], [45, 0]), # Up and right ([4, 4, 0], [135, 0]), # Down and right ([-4, 4, 0], [225, 0]), # Down and left ([-4, -4, 0], [315, 0]), # Up and left # Same as before but scaled up in size ([400, -400, 0], [45, 0]), # Up and right ([1000, 1000, 0], [135, 0]), # Down and right ([-0.5, 0.5, 0], [225, 0]), # Down and left ([-50000, -50000, 0], [315, 0]), # Up and left ([0, -4, 0], [0, 0]), # Dead Up ([4, 0, 0], [90, 0]), # Dead Right ([0, 4, 0], [180, 0]), # Dead Down ([-4, 0, 0], [270, 0]), # Dead Left ) for a, expected in vals: r, r2 = vectors.angle(a) self.assertAlmostEqual( r, expected[0], places=2, msg="vectors.angle(%s) should equal %s, instead got %s" % (a, expected[0], r)) self.assertAlmostEqual(r2, expected[1], places=2)
def __init__(self, filepath, columns=1, rows=1, animation_rate = 0.5, rotate_about=None): super(Animation, self).__init__() if columns < 1: raise Exception("Cannot have fewer than 1 column in an animation") if rows < 1: raise Exception("Cannot have fewer than 1 row in an animation") self.images = [] self.animation_rate = animation_rate img = pygame.image.load(filepath) r = img.get_rect() # Break it down into tiles, save the tiles tile_width = r.width / columns tile_height = r.height / rows for y in range(rows): for x in range(columns): tile = pygame.Surface((tile_width, tile_height), SRCALPHA) tile.blit(img, (0,0), (x * tile_width, y * tile_height, tile_width, tile_height)) self.images.append(tile) # Default the rotate about point if rotate_about == None: rotate_about = 0, 0, 0 # centre_offset is a distance and angle self.centre_offset = ( vectors.distance(rotate_about), vectors.angle(rotate_about), )
def test_angle_single(self): vals = ( # XY Tests ([4,-4,0], [45, 0]),# Up and right ([4,4,0], [135, 0]),# Down and right ([-4,4,0], [225, 0]),# Down and left ([-4,-4,0], [315, 0]),# Up and left # Same as before but scaled up in size ([400,-400,0], [45, 0]),# Up and right ([1000,1000,0], [135, 0]),# Down and right ([-0.5,0.5,0], [225, 0]),# Down and left ([-50000,-50000,0], [315, 0]),# Up and left ([0,-4,0], [0, 0]),# Dead Up ([4,0,0], [90, 0]),# Dead Right ([0,4,0], [180, 0]),# Dead Down ([-4,0,0], [270, 0]),# Dead Left ) for a, expected in vals: r, r2 = vectors.angle(a) self.assertAlmostEqual(r, expected[0], places=2, msg="vectors.angle(%s) should equal %s, instead got %s" % ( a, expected[0], r )) self.assertAlmostEqual(r2, expected[1], places=2)
def generate_bullet(self, target): if type(target) == list or type(target) == tuple: direction = vectors.angle(self.actor.pos, target) target_pos = target else: direction = vectors.angle(self.actor.pos, target.pos) target_pos = target.pos velocity = vectors.move_to_vector(direction, self.bullet['velocity']) velocity[2] = math_lib.calc_trajectory(0.1, vectors.distance(self.actor.pos, target_pos), self.bullet['velocity']) the_bullet = bullets.Shell( pos=self.actor.pos, velocity=velocity, image = self.bullet['image'], size = self.bullet['size'], blast_radius = self.bullet['blast_radius'], damage = self.bullet['damage'], dissipation_func = self.bullet.get('dissipation_func', "linear"), ) self.actor.bullets.append(the_bullet)
def _bounce_both(a1, a2): # These are the angles directly away from each other angle1 = vectors.angle(a2.pos, a1.pos) angle2 = vectors.angle(a1.pos, a2.pos) vel_angle1 = vectors.angle(a1.velocity) vel_angle2 = vectors.angle(a2.velocity) # If they are head on then we want to swivel them a little if vel_angle1[0] == angle2[0] and vel_angle2[0] == angle1[0]: angle1[0] = vectors.bound_angle(angle1[0] + 20) angle2[0] = vectors.bound_angle(angle2[0] + 20) # Keep trying distances further and further apart until they're # not going to be overlapping any more overlapping = True dist_multiplier = 0.1 while overlapping: dist_multiplier += 0.1 new_pos1 = vectors.add_vectors( a1.pos, vectors.move_to_vector(angle1, max(a1.size) * dist_multiplier)) new_pos2 = vectors.add_vectors( a2.pos, vectors.move_to_vector(angle2, max(a2.size) * dist_multiplier)) new_rect1 = (new_pos1[0], new_pos1[1], a1.size[0], a1.size[1]) new_rect2 = (new_pos2[0], new_pos2[1], a2.size[0], a2.size[1]) if not geometry.rect_collision(new_rect1, new_rect2): overlapping = False a1.pos = new_pos1 a2.pos = new_pos2
def _bounce_both(a1, a2): # These are the angles directly away from each other angle1 = vectors.angle(a2.pos, a1.pos) angle2 = vectors.angle(a1.pos, a2.pos) vel_angle1 = vectors.angle(a1.velocity) vel_angle2 = vectors.angle(a2.velocity) # If they are head on then we want to swivel them a little if vel_angle1[0] == angle2[0] and vel_angle2[0] == angle1[0]: angle1[0] = vectors.bound_angle(angle1[0] + 20) angle2[0] = vectors.bound_angle(angle2[0] + 20) # Keep trying distances further and further apart until they're # not going to be overlapping any more overlapping = True dist_multiplier = 0.1 while overlapping: dist_multiplier += 0.1 new_pos1 = vectors.add_vectors(a1.pos, vectors.move_to_vector( angle1, max(a1.size) * dist_multiplier )) new_pos2 = vectors.add_vectors(a2.pos, vectors.move_to_vector( angle2, max(a2.size) * dist_multiplier )) new_rect1 = (new_pos1[0], new_pos1[1], a1.size[0], a1.size[1]) new_rect2 = (new_pos2[0], new_pos2[1], a2.size[0], a2.size[1]) if not geometry.rect_collision(new_rect1, new_rect2): overlapping = False a1.pos = new_pos1 a2.pos = new_pos2
def _attack_ai(self): """AI handling the process of attacking""" target = self.get_first_target() if target == None: for a in self.abilities: a.turn(self.facing) return for a in self.abilities: # Turn the ability towards it's target a.turn(vectors.angle(self.pos, target.pos)) if a.can_use(target): a.use(target)
def __init__(self, filepath, columns=1, rows=1, animation_rate=0.5, rotate_about=None): super(Animation, self).__init__() if columns < 1: raise Exception("Cannot have fewer than 1 column in an animation") if rows < 1: raise Exception("Cannot have fewer than 1 row in an animation") self.images = [] self.animation_rate = animation_rate img = pygame.image.load(filepath) r = img.get_rect() # Break it down into tiles, save the tiles tile_width = r.width / columns tile_height = r.height / rows for y in range(rows): for x in range(columns): tile = pygame.Surface((tile_width, tile_height), SRCALPHA) tile.blit( img, (0, 0), (x * tile_width, y * tile_height, tile_width, tile_height)) self.images.append(tile) # Default the rotate about point if rotate_about == None: rotate_about = 0, 0, 0 # centre_offset is a distance and angle self.centre_offset = ( vectors.distance(rotate_about), vectors.angle(rotate_about), )
def _turn_ai(self, target): self.facing = vectors.bound_angle(self.facing) target_angle = vectors.angle(self.pos, target) diff = vectors.angle_diff(self.facing, target_angle)[0] if abs(diff) <= self.turn_speed: self.facing = target_angle return True if diff < 0: self.facing[0] -= self.turn_speed for a in self.abilities: a.facing[0] += self.turn_speed else: self.facing[0] += self.turn_speed for a in self.abilities: a.facing[0] -= self.turn_speed return False
def test_angle(self): vals = ( # XY Tests ([0, 0, 0], [4, -4, 0], [45, 0]), # Up and right ([0, 0, 0], [4, 4, 0], [135, 0]), # Down and right ([0, 0, 0], [-4, 4, 0], [225, 0]), # Down and left ([0, 0, 0], [-4, -4, 0], [315, 0]), # Up and left # Same as before but scaled up in size ([0, 0, 0], [400, -400, 0], [45, 0]), # Up and right ([0, 0, 0], [1000, 1000, 0], [135, 0]), # Down and right ([0, 0, 0], [-0.5, 0.5, 0], [225, 0]), # Down and left ([0, 0, 0], [-50000, -50000, 0], [315, 0]), # Up and left ([0, 0, 0], [0, -4, 0], [0, 0]), # Dead Up ([0, 0, 0], [4, 0, 0], [90, 0]), # Dead Right ([0, 0, 0], [0, 4, 0], [180, 0]), # Dead Down ([0, 0, 0], [-4, 0, 0], [270, 0]), # Dead Left # Specific XY tests ([1, 1, 0], [4, 100, 0], [178.26, 0]), # Down and a little bit right ([400, 400, 0], [100, 900, 0], [210.963, 0]), # Down and left # Z Tests # ([0,0,0], [4,-4,0], [0, 45]),# Aim up # ([0,0,0], [4,4,0], [135, 0]),# Aim down # ([0,0,0], [-4,4,0], [225, 0]), # ([0,0,0], [-4,-4,0], [315, 0]), ) for a, b, expected in vals: r, r2 = vectors.angle(a, b) self.assertAlmostEqual( r, expected[0], places=2, msg="vectors.angle(%s, %s) should equal %s, instead got %s" % (a, b, expected[0], r)) self.assertAlmostEqual(r2, expected[1], places=2)
def test_angle(self): vals = ( # XY Tests ([0,0,0], [4,-4,0], [45, 0]),# Up and right ([0,0,0], [4,4,0], [135, 0]),# Down and right ([0,0,0], [-4,4,0], [225, 0]),# Down and left ([0,0,0], [-4,-4,0], [315, 0]),# Up and left # Same as before but scaled up in size ([0,0,0], [400,-400,0], [45, 0]),# Up and right ([0,0,0], [1000,1000,0], [135, 0]),# Down and right ([0,0,0], [-0.5,0.5,0], [225, 0]),# Down and left ([0,0,0], [-50000,-50000,0], [315, 0]),# Up and left ([0,0,0], [0,-4,0], [0, 0]),# Dead Up ([0,0,0], [4,0,0], [90, 0]),# Dead Right ([0,0,0], [0,4,0], [180, 0]),# Dead Down ([0,0,0], [-4,0,0], [270, 0]),# Dead Left # Specific XY tests ([1,1,0], [4,100,0], [178.26, 0]),# Down and a little bit right ([400, 400, 0], [100, 900, 0], [210.963,0]),# Down and left # Z Tests # ([0,0,0], [4,-4,0], [0, 45]),# Aim up # ([0,0,0], [4,4,0], [135, 0]),# Aim down # ([0,0,0], [-4,4,0], [225, 0]), # ([0,0,0], [-4,-4,0], [315, 0]), ) for a, b, expected in vals: r, r2 = vectors.angle(a, b) self.assertAlmostEqual(r, expected[0], places=2, msg="vectors.angle(%s, %s) should equal %s, instead got %s" % ( a, b, expected[0], r )) self.assertAlmostEqual(r2, expected[1], places=2)
class GeometryTests(unittest.TestCase): def test_rectangle_collision(self): vals = ( # R1 is up and to the left of R2 ((5, 5, 15, 15), (10, 10, 20, 20), True), ((2, 2, 8, 8), (10, 10, 20, 20), False), # R1 is up of R2 ((10, 5, 20, 15), (10, 10, 20, 20), True), ((10, 2, 20, 8), (10, 10, 20, 20), False), # R1 is up and to the right of R2 ((15, 5, 25, 15), (10, 10, 20, 20), True), ((15, 2, 25, 8), (10, 10, 20, 20), False), # R1 is left of R2 ((5, 10, 15, 20), (10, 10, 20, 20), True), ((2, 10, 8, 20), (10, 10, 20, 20), False), # R1 is right of R2 ((15, 10, 25, 20), (10, 10, 20, 20), True), ((25, 10, 30, 20), (10, 10, 20, 20), False), # R1 is down and to the left of R2 ((5, 15, 15, 25), (10, 10, 20, 20), True), ((2, 15, 8, 25), (10, 10, 20, 20), False), # R1 is down of R2 ((10, 15, 20, 25), (10, 10, 20, 20), True), ((10, 22, 20, 28), (10, 10, 20, 20), False), # R1 is down and to the right of R2 ((15, 15, 25, 25), (10, 10, 20, 20), True), ((22, 15, 28, 25), (10, 10, 20, 20), False), # Overlapping ((15, 15, 25, 25), (15, 15, 25, 25), True),# Exact same size ((279, 779, 279+41, 779+41), (279, 779, 279+41, 779+41), True),# Exact same size ((394, 79, 435, 435), (317, 79, 358, 120), False), ((10, 10, 30, 30), (15, 15, 25, 25), True),# R1 is bigger ((15, 15, 25, 25), (10, 10, 30, 30), True),# R2 is bigger # Different types ((5, 15, 15, 25), pygame.Rect(10, 10, 20, 20), True), ((2, 15, 8, 25), pygame.Rect(10, 10, 20, 20), False), ) for r1, r2, expected in vals: # We want to make sure that it'll work for inputs regardless of order try: self.assertEqual(expected, geometry.rect_collision(r1, r2)) except Exception as e: print("Failed on first pass of %s, %s" % (str(r1), str(r2))) raise try: self.assertEqual(expected, geometry.rect_collision(r2, r1)) except Exception as e: print("Failed on second pass of %s, %s" % (str(r1), str(r2))) raise # To generated these values I created a lot of triangles with different # positions for A, B and C to ensure a range of angles. I then used # https://github.com/Teifion/Mini-tools/blob/master/vectors.html # to calcuate the various raw angles and with pen+paper calcuated the # approximate angles and sizes. If the code was close then I knew # it was right as my workings had a lot of rounding errors, if it was # not close then something was wrong with either my paper workings # or the code. collision_vals = ( # answers = A, B, C, a, b, c (([10,5,0], vectors.angle([3,1,0])), ([13,10,0], vectors.angle([2,-1,0])), (40.601, 94.398, 45.0, 5.366, 8.22, 5.83)), (([5,10,0], vectors.angle([3,-1,0])), ([10,1,0], vectors.angle([1,1,0])), (42.51, 74.05, 63.43, 7.778, 11.067, 10.295)), (([10,3,0], vectors.angle([1,0,0])), ([0,14,0], vectors.angle([1,-0.5,0])), (132.27, 21.16, 26.565, 24.596, 12.0, 14.866)), (([5,10,0], vectors.angle([2,-1,0])), ([1,1,0], vectors.angle([1,1,0])), (87.4, 21.037, 71.57, 10.370, 3.726, 9.848)), (([6,1,0], vectors.angle([-1,1,0])), ([15,10,0], vectors.angle([-3,-1,0])), (90.0, 26.57, 63.43, 14.230, 6.363, 12.727)), (([10,10,0], vectors.angle([-1,-1,0])), ([17,2,0], vectors.angle([-2,0,0])), (86.185, 48.814, 45.0, 15.0, 11.313, 10.63)), (([20,3,0], vectors.angle([-2,1,0])), ([5,15,0], vectors.angle([-1,-0.5,0])), (12.09, 114.78, 53.13, 5.031, 21.801, 19.209)), (([12,10,0], vectors.angle([-7,-1,0])), ([3,3,0], vectors.angle([-1,0,0])), (29.74, 142.13, 8.13, 40.0, 49.497, 11.401)), (([5,10,0], vectors.angle([-1,-1,0])), ([10,11,0], vectors.angle([-1.5,-1,0])), (146.309, 22.381, 11.309, 14.422, 9.899, 5.099)), ) def test_inside_angle(self): for act1, act2, expected in self.collision_vals: A, B, C, a, b, c = expected ansA, ansB, ansC = geometry.inside_angles(act1, act2) try: self.assertAlmostEqual(A, ansA, places=2) self.assertAlmostEqual(B, ansB, places=2) self.assertAlmostEqual(C, ansC, places=2) except Exception as e: print("Failure with inputs of A=%s, B=%s" % (act1, act2)) raise # This will overlap with the test_inside_angle function but the above # will remain incase the functionality is moved around def test_collision_angle(self): for act1, act2, expected in self.collision_vals: A, B, C, a, b, c = expected ansA, ansB, ansC, ansa, ansb, ansc = geometry.rect_collision_distance(act1, act2, testing=True) try: self.assertAlmostEqual(A, ansA, places=2) self.assertAlmostEqual(B, ansB, places=2) self.assertAlmostEqual(C, ansC, places=2) except Exception as e: print("Failure on angles with inputs of A=%s, B=%s" % (act1, act2)) raise try: self.assertAlmostEqual(a, ansa, places=2) self.assertAlmostEqual(b, ansb, places=2) self.assertAlmostEqual(c, ansc, places=2) except Exception as e: print("Failure on sides with inputs of A=%s, B=%s" % (act1, act2)) raise