How can I mouse-scroll around a 2D tile map in pygame? - python
I am trying to play around with pygame and I have using some examples that I found to learn and create a simple. My next goal is to create a 2D tile map bigger than the screen size and then being able to scroll around with the mouse. I would like to make something similar as a strategic game, where if you move the mouse to the edges of the screen, the "camara" will move in that direction, showing that part of the map (I would like also to stop the camara if it reaches the end of the map). At this moment, if I hover the mouse over the edges, it will only move once.
import pygame, os
from pygame.locals import *
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
SCREEN_WIDTH = 5*40
SCREEN_HEIGHT = 7*40
#functions to create our resources
def load_image(name, colorkey=None):
try:
image = pygame.image.load(name)
except pygame.error, message:
print 'Cannot load image:', name
raise SystemExit, message
image = image.convert_alpha()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()
#classes for our game objects
class Camera(object):
def __init__(self, camera_func, width, height):
self.camera_func = camera_func
self.state = pygame.Rect(100,100, width, height)
def apply(self, rect):
l, t, w, h = rect
if 0 <= self.state[0] <= (SCREEN_WIDTH/5):
l += 10
elif (SCREEN_WIDTH - (SCREEN_WIDTH/5)) < self.state[0] <= SCREEN_WIDTH:
l -=10
if 0 <= self.state[1] <= (SCREEN_HEIGHT/5):
t += 10
elif (SCREEN_HEIGHT - (SCREEN_HEIGHT/5)) < self.state[1] <= SCREEN_HEIGHT:
t -=10
return rect.move(l,t)
def update(self):
pos = pygame.mouse.get_pos()
self.state.topleft = pos
#self.state = self.camera_func(self.state)
def complex_camera(camera):
l, t, w, h = camera
l, t, _, _ = -l, -t, w, h
l = min(0, l) # stop scrolling at the left edge
l = max(-(camera.width-SCREEN_WIDTH), l) # stop scrolling at the right edge
t = max(-(camera.height-SCREEN_HEIGHT), t) # stop scrolling at the bottom
t = min(0, t) # stop scrolling at the top
return pygame.Rect(l, t, w, h)
def main ():
pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.display.set_caption("W40K")
grasstile = pygame.image.load('./textures/grass.png')
watertile = pygame.image.load('./textures/water.png')
waterbeach = pygame.image.load('./textures/dirt.png')
grassrect = grasstile.get_rect()
waterrect = watertile.get_rect()
waterb = waterbeach.get_rect()
TILESIZE = 40
tilemap = [
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,waterbeach,waterbeach,waterbeach,watertile,watertile,watertile,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[waterbeach,waterbeach,waterbeach,waterbeach,watertile,watertile,watertile,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,grasstile,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[watertile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[watertile,watertile,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,grasstile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,grasstile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
[grasstile,grasstile,waterbeach,waterbeach,watertile,watertile,waterbeach,waterbeach,grasstile,grasstile,waterbeach,watertile,watertile,watertile,waterbeach,waterbeach,waterbeach,grasstile,grasstile,waterbeach],
]
#Creates surface of the background
map_surface = pygame.Surface((len(tilemap[0])*TILESIZE, len(tilemap)*TILESIZE))
#Display the surface
for y,row in enumerate(tilemap):
for x,tile_surface in enumerate(row):
map_surface.blit(tile_surface,(x*TILESIZE,y*TILESIZE))
total_level_width = len(tilemap[0]) * 40
total_level_height = len(tilemap) * 40
camera = Camera(complex_camera,total_level_width, total_level_height)
#mouse = Mouse()
#allsprites = pygame.sprite.RenderPlain((mouse))
clock = pygame.time.Clock()
while 1:
clock.tick(60)
#Handle Input Events
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
screen.fill(BLACK)
#Camera moves.
camera.update()
#Display background.
screen.blit(map_surface, camera.apply(waterrect))
pygame.display.flip()
if __name__ == "__main__":
main()
pygame.quit()
I think that the problem is in the camara function and the apply function, but I have no clue to improve this and make the camara work properly.
Thanks!
It is hard for me to say exactly what is wrong with your sode ( i got too little experience in coding ) , but i just realised movin screen by mouse in my script and i did this :
screen=pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT),32)
Map=pygame.image.load(os.path.join('Image','MAP.png')).convert_alpha()
startX,startY=0,0 # starting coordinates for Map
while 1:
screen.fill(white)
screen.blit(Map,(startX,startY))
for event in pygame.event.get():
if event.type == MOUSEMOTION :
mousepos=pygame.mouse.get_pos()
if WINDOWHEIGHT-mousepos[1]<50: # if y-position of mouse is 50 pixel far from lower edge ..
startY -= 5 # ..move Map by 5 px
if mousepos [1]<50:
startY += 5
if mousepos [0]<50 :
startX += 5
if WINDOWWIDTH - mousepos[0]<50:
startX -= 5
pygame.display.flip()
Hope this can suggest you an idea how to improve your code
Related
Pygame: Sprites spawn close to each other [duplicate]
Right now, my game blits all the images in random positions correctly and also gets the rect of the images correctly, but I can´t figure out how to use colliderect to make sure the images don´t overlap. How could it work for my code? Also I´m trying to make the first text fade out and I don´t know why it doesn´t work for me. Here is the code: class GAME1: def __init__(self, next_scene): self.background = pygame.Surface(size) # Create an array of images with their rect self.images = [] self.rects = [] self.imagenes1_array = ['autobus.png','coche.png','barco.png','autobus2.png','grua.png','bici.png'] for i in self.imagenes1_array: # We divide in variables so we can then get the rect of the whole Img (i2) i2 = pygame.image.load(i) self.images.append(i2) s = pygame.Surface(i2.get_size()) r = s.get_rect() # Trying to use colliderect so it doesnt overlap if pygame.Rect.colliderect(r,r) == True: x = random.randint(300,1000) y = random.randint(200,700) self.rects.append(r) def start(self, gamestate): self.gamestate = gamestate for rect in self.rects: # Give random coordinates (we limit the dimensions (x,y)) x = random.randint(300,1000) y = random.randint(200,700) rect.x = x rect.y = y def draw(self,screen): self.background = pygame.Surface(size) font = pygame.font.SysFont("comicsansms",70) # First half (Show image to remember) text1 = font.render('¡A recordar!',True, PURPLE) text1_1 = text1.copy() # This surface is used to adjust the alpha of the txt_surf. alpha_surf = pygame.Surface(text1_1.get_size(), pygame.SRCALPHA) alpha = 255 # The current alpha value of the surface. if alpha > 0: alpha = max(alpha-4, 0) text1_1 = text1.copy() alpha_surf.fill((255, 255, 255, alpha)) text1_1.blit(alpha_surf, (0,0), special_flags = pygame.BLEND_RGBA_MULT) screen.blit(text1_1, (600,50)) # Second half (Show all similar images) text2 = font.render('¿Cuál era el dibujo?',True, PURPLE) #screen.blit(text2, (500,50)) for i in range(len(self.images)): #colliding = pygame.Rect.collidelistall(self.rects) screen.blit(self.images[i], (self.rects[i].x, self.rects[i].y)) def update(self, events, dt): for event in events: if event.type == pygame.MOUSEBUTTONDOWN: for rect in self.rects: if rect.collidepoint(event.pos): print('works!')
Use collidelist() to test test if one rectangle in a list intersects: for i in self.imagenes1_array: s = pygame.image.load(i) self.images.append(s) r = s.get_rect() position_set = False while not position_set: r.x = random.randint(300,1000) r.y = random.randint(200,700) margin = 10 rl = [rect.inflate(margin*2, margin*2) for rect in self.rects] if len(self.rects) == 0 or r.collidelist(rl) < 0: self.rects.append(r) position_set = True See the minimal example, that uses the algorithm to generate random not overlapping rectangles: import pygame import random pygame.init() window = pygame.display.set_mode((400, 400)) clock = pygame.time.Clock() def new_recs(rects): rects.clear() for _ in range(10): r = pygame.Rect(0, 0, random.randint(30, 40), random.randint(30, 50)) position_set = False while not position_set: r.x = random.randint(10, 340) r.y = random.randint(10, 340) margin = 10 rl = [rect.inflate(margin*2, margin*2) for rect in rects] if len(rects) == 0 or r.collidelist(rl) < 0: rects.append(r) position_set = True rects = [] new_recs(rects) run = True while run: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False elif event.type == pygame.KEYDOWN: new_recs(rects) window.fill(0) for r in rects: pygame.draw.rect(window, (255, 0, 0), r) pygame.display.flip() pygame.quit() exit()
How to delete a single pygame drawing from the screen?
When the big circle touches the little circles I want the little circle that it touched to disappear from the screen. However, I can't figure out how exactly you delete an individual drawing in pygame. How do I fix this issue? does pygame have this feature built-in? from pygame import * import random as rd import math as m init() screen = display.set_mode((800, 600)) p_1_x = 200 p_1_y = 200 p_1_change_x = 0 p_1_change_y = 0 def p_1(x, y): player_1 = draw.circle(screen, (0, 0, 0), (x, y), 15) def pick_up(x, y, xx, yy): distance = m.sqrt(m.pow(xx - x, 2) + m.pow(yy - y, 2)) if distance < 19: # I think the code to delete should go here pass dots = [] locations = [] for i in range(5): x = rd.randint(100, 700) y = rd.randint(100, 500) locations.append((x, y)) while True: screen.fill((255, 255, 255)) for events in event.get(): if events.type == QUIT: quit() if events.type == KEYDOWN: if events.key == K_RIGHT: p_1_change_x = 1 if events.key == K_LEFT: p_1_change_x = -1 if events.key == K_UP: p_1_change_y += 1 if events.key == K_DOWN: p_1_change_y -= 1 if events.type == KEYUP: if events.key == K_RIGHT or K_LEFT or K_UP or K_DOWN: p_1_change_x = 0 p_1_change_y = 0 p_1_x += p_1_change_x p_1_y -= p_1_change_y for i, locate in enumerate(locations): dot = draw.circle(screen, (0, 0, 0), locate, 5) dots.append(dot) for l in enumerate(locate): pick_up(p_1_x, p_1_y, locate[0], locate[1]) p_1(p_1_x, p_1_y) display.update()
Your code was so messy and hard to maintain, first I made 2 classes for Balls & Dots. I detect collision by pygame.Rect.colliderect, first I make 2 rectangle then I check the collision like this: def pick_up(ball, dot): ball_rect = Rect( ball.x - ball.SIZE , ball.y - ball.SIZE , ball.SIZE*2, ball.SIZE*2) dot_rect = Rect( dot.x - dot.SIZE , dot.y - dot.SIZE , dot.SIZE*2, dot.SIZE*2) if ball_rect.colliderect(dot_rect): return True return False If collision detects I remove it from dots array in the while loop: for dot in dots: if pick_up(ball, dot): # if dot in range ball dots.remove(dot) dot.draw() Here is the whole source: from pygame import * import random as rd SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 NUMBER_OF_DOTS = 5 class Ball(): SIZE = 15 def __init__(self, x, y): self.x = x self.y = y def draw(self): draw.circle(screen, (0, 0, 0), (self.x, self.y), Ball.SIZE) def move(self, vx, vy): self.x += vx self.y += vy class Dot(): SIZE = 5 def __init__(self, x, y): self.x = x self.y = y def draw(self): draw.circle(screen, (0, 0, 0), (self.x, self.y), Dot.SIZE) def pick_up(ball, dot): ball_rect = Rect( ball.x - ball.SIZE , ball.y - ball.SIZE , ball.SIZE*2, ball.SIZE*2) dot_rect = Rect( dot.x - dot.SIZE , dot.y - dot.SIZE , dot.SIZE*2, dot.SIZE*2) if ball_rect.colliderect(dot_rect): return True return False init() screen = display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) dots = [] ball = Ball(200,200) # generate dots for i in range(NUMBER_OF_DOTS): x = rd.randint(100, 700) y = rd.randint(100, 500) dots.append(Dot(x,y)) # the main game loop while True: screen.fill((255, 255, 255)) keys=key.get_pressed() for events in event.get(): keys=key.get_pressed() if events.type == QUIT: quit() if keys[K_RIGHT]: ball.move(+1,0) if keys[K_LEFT]: ball.move(-1,0) if keys[K_UP]: ball.move(0,-1) if keys[K_DOWN]: ball.move(0,+1) for dot in dots: dot.draw() if pick_up(ball, dot): dots.remove(dot) ball.draw() display.update() time.delay(1) # Speed down Update1: PyGame Rectangle Collision http://www.pygame.org/docs/ref/rect.html#pygame.Rect.colliderect Update2: I make a repo in the github and did some changes, Dots are colorful, new dot gets random color and the ball gets bigger whenever eats a dot. https://github.com/peymanmajidi/Ball-And-Dots-Game__Pygame
The code should delete it from the locations list so that it's not re-drawn in the future. You clear the screen each frame, so clearing + not-redrawing is "deleting". Say you modified pick_up() to simply return True or False: def pick_up(x, y, xx, yy): result = False distance = m.sqrt(m.pow(xx - x, 2) + m.pow(yy - y, 2)) if distance < 19: result = True # It was picked return result Then as you iterate through the locations list drawing & checking for being picked, save the index of the picked circles, then remove them from the locations in a second step. Using the 2-step form means you don't have to worry about accidentally skipping items if you delete from the list as you iterate over it. p_1_x += p_1_change_x p_1_y -= p_1_change_y picked_up = [] # empty list to hold "picked" items for i, locate in enumerate(locations): dot = draw.circle(screen, (0, 0, 0), locate, 5) dots.append(dot) for l in enumerate(locate): if ( pick_up(p_1_x, p_1_y, locate[0], locate[1]) ): picked_up.append( i ) # save the index of anything "picked" # remove any picked-up circles from the list for index in sorted( picked_up, reverse=True ): # start with the highest index first print( "Removing circle from location[%d]" % ( index ) ) # DEBUG del( locations[ index ] )
My screen glitches out when I try to run my subprogram code
I'm working on a subprogram code that will make this happy face bounce around the screen and turn different colours. For some reason, the screen turns into that black glitchy screen and when I press exit at the top the face shows for a quick second before the program shuts down. I can't figure out why this is, here is my code and I've included a picture of what happens at first when I run it: """ Program to show a very basic function Most of the program is exactly the same as other programs we have done The main difference is the grouping of code into a function called drawHappy() to draw a few shapes together In the main loop we "call" this function whenever we want to draw this group of shapes """ # import the necessary modules import pygame import sys import math import random from random import randint # initialize pygame pygame.init() # set the size for the surface (screen) # note this screen is resizable by the user screen = pygame.display.set_mode((800, 600), pygame.RESIZABLE) # set the caption for the screen pygame.display.set_caption("Happy Face") #screen width and height screenW = screen.get_width() screenH = screen.get_height() # define colours you will be using WHITE = (255, 255, 255) GREEN = (0, 255, 0) RED = (255, 0, 0) BLUE = (0, 0, 255) BLACK = (0, 0, 0) YELLOW = (255, 255, 0) # funtion to draw a the "happy face" # it has 4 parameters passed to it xPos, yPos, radius, and colour # notice all the shapes are drawn "relative" to the xPos and yPos and the radius def drawHappy(xPos,yPos,r,colour): pygame.draw.circle(screen,colour,(xPos,yPos),r,1) eyeRadius = int(1/6*r) eyeX = int(xPos-1/3*r) eyeY = int(yPos- 1/3*r) pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1) eyeX = int(xPos + 1/3*r) pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1) wMouth = 1.5*r xMouth = xPos - 3/4*r yMouth = yPos - 3/4*r pygame.draw.arc(screen,colour,(xMouth,yMouth,wMouth,wMouth),math.pi,2*math.pi,1) randomR = randint(1,300) r = randomR randomX = randint(r, 800-r) randomY = randint(r, 600-r) dx = 0 dy = 0 x = 100 y = 100 speed = 3 x2 = randomX y2 = randomY dx2 = speed dy2 = -speed colour_list = [YELLOW, BLACK, BLUE, RED, GREEN] randomcolour = random.choice(colour_list) colour = RED # set up clock to control frames per second clock = pygame.time.Clock() FPS = 120 # set main loop to True so it will run main = True # main loop while main: for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.) if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen main = False # set the "main" variable to False to exit while loop clock.tick(FPS) screen.fill(WHITE) oldx = x oldy = y x += dx y += dy if x >= 800-r or x <= 0+r: x = oldx if y >= 600-r or y <= 0+r: y = oldy x2 += dx2 y2 += dy2 if x >= 800-r or x <= 0+r: dx2 = -dx2 randomcolour = random.choice(colour_list) colour = randomcolour if y2 >= 600-r or y2 <= 0+r: dy2 = -dy2 randomcolour = random.choice(colour_list) colour = randomcolour # "call" the function "drawHappy()" to draw the happy face # this is where we would normally do a pygame.draw or a screen.blit() # we are "passing" the function 4 values to use(x,y,radius, colour) # it will use these to know where to draw the happy face drawHappy(x2,y2,r,colour) pygame.display.flip() # quit pygame and exit the program (i.e. close everything down) pygame.quit() sys.exit()
First of all, you need to call your draw function inside the loop. Your current code shows only a glimpse of "drawing" because it gets executed once you exit the main loop. So, put your drawHappy() inside of main loop: while main: for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.) if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen main = False # set the "main" variable to False to exit while loop drawHappy(x2,y2,r,colour) pygame.display.update() clock.tick(FPS) screen.fill(WHITE) Now you will get a random size "smiley" on the screen, But now it will move on exit only, for the same reason it wouldn't display earlier. Next thing is to make it bounce (move). For this you'll need some kind of update of the coordinates, just like you did in the last part of your code, except they also need to be updated during the loop, not after it. I suggest making a Class because then it will be easier to manipulate the object. Also, I found it easier to separate draw and update_coordinates code into separate functions and them call them from main loop for example. Hope this helps, and if you need more help, ask. Here, I made a quick solution using parts of your code, there is plenty room for improvement especially for update_smiley_position() method where you can control how "smiley" moves. Also, if you need multiple objects, a list should be passed instead of single object. import pygame as pg import math import random pg.init() clock = pg.time.Clock() window = pg.display.set_mode((800, 600), pg.RESIZABLE) pg.display.set_caption("Happy Face") SCREEN_W = window.get_width() SCREEN_H = window.get_height() class Smiley: def __init__(self, x, y, r, color): self.x = x self.y = y self.r = r self.color = color self.create_smiley() def create_smiley(self): self.eye_radius = int(1/6 * self.r) self.eye_x1 = int(self.x - 1/3 * self.r) self.eye_x2 = int(self.x + 1/3 *self.r) self.eye_y = int(self.y - 1/3 *self.r) self.mouth_width = 1.5 * self.r self.mouth_x = self.x - self.r * 0.75 self.mouth_y = self.y - self.r * 0.75 def draw_smiley(self, win): pg.draw.circle(win, self.color, (self.x, self.y), self.r, 1) pg.draw.circle(win, self.color, (self.eye_x1, self.eye_y), self.eye_radius, 1) pg.draw.circle(win, self.color, (self.eye_x2, self.eye_y), self.eye_radius, 1) pg.draw.arc(win, self.color, (self.mouth_x, self.mouth_y, self.mouth_width, self.mouth_width), math.pi, 2*math.pi, 1) def update_smiley_position(self): if self.x >= SCREEN_H - self.r or self.x <= 0 + self.r: self.x = random.randint(100, 400) else: self.x += 5 if self.y >= SCREEN_W - self.r or self.y <= 0 + self.r: self.y = random.randint(100, 400) else: self.y -= 5 self.create_smiley() def draw(win, smiley): win.fill(pg.Color("white")) smiley.draw_smiley(win) smiley.update_smiley_position() pg.display.update() def main_loop(win, smiley): clock.tick(30) for event in pg.event.get(): if event.type == pg.QUIT: return False draw(win, smiley) return True r = random.randint(1, 300) x = random.randint(r, SCREEN_W - r) y = random.randint(r, SCREEN_H - r) smiley = Smiley(x, y, r, pg.Color("red")) while main_loop(window, smiley): pass pg.quit()
Pygame. How to make a rect change direction on collision (boundary check)
Part of an assignment I'm working on is making a ball bounce around the screen, I can make it move, but my boundary test doesn't seem to be working: the ball simply moves in direction instead of changing direction. So to clarify, what I want to ball to do is change direction as it hits the screen edge. import sys import pygame SCREEN_SIZE = 750, 550 BALL_DIAMETER = 16 BALL_RADIUS = BALL_DIAMETER // 2 MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER BLACK = (0, 0, 0) WHITE = (255, 255, 255) LEFT = 11 RIGHT = 12 pygame.init() clock = pygame.time.Clock() pygame.display.init() font = pygame.font.SysFont("impact", 20) pygame.display.set_caption("Breakout") screen = pygame.display.set_mode(SCREEN_SIZE) class Ball: def __init__(self): ''' ''' self.ball = pygame.Rect(300, 730 - BALL_DIAMETER, BALL_DIAMETER, BALL_DIAMETER) # Draw ball def draw_ball(self): pygame.draw.circle(screen, WHITE, (self.ball.left + BALL_RADIUS, self.ball.top + BALL_RADIUS), BALL_RADIUS) # Updates the coordinates by adding the speed components def move_ball(self, x, y): self.xspeed = x self.yspeed = y self.ball = self.ball.move(self.xspeed, self.yspeed) # bounds check if self.ball.left <= 0: self.ball.left = 0 self.xspeed = -self.xspeed elif self.ball.left >= MAX_BALL_X: self.ball.left = MAX_BALL_X self.xspeed = -self.xspeed if self.ball.top < 0: self.ball.top = 0 self.yspeed = -self.yspeed elif self.ball.top >= MAX_BALL_Y: self.ball.top = MAX_BALL_Y self.yspeed = -self.yspeed # shows a message on screen, for testing purposes class Text: def show_message(self, message): self.font = pygame.font.SysFont("impact", 20) font = self.font.render(message,False, WHITE) screen.blit(font, (200, 400)) class Game: def __init__(self): ''' ''' def run(self): b = Ball() while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() keys = pygame.key.get_pressed() # fps lock, screen fill and method call for input clock.tick(60) screen.fill(BLACK) b.draw_ball() b.move_ball(5, -5) # used to keep track of various elements # Text().show_message("P: " + str(p)) pygame.display.flip() # Creates instance of the game class, and runs it if __name__ == "__main__": Game().run()
Your only call to move_ball uses a constant vector. Since you never change the call parameters, the ball moves only that way. b.move_ball(5, -5) Yes, you change the vector components within move_ball when you hit a wall. However, on the next call, you change them back to the original values and move the ball in the original direction. You have to initialize the vector outside move_ball, and then let the routine access the existing vector when it's called.
Python Pygame randomly draw non overlapping circles
Im very new to python and seem to be missing something. I want to randomly draw circles on a pygame display but only if the circles don't overlap each other. I believe I must find the distance between all circle centers and only draw it if the distance is bigger than circle radius * 2. I've tried many different things but all without success, I always get the same result - circles drawn overlapping. #!/usr/bin/env python import pygame, random, math red = (255, 0, 0) width = 800 height = 600 circle_num = 10 tick = 2 speed = 5 pygame.init() screen = pygame.display.set_mode((width, height)) class circle(): def __init__(self): self.x = random.randint(0,width) self.y = random.randint(0,height) self.r = 100 def new(self): pygame.draw.circle(screen, red, (self.x,self.y), self.r, tick) c = [] for i in range(circle_num): c.append('c'+str(i)) c[i] = circle() for j in range(len(c)): dist = int(math.hypot(c[i].x - c[j].x, c[i].y - c[j].y)) if dist > int(c[i].r*2 + c[j].r*2): c[j].new() pygame.display.update() else: continue while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit()
You did not check against all other circles. I added a variable shouldprint which gets set to false if any other circle is too close. import pygame, random, math red = (255, 0, 0) width = 800 height = 600 circle_num = 20 tick = 2 speed = 5 pygame.init() screen = pygame.display.set_mode((width, height)) class circle(): def __init__(self): self.x = random.randint(0,width) self.y = random.randint(0,height) self.r = 100 def new(self): pygame.draw.circle(screen, red, (self.x,self.y), self.r, tick) c = [] for i in range(circle_num): c.append('c'+str(i)) c[i] = circle() shouldprint = True for j in range(len(c)): if i != j: dist = int(math.hypot(c[i].x - c[j].x, c[i].y - c[j].y)) if dist < int(c[i].r*2): shouldprint = False if shouldprint: c[i].new() pygame.display.update() while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit()
The for loop has been changed to a while loop. It will keep trying to generate circles until the target number is reached. A circle is first generated. Then, it checks if it intersects with any existing circle using the formula from this answer. It iterates through every existing circle (store in the list circles) and performs the check using the formula. any() returns True if the formula evaluates to True for any iteration. If it's True, it means it found an intersection. Thus, it continues to the next iteration to try again with a new circle. circles = [] while len(circles) < circle_num: new = circle() if any(pow(c.r - new.r, 2) <= pow(c.x - new.x, 2) + pow(c.y - new.y, 2) <= pow(c.r + new.r, 2) for c in circles): continue circles.append(new) new.new() pygame.display.update()