New to pygame, and game development in general.
This is my main loop and I am trying to blit just a tile selector image on top of the current tile that the mouse is pointing to by using collisionpoint detection. However, as seen in the picture, it partially select everything around the tile I am pointing at. Attached pics are examples of what happens and the selector tile I am using. I am not sure how to adjust the mouse coordinates appropriately and would love advice on what to do with it.
image_list[3] points to the image class that contains details about the selector tile.
def loop(display, screen, image_list, map_data):
# initialise conditions used in loop
display_mouse_coords = False
player_in_grasslands = True
font = pygame.font.Font(pygame.font.get_default_font(), 13)
while True:
display.fill(constants.COLOURS["BLACK"])
# display map
for y, row in enumerate(map_data):
for x, tile in enumerate(row):
if tile != 0:
tile_rect = display.blit(image_list[tile - 1].img_surf, (150 + x * 10 - y * 10, 100 + x * 5 + y * 5))
pos = pygame.mouse.get_pos()
# take the mouse position and scale it, too
screen_rect = screen.get_rect()
display_rect = display.get_rect()
ratio_x = (screen_rect.width / display_rect.width)
ratio_y = (screen_rect.height / display_rect.height)
scaled_pos = (pos[0] / ratio_x, pos[1] / ratio_y)
if tile_rect.collidepoint(scaled_pos):
display.blit(image_list[3].img_surf, tile_rect)
Here are the textures that I used for this example (respectively cursor.png
, right.png
, top.png
, left.png
:
You can use the code below for a working example of pygame.mask
:
import pygame
from pygame.locals import *
class Map:
def __init__(self, width, height, origin):
self.top_image = pygame.image.load('top.png')
self.left_image = pygame.image.load('left.png')
self.right_image = pygame.image.load('right.png')
self.cursor = pygame.image.load('cursor.png')
# create the Mask for the top image
# (only activate when the selected pixel is non-transparent)
self.top_image_mask = pygame.mask.from_surface(self.top_image)
origin[0] -= 20*width
origin[1] += 5*height
self.map = []
for x in range(width):
for y in range(height):
tile_x, tile_y = origin
tile_x += 20*x + 20*y
tile_y += 10*y - 10*x
self.map.append(Tile(tile_x, tile_y))
# draw the sides textures if needed
if not x:
self.map[-1].left = True
if y+1 == height:
self.map[-1].right = True
def update(self):
for tile in self.map:
tile.update()
def draw(self):
for tile in sorted(self.map, key=lambda tile: tile.selected):
tile.draw()
class Tile:
def __init__(self, x, y):
self.x = x
self.y = y
# make rect to check for rough collision
self.rect = pygame.Rect(x, y, 40, 48)
self.left = False
self.right = False
self.selected = False
def update(self):
self.selected = False
x, y = pygame.mouse.get_pos()
if self.rect.collidepoint((x, y)):
# the mask needs relative coordinates
x -= self.rect.x
y -= self.rect.y
if map.top_image_mask.get_at((x, y)):
self.selected = True
def draw(self):
pos = self.rect.x, self.rect.y
screen.blit(map.top_image, pos)
if self.left:
screen.blit(map.left_image, pos)
if self.right:
screen.blit(map.right_image, pos)
if self.selected:
screen.blit(map.cursor, pos)
pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
map = Map(15, 15, [320, 100])
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
screen.fill((230, 250, 255))
map.update()
map.draw()
pygame.display.flip()
clock.tick(60) # limit the framerate
Screenshot of the example:
It namely uses classes to store the tiles, which are generated by Map.__init__
according to an original position, a width
and height
values. The code in Map.__init__
determines the position of each tile, stored as a Tile
object. They sometimes get the order to draw the left or right textures, to make the map look more 3D-ish.
But the most important part is the use of the Map.top_image_mask
variable. This is a mask created from the Map.top_image
Surface
. Its get_at
function is used, which returns True
if the relative point in the mask is opaque, or False
if not. For example, with this image, mask.get_at((0, 0))
will return False
, because the pixel on the very top left of the image is transparent, but it will return True
if you ask for mask.get_at((20, 10))
for example.
I make sure to check for a pygame.Rect
collision before checking the mask, because calling mask.get_at
with a point outside of the texture will return an error. The first step is then to check if the absolute mouse position collides with the rect of the texture of a specific tile, then if it "touches" it, check if the touched pixel is not transparent by the use of relative mouse coordinates and the mask.get_at
function.
I hope that helps.