Have an issue with a game of snake in Python im making where my collision check isnt working. I have written a function to check a collision between the snake and the food itself. When it collides it doesnt do anything, i have written it to undraw if it collides using the function, i also put in a print function to see if the function was working if i was using it and saw no print.
def collide(block1,block2):
if math.sqrt(((block2.getCenterX() - block1.getCenterX()) **2)+ ((block2.getCenterY() - block1.getCenterY())**2)) < BLOCK_SIZE:
print("true")
return True
else:
return False
------------------------------------------------------- not part of functiom
if collide(theSnake[0],food) == True:
food.undraw()
foodX = random.randint(BLOCK_SIZE, WIN_WIDTH-BLOCK_SIZE)
foodY = random.randint(BLOCK_SIZE, WIN_HEIGHT-BLOCK_SIZE)
food.draw()
theSnake.append(block)
else:
foodX = foodX
foodY = foodY
I'd suggest you modify your collide function to provide more information. eg.
def collide(block1,block2):
dx = block2.getCenterX() - block1.getCenterX()
dy = block2.getCenterY() - block1.getCenterY()
dist = math.sqrt(dx ** 2 + dy ** 2) # equivalent to math.hypot(dx, dy)
if dist < BLOCK_SIZE:
print("true")
return True
else:
print("false", dx, dy, dist)
return False
Related
Im running Python 3.8.2 and Pygame 2.0.0.dev6 in Pycharm.
I read a few of the other threads on similar issues but couldn't figure out why I'm getting this error in this instance. The purpose of this post is two-fold: 1 is that I would like to understand this error (I am very new to programming), and 2 is that I would like to know if there is a better method to accomplish what I am trying to do.
I am trying to make enemies that sway back and forth in the x-direction, their change being triggered either by a certain amount of time passing or (haven't added this yet) a certain number of collisions with other enemies.
The error seems to be triggered by the second if statement in the movement method here:
class EnemyShip(ShipGeneral):
MOVEMENT_TIME = 180
SHIP_TYPE_DIC = {
'pawn': Images().pawn_ship_img,
'boss': Images().boss_ship_img
}
def __init__(self, x, y, enemy_type=None):
super().__init__(x, y, enemy_type)
self.ship_img = self.SHIP_TYPE_DIC[enemy_type]
self.mask = pygame.mask.from_surface(self.ship_img)
self.health = 100
self.movement_time_counter = 0 # Defaut set to zero
def movement_timer(self):
if self.movement_time_counter >= self.MOVEMENT_TIME:
self.movement_time_counter = 0
elif self.movement_time_counter > 0:
self.movement_time_counter +=1
def movement(self): # TODO fix enemy movement to prevent overlapping and allow for advances
y_vel = 10
x_vel = 2
boundaries = Settings()
if self.y <= 100:
self.y += y_vel
if self.movement_time_counter == 0: # should be true and run first in the while loop
x_direction = random.choice(['R', 'L'])
self.movement_time_counter += 1
if x_direction == 'R' and self.x + self.ship_img.get_width() + x_vel < boundaries.screen_width:
self.x += x_vel
if x_direction == 'L' and self.x > 0:
self.x -= x_vel
I call the class and the functions in the game's main while loop here:
enemies = []
running = True
while running:
clock.tick(FPS)
gf.check_events() # checks for user quit event
if len(enemies) == 0: # updates level and spawns new enemies after each wave
level += 1
fleet_size += 5
for enemy in range(fleet_size):
enemy = EnemyShip(
random.randrange(20, 800 - 20),
random.randrange(-1000, -100, 10),
enemy_type=random.choice(['pawn', 'pawn', 'pawn', 'pawn', 'boss'])
)
enemies.append(enemy)
for enemy in enemies: # enables each enemy to move
enemy.movement()
enemy.move_missile(7, playership)
enemy.movement_timer()
if enemy.health <=0:
enemies.remove(enemy)
If I run this I get the following error:
File >"/Users/aaronbastian/Documents/PythonCode/Part_II_Projects/alien_invasion/ships.py", line 114, in movement
if x_direction == 'R' and self.x + self.ship_img.get_width() + x_vel < boundaries.screen_width:
UnboundLocalError: local variable 'x_direction' referenced before assignment
I don't understand the error because I thought self.movement_time_counter == 0 should evaluate to True and thus assign x_direction before the following if statements were run.
If someone could explain to me my error, I would greatly appreciate it! Also, I apologize if this is a redundant post, I just couldn't understand the other similar threads.
This error is raised during compilation because you have not initialized the variable anywhere. Just initialize the variable with some default value in the ____init____().
Example:
self.x_direction = None
I am currently working on a 2D top down rogue-like game using Python. The map is a dungeon containing many open rectangular rooms (image), each with around 2-4 enemies inside. I am currently looking to implement a path-finding system where the enemies will move around each other and attempt to swarm the player.
So far, I have implemented an A* algorithm that does allow the enemies to navigate and swarm the player in this way. However, my approach is causing very low frame rates: generally around 15 FPS but it will go as low as under 1 FPS when an enemy has no path to the player. I feel it is very inefficient, since path-finding is being done for every enemy on each frame. Currently, other enemies are seen as obstacles for the A* algorithm, and the only optimization is that an enemy will move directly towards the player if there are no obstacles in its way. Here's the code:
import heapq
#...
FLOOR = 1
#...
class Game:
def __init__(self):
#...
self.pathfindingGranularity = 5
# Slope and line intersection functions are based on: https://www.codeproject.com/Tips/864704/Python-Line-Intersection-for-Pygame
def lineInRect(self, start, end, r):
if start in r and end in r: return True
if self.segmentIntersect(start, end, r.origin, Point(r.x + r.width, r.y)) is not None: return True
if self.segmentIntersect(start, end, Point(r.x, r.y + r.height), Point(r.x + r.width, r.y + r.height)) is not None: return True
if self.segmentIntersect(start, end, r.origin, Point(r.x, r.y + r.height)) is not None: return True
if self.segmentIntersect(start, end, Point(r.x + r.width, r.y), Point(r.x + r.width, r.y + r.height)) is not None: return True
return False
def slope(self, p1, p2):
if p2.x - p1.x == 0: return 1e10
return (p2.y - p1.y) / (p2.x - p1.x)
def yIntercept(self, slope, p1):
return p1.y - slope * p1.x
def lineIntersect(self, start1, end1, start2, end2):
min_allowed = 1e-5
big_value = 1e10
m1 = self.slope(start1, end1)
b1 = self.yIntercept(m1, start1)
m2 = self.slope(start2, end2)
b2 = self.yIntercept(m2, start2)
if abs(m1 - m2) < min_allowed: x = big_value if (b2 - b1 >= 0) else -big_value
else: x = (b2 - b1) / (m1 - m2)
y = m1 * x + b1
return Point(x, y)
def segmentIntersect(self, start1, end1, start2, end2):
intersection = self.lineIntersect(start1, end1, start2, end2)
def approx(f):
return round(f * 10000) / 10000
if not approx(start1.x) <= approx(intersection.x) <= approx(end1.x):
if not approx(end1.x) <= approx(intersection.x) <= approx(start1.x):
return None
if not approx(start2.x) <= approx(intersection.x) <= approx(end2.x):
if not approx(end2.x) <= approx(intersection.x) <= approx(start2.x):
return None
if not approx(start1.y) <= approx(intersection.y) <= approx(end1.y):
if not approx(end1.y) <= approx(intersection.y) <= approx(start1.y):
return None
if not approx(start2.y) <= approx(intersection.y) <= approx(end2.y):
if not approx(end2.y) <= approx(intersection.y) <= approx(start2.y):
return None
return intersection
class Enemy (Entity):
def update(self, game):
#...
if not self.getRect().intersects(game.player.getRect()) and self.canMove():
self.generatePath(game)
if self.path:
# Move towards player
elif self.canMove():
# Hurt the player
#...
def generatePath(self, game):
if not self.lineOccupied(Point(self.x, self.y), game.player.getCenterpoint(), game):
self.path = [game.player.getCenterpoint()]
return
frontier = PriorityQueue()
start = Point(self.x, self.y)
frontier.put(start, 0)
came_from = {}
came_from[start] = None
done = False
while not frontier.empty():
current = frontier.get()
if Rect(current.x + self.hitbox.x, current.y + self.hitbox.y, self.hitbox.w, self.hitbox.h).intersects(game.player.getRect()):
done = True
break
for next in self.findAdjacents(current, game):
if self.lineOccupied(current, next, game): continue
if next not in came_from:
priority = self.heuristic(next, game)
frontier.put(next, priority)
came_from[next] = current
if not done:
self.path.clear()
else:
p = [current]
while came_from[p[-1]] is not None:
p.append(came_from[p[-1]])
self.path = p[::-1][1:]
i = 0
def findAdjacents(self, currentPoint, game):
d = 1 / game.pathfindingGranularity
for x in (currentPoint.x - d, currentPoint.x, currentPoint.x + d):
for y in (currentPoint.y - d, currentPoint.y, currentPoint.y + d):
if x == currentPoint.x and y == currentPoint.y: continue
elif self.canWalkAtCoords(x, y, game):
yield Point(x, y)
def canWalkAtCoords(self, x, y, game):
for nx in (x, x + self.hitbox.w):
for ny in (y, y + self.hitbox.h):
if game.blockAt(nx, ny) != FLOOR:
return False
return True
def lineOccupied(self, start, end, game):
for e in self.room.enemies:
if e is self:
continue
for xo in (self.hitbox.x, self.hitbox.x + self.hitbox.w):
for yo in (self.hitbox.y, self.hitbox.y + self.hitbox.h):
if game.lineInRect(start + Point(xo, yo), end + Point(xo, yo), e.getRect()):
return True
return False
I feel like there should be a much more efficient solution to this, especially seeing as the room is rectangular and there are no extra walls or obstacles that the enemies need to move around, but so far my searches for a solution have come up empty-handed. Are there some optimizations I could make to increase the performance of the program? Or if not, is there a better pathfinding method I should look into? Any help would be greatly appreciated!
You should try to have you path finding start from your character and fan out using a breadth-first-search (with some adjustment for slopes). Every time you come across an enemy, you can compute its optimal path toward the player.
That way you only do one pass across the whole board rather than one for each enemy.
Let me know if you want more details.
This question already has answers here:
return statement in for loops [duplicate]
(6 answers)
Closed 3 years ago.
Hi I'm new to game dev and I'm using pygame, now I'm in this problem I have multiple enemies that the player has to avoid and kill simultaneously, but my collision detection function checks collision for one enemy at a time.
Aside from the first enemy in the list, the function does not work for other enemies from the list.
Here's the code for enemy creation and collision detection
class Enemy(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.width, self.height = 30, 30
self.vel = 10
self.color = (0, 0, 255)
self.rect = (x, y, self.width, self.height)
def create_enemies(n):
enemies = []
for i in range(n):
xcor = randint(0, (W - 50))
ycor = randint(0, H - H/2)
enemies.append(Enemy(xcor, ycor))
return enemies
n = 2
enemies = create_enemies(n)
def collision_detection(obj1, obj2list):
for obj2 in obj2list:
if (obj2.x < obj1.x < (obj2.x + obj2.width)) and (obj2.y < obj1.y < (obj2.y + obj2.height)):
return False
if (obj2.x < obj1.x < (obj2.x + obj2.width)) and (obj2.y < (obj1.y + obj1.height) < (obj2.y + obj2.height)):
return False
if (obj2.x < (obj1.x + obj1.width) < (obj2.x + obj2.width)) and (obj2.y < (obj1.y + obj1.height) < (obj2.y + obj2.height)):
return False
if (obj2.x < (obj1.x + obj1.width) < (obj2.x + obj2.width)) and (obj2.y < obj1.y < (obj2.y + obj2.height)):
return False
if obj1.x == obj2.x and obj1.y <= obj2.y + obj2.height:
return False
if obj1.x == obj2.x and obj1.y + obj1.height >= obj2.y:
return False
else:
return True
Your problem is in your collision detection logic. Your logic is as follows:
main loop > call collision_detection > iterate over objects
It should be:
main loop > iterate over objects > call collision_detection
This is because if the first object in obj2list collides with obj1, it returns from the function and doesn't check the rest of the objects.
I'm trying to create a collision detection between 4 controllable characters on an RPG battle map. Here is the function I'm using
def player_collission(Lord_x,Lord_y,Journeyman_x,Journeyman_y,Archer_x,Archer_y,
Cleric_x,Cleric_y):
print("Running")
if abs(Lord_x - Journeyman_x) <= 0 and abs(Lord_y - Journeyman_y) <= 0:
print("Colission detected")
return True
elif abs(Lord_x - Archer_x) <= 0 and abs(Lord_y - Archer_y) <= 0:
print("Colission detected")
return True
elif abs(Lord_x - Cleric_x) <= 0 and abs(Lord_y == Cleric_y) <= 0:
print("Colission detected")
return True
elif abs(Journeyman_x - Archer_x) <= 0 and abs(Journeyman_y - Archer_y) <= 0:
print("Colission detected")
return True
elif abs(Journeyman_x - Cleric_x) <= 0 and abs(Journeyman_y - Cleric_y) <= 0:
print("Colission detected")
return True
elif abs(Archer_x - Cleric_x) <= 0 and abs(Archer_y == Cleric_y) <= 0:
print("Colission detected")
return True
else:
return False #I didnt use classes so it has alot of if statements
if player_up:
p_collide = player_collission(Lord_x,Lord_y,Journeyman_x,Journeyman_y,Archer_x,Archer_y,
Cleric_x,Cleric_y)
if current_player == "lord":
if p_collide != True:
Lord_y -= tile_increment
if Lord_y <= 0:
Lord_y = 50
What happens is that the characters still move into each other but it detects the collision after it has already moved into each other and freezes all movement. I'm not sure how to re arrange it to make it work properly.
You detect collision when it has already happened. This is why you see characters overlapping.
Instead, you should detect if a collision is going to happen, an prevent a motion that would lead to that.
An example:
def move(coords, velocity):
"""Moves coords according to velocity."""
x, y = coords # Unpack a 2-tuple.
vx, vy = velocity
return (x + vx, y + vy)
tom_coords = (0, 0) # Tom is in the corner.
tom_v = (1, 1) # Tom moves by diagonal.
jerry_coords = (5, 0) # Jerry is a bit away from Tom.
jerry_v = (0, 1) # Jerry moves vertically.
while True:
new_tom_coords = move(tom_coords, tom_v) # Tom moves without fear.
new_jerry_coords = move(jerry_coords, jerry_v)
if new_jerry_coords == new_tom_coords: # Would be a collision!
new_jerry_coords = jerry_coords # Back to previous tile.
vx, vy = jerry_v
jerry_v = (-vx, -vy) # Jerry runs back.
print("Collision imminent, Jerry runs away!")
else:
jerry_coords = new_jerry_coords # Only update if no collision.
# Could also check collisions with walls, etc.
tom_coords = new_tom_coords
# Not inside pygame, so just print it and wait for a key press.
print('Tom:', tom_coords, 'Jerry:', jerry_coords)
input("Enter to continue, Ctrl+C to stop ")
Run it in and see how Tom and Jerry come close to each other but never occupy the same tile.
I Have Been Trying for days on how to do this. Basically You Control A Player, And zombies follow you.
Problem Is, I Cant Seem To Get The Zombies To Follow! I Tried If Statements For Example
if playerx > zombiex:
zombiex=zombiex - 2
screen.blit(zombie,(zombiex,zombiey))
aaaaaand That Didnt Work.... :/
Any Ideas?
Maybe this is what you searched for.
def length(x, y):
return (x ** 2 + y ** 2) ** .5
def norm(x, y):
_len = length(x, y)
return x / _len, y / _len
class ZombieController(object):
def __init__(self, zombie_view_range):
self._zombs = []
self.append = self._zombs.append
self._range = zombie_view_range
def NextFrame(self, player_pos):
px, py = player_pos
_range = self._range
for zombie in self._zombs:
x, y = zombie.position
dx, dy = px - x, py - y
_len = length(dx, dy)
if _len <= _range:
speed = zombie.speed
direction = norm(dx, dy)
zombie.positon = x + direction[0] * speed, y + direction[1] * speed
First, can't answer your whole questions as there's not enough information. How doesn't it work exactly?
Second, if you want the zombies to follow, you need thier coordinates to converge with the players so you need something like:
if playerx > zombiex:
zombiex = zombiex + max(zombiespeed, playerx - zombiex)
elif playerx < zombiex:
zombiex = zombiex - max(zombiespeed, zombiex - playerx)
NB:
I replace 2 with zombiespeed which you define elsewhere so you can change the speed in one place for future.
I use max() to ensure the zombie won't move PAST the player when very close.
You'd obviously do the same for the y direction too.