How to delete a single pygame drawing from the screen? - python
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 ] )
Related
How to generate my maze instantly so I don't have to watch it Generate?
So I'm creating a game and I'm using Recursive backtracking algorithm to create the maze, however, I don't want it to show the maze generation and just to instantly generate the maze. I'm unsure of how to actually do this though so any help would be appreciated, I've already tried not drawing the generated white part but that then doesn't create the maze. import pygame import random import time class Cell(object): def __init__(self, x, y, cell_size, screen, black, white, red, blue): # position in matrix self.x = x self.y = y # keeps track of which walls are still visible self.walls = [True, True, True, True] # checks if cell has been visited during generation self.generated = False # checks if cell is on path during solving self.on_path = False # checks if cell has been visited during solving self.visited = False self.cell_size = cell_size self.screen = screen self.black = black self.white = white self.red = red self.blue = blue def draw_cell(self): # coordinates on screen x = self.x * self.cell_size y = self.y * self.cell_size # draws a wall if it still exists if self.walls[0]: pygame.draw.line(self.screen, self.black, (x, y), (x + self.cell_size, y), 5) if self.walls[1]: pygame.draw.line(self.screen, self.black, (x, y + self.cell_size), (x + self.cell_size, y + self.cell_size), 5) if self.walls[2]: pygame.draw.line(self.screen, self.black, (x + self.cell_size, y), (x + self.cell_size, y + self.cell_size), 5) if self.walls[3]: pygame.draw.line(self.screen, self.black, (x, y), (x, y + self.cell_size), 5) # marks out white if generated during generation if self.generated: pygame.draw.rect(self.screen, self.white, (x, y, self.cell_size, self.cell_size)) class Maze: def __init__(self, screen, cell_size, rows, cols, white, black, red, blue): self.screen = screen self.cell_size = cell_size self.rows = rows self.cols = cols self.state = None self.maze = [] self.stack = [] self.current_x = 0 self.current_y = 0 self.row = [] self.neighbours = [] self.black = black self.white = white self.red = red self.blue = blue self.cell = None def on_start(self): # maintains the current state # maze matrix of cell instances self.maze = [] # stack of current cells on path self.stack = [] self.current_x, self.current_y = 0, 0 self.maze.clear() self.stack.clear() for x in range(self.cols): self.row = [] for y in range(self.rows): self.cell = Cell(x, y, self.cell_size, self.screen, self.black, self.white, self.red, self.blue) self.row.append(self.cell) self.maze.append(self.row) def in_bounds(self, x, y): return 0 <= x < self.cols and 0 <= y < self.rows def find_next_cell(self, x, y): # keeps track of valid neighbors self.neighbours = [] # loop through these two arrays to find all 4 neighbor cells dx, dy = [1, -1, 0, 0], [0, 0, 1, -1] for d in range(4): # add cell to neighbor list if it is in bounds and not generated if self.in_bounds(x + dx[d], y + dy[d]): if not self.maze[x + dx[d]][y + dy[d]].generated: self.neighbours.append((x + dx[d], y + dy[d])) # returns a random cell in the neighbors list, or -1 -1 otherwise if len(self.neighbours) > 0: return self.neighbours[random.randint(0, len(self.neighbours) - 1)] else: return -1, -1 def remove_wall(self, x1, y1, x2, y2): # x distance between original cell and neighbor cell xd = self.maze[x1][y1].x - self.maze[x2][y2].x # to the bottom if xd == 1: self.maze[x1][y1].walls[3] = False self.maze[x2][y2].walls[1] = False # to the top elif xd == -1: self.maze[x1][y1].walls[1] = False self.maze[x2][y2].walls[3] = False # y distance between original cell and neighbor cell xy = self.maze[x1][y1].y - self.maze[x2][y2].y # to the right if xy == 1: self.maze[x1][y1].walls[0] = False self.maze[x2][y2].walls[2] = False # to the left elif xy == -1: self.maze[x1][y1].walls[2] = False self.maze[x2][y2].walls[0] = False def create_maze(self): self.maze[self.current_x][self.current_y].generated = True # self.maze[self.current_x][self.current_y].draw_current() next_cell = self.find_next_cell(self.current_x, self.current_y) # checks if a neighbor was returned if next_cell[0] >= 0 and next_cell[1] >= 0: self.stack.append((self.current_x, self.current_y)) self.remove_wall(self.current_x, self.current_y, next_cell[0], next_cell[1]) self.current_x = next_cell[0] self.current_y = next_cell[1] # no neighbor, so go to the previous cell in the stack elif len(self.stack) > 0: previous = self.stack.pop() self.current_x = previous[0] self.current_y = previous[1] def main(): WIDTH, HEIGHT = 800, 800 CELL_SIZE = 40 ROWS, COLUMNS = int(HEIGHT / CELL_SIZE), int(WIDTH / CELL_SIZE) # color variables WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) BLUE = (0, 0, 255) # initialize pygame pygame.init() SCREEN = pygame.display.set_mode((WIDTH, HEIGHT)) SCREEN.fill(WHITE) pygame.display.set_caption("Maze Gen") CLOCK = pygame.time.Clock() FPS = 60 m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE) m.on_start() running = True while running: CLOCK.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False for i in range(m.cols): for j in range(m.rows): m.maze[i][j].draw_cell() m.create_maze() pygame.display.flip() if __name__ == "__main__": main() pygame.quit()
Call m.create_maze() in a loop before the application loop. Terminate the loop when len(m.stack) == 0: def main(): # [...] m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE) m.on_start() while True: m.create_maze() if len(m.stack) == 0: break running = True while running: CLOCK.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False for i in range(m.cols): for j in range(m.rows): m.maze[i][j].draw_cell() pygame.display.flip()
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()
Putting the scrolling camera in a mini-window in pygame
I'm trying to create a game where the action is shown in a little box within the main screen object, freeing up the surrounding space for text and menus and what-not. Since the map is larger than the allotted window, I coded a basic "camera" that follows the player around. It mostly works, but I'm having trouble "trimming off" the area outside of this window. Here's the relevant bits of code (EDITED to provide Working Example): import pygame, os, sys from pygame.locals import * pygame.init() RIGHT = 'RIGHT' LEFT = 'LEFT' UP = 'UP' DOWN = 'DOWN' class Camera(): def __init__(self, screen, x_ratio = 1, y_ratio = 1, x_offset = 0, y_offset = 0): self.screen = screen.copy() self.rec = self.screen.get_rect() self.rec.width *= x_ratio self.rec.height *= y_ratio self.x_offset = x_offset self.y_offset = y_offset def get_pos(self): return (self.x_offset - self.rec.x, self.y_offset - self.rec.y) def get_window(self): w = pygame.Rect(self.rec) w.topleft = (0 - self.rec.x, 0 - self.rec.y) return w def move(self, x, y): """Move camera into new position""" self.rec.x = x self.rec.y = y def track(self, obj): while obj.rec.left < self.rec.left: self.rec.x -= 1 while obj.rec.right > self.rec.right: self.rec.x += 1 while obj.rec.top < self.rec.top: self.rec.y -= 1 while obj.rec.bottom > self.rec.bottom: self.rec.y += 1 class Map: def __init__(self, width, height): self.width = width self.height = height self.rec = pygame.Rect(0,0,self.width,self.height) def draw(self, screen): pygame.draw.rect(screen, (200,200,200), self.rec) class Obj: def __init__(self, char, x = 0, y = 0, width = 0, height = 0): self.width = width self.height = height self.rec = pygame.Rect(x, y, width, height) self.cur_map = None self.timers = {} #Dummying in chars for sprites self.char = char self.x_dir = 1 self.y_dir = 1 self.speed = 1 self.moving = False def move(self): if self.x_dir != 0 or self.y_dir != 0: new_x = self.rec.x + (self.x_dir*self.speed) new_y = self.rec.y + (self.y_dir*self.speed) new_rec = pygame.Rect(new_x, new_y, self.width, self.height) #Keep movement within bounds of map while new_rec.left < self.cur_map.rec.left: new_rec.x += 1 while new_rec.right > self.cur_map.rec.right: new_rec.x -= 1 while new_rec.top < self.cur_map.rec.top: new_rec.y += 1 while new_rec.bottom > self.cur_map.rec.bottom: new_rec.y -= 1 self.rec = new_rec def set_dir(self, d): self.x_dir = 0 self.y_dir = 0 if d == LEFT: self.x_dir = -1 elif d == RIGHT: self.x_dir = 1 elif d == UP: self.y_dir = -1 elif d == DOWN: self.y_dir = 1 def set_moving(self, val = True): self.moving = val class Game: def __init__(self): self.screen_size = (800, 600) self.screen = pygame.display.set_mode(self.screen_size) self.map_screen = self.screen.copy() self.title = 'RPG' pygame.display.set_caption(self.title) self.camera = Camera(self.screen, 0.75, 0.75)#, 10, 75) self.fps = 80 self.clock = pygame.time.Clock() self.debug = False self.bg_color = (255,255,255) self.text_size = 18 self.text_font = 'Arial' self.text_style = pygame.font.SysFont(self.text_font, self.text_size) self.key_binds = {LEFT : [K_LEFT, K_a], RIGHT : [K_RIGHT, K_d], UP : [K_UP, K_w], DOWN : [K_DOWN, K_s], 'interact' : [K_RETURN, K_z], 'inventory' : [K_i, K_SPACE], 'quit' : [K_ESCAPE]} self.player = Obj('p', 0, 0, 10, self.text_size) def draw(self, obj): char = obj.char self.draw_text(char, obj.rec.x, obj.rec.y, screen = self.map_screen) def draw_text(self, text, x, y, color = (0,0,0), screen = None): textobj = self.text_style.render(text, 1, color) textrect = textobj.get_rect() textrect.x = x textrect.y = y if screen == None: """Use default screen""" self.screen.blit(textobj, textrect) else: screen.blit(textobj, textrect) def play(self): done = False cur_map = Map(800, 800) self.map_screen = pygame.Surface((cur_map.width, cur_map.height)) self.map_screen.fill(self.bg_color) bg = pygame.Surface((cur_map.width, cur_map.height)) cur_map.draw(bg) self.player.cur_map = cur_map while not done: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key in self.key_binds[LEFT]: self.player.set_dir(LEFT) self.player.set_moving() elif event.key in self.key_binds[RIGHT]: self.player.set_dir(RIGHT) self.player.set_moving() elif event.key in self.key_binds[UP]: self.player.set_dir(UP) self.player.set_moving() elif event.key in self.key_binds[DOWN]: self.player.set_dir(DOWN) self.player.set_moving() elif event.type == KEYUP: self.player.set_moving(False) if self.player.moving: self.player.move() self.camera.track(self.player) self.clock.tick() self.screen.fill(self.bg_color) self.map_screen.blit(bg, (0,0)) self.draw(self.player) pygame.draw.rect(self.map_screen, (0,0,0), self.camera.rec, 1) #self.screen.blit(self.map_screen, (0,0), [0 - self.camera.rec.x, 0 - self.camera.rec.y, self.camera.rec.width, self.camera.rec.height]) self.screen.blit(self.map_screen, self.camera.get_pos(), self.camera.get_window()) pygame.display.flip() game = Game() game.play() Moving the player past past the bounds of the camera's window causes the window to roll up completely and disappear. I tried adjusting the blitting coordinates, as advised earlier, but it seems to only change the direction in which the window rolls up.
From your updated code, the blitting coordinates for self.screen.blit(...) are still changing: self.camera.get_window() changes value because rec.x and rec.y are values referring to the player position within the map. Hence you should define a constant minimap coordinate, this should be the same as the camera offset. self.screen.blit(self.map_screen, (self.camera.x_offset,self.camera.y_offset), (*self.camera.get_pos(), self.camera.rec.width, self.camera.rec.height)) Change the Camera().get_pos() to: def get_pos(self): return (self.rec.x, self.rec.y) I believe I only changed the self.screen.blit(...) and stopped using or rewrote your Camera functions as you're confusing yourself with all the rec variables. To illustrate it working amend the Map().draw(screen) to: def draw(self, screen): pygame.draw.rect(screen, (200,200,200), self.rec) pygame.draw.circle(screen, (255, 255, 255), (50, 50), 20, 2) One tip as well don't draw the entire map at each loop, just the part that will be visible.
How can I mouse-scroll around a 2D tile map in pygame?
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
Scrolling camera
I've been tinkering with a scrolling camera that follows the player around and have got it to follow the player. The problem is that it moves slower than the player and because of this the player wont stay in the middle of the screen. I believe the problem is in the offset (cameraX) and have tried a few different values but haven't found any that work. Code: import pygame, sys, time, random, math from pygame.locals import * BACKGROUNDCOLOR = (255, 255, 255) WINDOWW = 800 WINDOWH = 600 PLAYERW = 66 PLAYERH = 22 FPS = 60 MOVESPEED = 3 YACCEL = 0.13 GRAVITY = 2 BLOCKSIZE = 30 pygame.init() screen = pygame.display.set_mode((WINDOWW, WINDOWH), 0, 32) mainClock = pygame.time.Clock() testLevel = [ (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,)] def createblock(length, height, color): tmpblock = pygame.Surface((length, height)) tmpblock.fill(color) tmpblock.convert() return tmpblock def terminate(): # Used to shut down the software pygame.quit() sys.exit() def add_level(lvl, bSize): # Creates the level based on a map (lvl) and the size of blocks bList = [] # List of every block bListDisp = [] # List of every block to display bTypeList = [] # List with corresponding type of block(wall, air, etc.) for y in range(len(lvl)): for x in range(len(lvl[0])): if lvl[y][x] == 0: # If the block type on lvl[y][x] is '0', write "air" down in the type list bTypeList.append("air") elif lvl[y][x] == 1: # If the block type on lvl[y][x] is '1', write "wall" down in the type list bTypeList.append("solid") bList.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block that is registered bListDisp.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block to display that is registered return bList, bListDisp, bTypeList player = pygame.Rect((WINDOWW/2), (WINDOWH - BLOCKSIZE*3), PLAYERW, PLAYERH) wallblock = createblock(BLOCKSIZE, BLOCKSIZE,(20,0,50)) lastTime = pygame.time.get_ticks() isGrounded = False vx = 0 vy = 0 allLevels = [testLevel] # A list containing all lvls(only one for now) maxLevel = len(allLevels) # Checks which level is the last currLevel = allLevels[0] # Current level(start with the first lvl) blockList, blockListDisp, blockTypeList = add_level(currLevel, BLOCKSIZE) # A list with every block and another list with the blocks types thrusters = True jumping = False falling = True while True: """COLLISION""" collision = False for i in range(len(blockTypeList)): if blockTypeList[i] == "solid": if player.colliderect(blockList[i]): collision = True if vx > 0 and not falling: player.right = blockListDisp[i].left vx = 0 print('Collide Right') if vx < 0 and not falling: player.left = blockListDisp[i].right vx = 0 print('Collide Left') if vy > 0: player.bottom = blockListDisp[i].top isGrounded = True falling = False vy = 0 print('Collide Bottom') if vy < 0: player.top = blockListDisp[i].bottom vy = 0 print('Collide Top') else: player.bottom += 1 if player.colliderect(blockList[i]): collision = True #isGrounded = True #falling = False player.bottom -= 1 if not collision: falling = True isGrounded = False # Input pressedKeys = pygame.key.get_pressed() # Checks which keys are being pressed timeDiff = pygame.time.get_ticks() - lastTime # Calculates time difference lastTime += timeDiff # Last time checked reset to current time # Shut-down if the ESC-key is pressed or the window is "crossed down" for event in pygame.event.get(): if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE: terminate() """X-axis control""" if pressedKeys[ord('a')]: vx = -MOVESPEED if pressedKeys[ord('d')]: vx = MOVESPEED if not pressedKeys[ord('d')] and not pressedKeys[ord('a')]: vx = 0 """Y-axis control""" # Controls for jumping if pressedKeys[ord('w')] and thrusters == True: vy -= YACCEL * timeDiff; # Accelerate along the y-xis when "jumping", but not above/below max speed if vy <= -4: vy = -4 isGrounded = False # You are airborne jumping = True # You are jumping if event.type == KEYUP: # If you let go of the "jump"-button, stop jumping if event.key == ord('w') and vy < 0 and not isGrounded: jumping = False falling = True player.x += vx player.y += vy cameraX = player.x - WINDOWW/2 # Gravity if not isGrounded or falling: vy += 0.3 if vy > 80: vy = 80 screen.fill(BACKGROUNDCOLOR) for i in range(len(blockTypeList)): if blockTypeList[i] == "solid": screen.blit(wallblock, (blockListDisp[i].x-cameraX, blockListDisp[i].y)) #blit the wall-block graphics pygame.draw.rect(screen, (0, 0, 0), player) pygame.display.update() mainClock.tick(FPS)
You don't apply the camera-offset to the player itself, only to the wallblocks. So change pygame.draw.rect(screen, (0, 0, 0), player) to pygame.draw.rect(screen, (0, 0, 0), player.move(-cameraX, 0)) Some more notes: Using three lists (blockList, blockListDisp, blockTypeList) to keep track of your level is way to complex, use a single list :-) Change your add_level to: # use a dict to keep track of possible level blocks, so adding new ones becomes simple types = {0: "air", 1: "solid"} def add_level(lvl, bSize): # Creates the level based on a map (lvl) and the size of blocks for y in range(len(lvl)): for x in range(len(lvl[0])): # no more if/elif yield types[lvl[y][x]], pygame.Rect((bSize * x), (bSize * y), bSize, bSize) your lists to: blocks = list(add_level(currLevel, BLOCKSIZE)) # a single list which holds the type and rect for each block of the level Then your collision detection can look like this: while True: """COLLISION""" collision = False for type, rect in blocks: # list contains a tuple of type, rect if type == "solid": if player.colliderect(rect): collision = True if vx > 0 and not falling: player.right = rect.left # now you can always use the rect directly instead of accesing other lists vx = 0 print('Collide Right') ... Also, the drawing code becomes simpler: for type, rect in blocks: if type == "solid": screen.blit(wallblock, rect.move(-cameraX, 0)) #blit the wall-block graphics pygame.draw.rect(screen, (0, 0, 0), player.move(-cameraX, 0)) In the long run, you may want to use a class instead of a tuple, but that's another topic.