python - 在 Pygame 中获取等距平铺鼠标选择

标签 python pygame tiles isometric

我没能正确地计算出这个数学,而且用语言解释起来有点困难。我成功地创建了一个等距网格,您可以用鼠标完美地选择图 block ,并且我成功地使用 was 实现相机移动d 键,仍然可以正确选择图 block ,但是有一个小错误,我无法弄清楚它来自哪里。 这就是发生的情况,但只是有时,取决于相机偏移的位置:

当这种情况发生时,它仅发生在 x 轴上,而不是在每个图 block 中。 我几乎要放弃这个了,因为我找不到这个错误,想在这里发帖看看是否有人遇到类似的问题。

import time
import pygame
import sys
import math
from os import path
from settings import *
from sprites import *

# ------------------------- SETTINGS ---------------------------- #
# COLORS (r, g, b)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
DARKGREY = (40, 40, 40)
LIGHTGREY = (100, 100, 100)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

# game settings
WIDTH = 1024
HEIGHT = 768
FPS = 60
title = "Isometric-Based game"
BGCOLOUR = DARKGREY

TILE_X = 80
TILE_Y = 40

WORLD_X, WORLD_Y = 14, 10
ORIGIN_X, ORIGIN_Y = 5, 1

# Debug
pygame.init()
font = pygame.font.Font(None, 25)

CAMERA_SPEED = 300


def get_info(info_list):
    display_surface = pygame.display.get_surface()
    for i, key in enumerate(info_list):
        text = font.render(str(key) + " : " + str(info_list[key]), True, (255, 255, 255), (0, 0, 0))
        text_rect = text.get_rect()
        text_rect.y = 20 * i
        display_surface.blit(text, text_rect)


# ------------------------- SPRITES ---------------------------- #

class Camera:
    def __init__(self, game, x, y):
        self.game = game
        self.x, self.y = self.game.to_screen(x, y)
        self.vx, self.vy = 0, 0

    def update(self):
        self.get_keys()
        self.x += self.vx * self.game.dt
        self.y += self.vy * self.game.dt

    def get_keys(self):
        self.vx, self.vy = 0, 0
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            self.vy = -CAMERA_SPEED
        if keys[pygame.K_s]:
            self.vy = CAMERA_SPEED
        if keys[pygame.K_a]:
            self.vx = -CAMERA_SPEED
        if keys[pygame.K_d]:
            self.vx = CAMERA_SPEED
        if self.vx != 0 and self.vy != 0:
            self.vx *= 1.0
            self.vy *= 0.50


class MouseSelection:
    def __init__(self, game, image):
        self.game = game
        self.image = image

    def update(self):
        # get mouse x and y
        self.mouse_x, self.mouse_y = pygame.mouse.get_pos()

        # get the mouse offset position inside the tile
        self.offset_x, self.offset_y = self.mouse_x % TILE_X, self.mouse_y % TILE_Y
        self.offset_x += self.game.scroll_x % TILE_X  # Add camera scroll to offset
        self.offset_y += self.game.scroll_y % TILE_Y

        # get the cell number
        self.cell_x, self.cell_y = (self.mouse_x // TILE_X), (self.mouse_y // TILE_Y)
        self.cell_x += int((self.game.scroll_x // TILE_X))  # Add camera scroll to cell
        self.cell_y += int((self.game.scroll_y // TILE_Y))

        # get the selected cell in iso grid
        self.selected_x = (self.cell_y - ORIGIN_Y) + (self.cell_x - ORIGIN_X)
        self.selected_y = (self.cell_y - ORIGIN_Y) - (self.cell_x - ORIGIN_X)

        # height and width of a quarter of a tile, select the corner of the tile to nodge to a direction
        h, w = TILE_Y / 2, TILE_X / 2
        if self.offset_y < (h / w) * (w - self.offset_x):
            self.selected_x -= 1
        if self.offset_y > (h / w) * self.offset_x + h:
            self.selected_y += 1
        if self.offset_y < (h / w) * self.offset_x - h:
            self.selected_y -= 1
        if self.offset_y > (h / w) * (2 * w - self.offset_x) + h:
            self.selected_x += 1

        # translate the selected cell to world coordinate
        self.selectedWorld_x, self.selectedWorld_y = self.game.to_screen(self.selected_x, self.selected_y)

    def draw(self):
        # Draw the selected tile with the camera scroll offset
        self.game.screen.blit(self.image, (self.selectedWorld_x - self.game.scroll_x,
                                           self.selectedWorld_y - self.game.scroll_y))


class SpriteSheet:
    def __init__(self, image):
        self.image = image
        self.frames = []

    def get_image(self):
        for row in range(2):
            for col in range(4):
                if row == 0:
                    image = pygame.Surface((TILE_Y, TILE_Y / 2)).convert_alpha()
                    image.blit(self.image, (0, 0), (col * TILE_X / 2, row * TILE_Y / 2, TILE_X, TILE_Y))
                    image = pygame.transform.scale(image, (TILE_X, TILE_Y))
                else:
                    image = pygame.Surface((TILE_Y, TILE_Y)).convert_alpha()
                    image.blit(self.image, (0, 0), (col * TILE_X / 2, row * TILE_Y / 2, TILE_X, TILE_Y * 2))
                    image = pygame.transform.scale(image, (TILE_X, TILE_Y * 2))
                image.set_colorkey(WHITE)
                self.frames.append(image)
        return self.frames


# ------------------------- GAME LOOP ---------------------------- #
class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption(title)
        self.clock = pygame.time.Clock()
        pygame.key.set_repeat(400, 100)
        self.debug = {}
        self.sprite_sheet_image = pygame.image.load("isometric_whitebg - Copy.png")
        self.index = 1
        self.scroll_x, self.scroll_y = 0, 0

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.sprite_sheet = SpriteSheet(self.sprite_sheet_image)
        self.tile_selected = self.sprite_sheet.get_image()[0]
        self.tiles = self.sprite_sheet.get_image()
        self.mouse_selection = MouseSelection(self, self.tile_selected)
        self.camera = Camera(self, 1, 1)

    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pygame.quit()
        sys.exit()

    def update(self):
        # update portion of the game loop
        self.camera.update()
        self.mouse_selection.update()
        self.mx, self.my = pygame.mouse.get_pos()

        # -------------------------------------------------- CAMERA SCROLLING ----------------------------------------#
        if self.camera.x - self.scroll_x != WIDTH / 2:
            self.scroll_x += (self.camera.x - (self.scroll_x + WIDTH / 2)) / 10
        if self.camera.y - self.scroll_y != HEIGHT / 2:
            self.scroll_y += (self.camera.y - (self.scroll_y + HEIGHT / 2)) / 10
        # -------------------------------------------------- CAMERA SCROLLING ----------------------------------------#

        self.debug_info()

    def to_screen(self, x, y):
        screen_x = (ORIGIN_X * TILE_X) + (x - y) * (TILE_X / 2)
        screen_y = (ORIGIN_Y * TILE_Y) + (x + y) * (TILE_Y / 2)
        return screen_x, screen_y

    def draw_world(self):
        for y in range(WORLD_Y):
            for x in range(WORLD_X):
                vWorld_x, vWorld_y = self.to_screen(x, y)
                # Invisible tile
                if self.index == 0:
                    self.screen.blit(self.tiles[1], (vWorld_x, vWorld_y))
                # Grass
                elif self.index == 1:
                    self.screen.blit(self.tiles[2], (vWorld_x - self.scroll_x, vWorld_y - self.scroll_y))

    def draw(self):
        self.screen.fill(BGCOLOUR)
        self.draw_world()
        self.mouse_selection.draw()

        get_info(self.debug)
        pygame.display.flip()

    def debug_info(self):
        self.debug["FPS"] = int(self.clock.get_fps())
        self.debug["Cell"] = self.mouse_selection.cell_x, self.mouse_selection.cell_y
        self.debug["Selected"] = int(self.mouse_selection.selected_x), int(self.mouse_selection.selected_y)
        self.debug["Scroll"] = int(self.scroll_x), int(self.scroll_y)
        self.debug["Mouse"] = int(self.mx), int(self.my)
        self.debug["Mouse_offset"] = int(self.mouse_selection.offset_x), int(self.mouse_selection.offset_y)

    def events(self):
        # catch all events here
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.quit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    pass


game = Game()
while True:
    game.new()
    game.run()

最佳答案

定义 map 的角点:

map_outline = [
    pygame.math.Vector2(left_x, left_y), 
    pygame.math.Vector2(top_x, top_y),
    pygame.math.Vector2(right_x, right_y,
    pygame.math.Vector2(bottom_x, bottom_y)
]

利用此信息,您可以计算 map 的 x 轴和 y 轴:

origin = map_outline[0]
x_axis = (map_outline[1] - map_outline[0]) / columns
y_axis = (map_outline[3] - map_outline[0]) / rows

您可以使用 x 轴和 y 轴来计算 map 中的点作为行和列的函数:

def transform(p, mat2x2):
    x = p[0] * mat2x2[0][0] + p[1] * mat2x2[1][0]
    y = p[0] * mat2x2[0][1] + p[1] * mat2x2[1][1]
    return pygame.math.Vector2(x, y)

p_position = transform((column + 0.5, row + 0.5), (x_axis, y_axis)) + origin

如果你想根据鼠标光标获取行和列,则需要执行相反的操作。您需要计算inverse 2x2 matrix从 x 轴和 y 轴。使用逆矩阵,您可以计算作为 map 上点的函数的行和列:

def inverseMat2x2(m):
    a, b, c, d = m[0].x, m[0].y, m[1].x, m[1].y
    det = 1 / (a*d - b*c)
    return [(d*det, -b*det), (-c*det, a*det)]

m_pos = pygame.mouse.get_pos()
m_grid_pos = transform(pygame.math.Vector2(m_pos) - origin, point_to_grid)
m_col, m_row = int(m_grid_pos[0]), int(m_grid_pos[1])

另请参阅PyGameExamplesAndAnswers - Isometric


最小示例:

replit.com/@Rabbid76/Pygame-IsometircMap

import pygame

pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()

colors = {'g': (40, 128, 40), 'd': (90, 60, 40)}
tilemap = [
    'gdddg',
    'dgddd',
    'ggddg',
    'ggddg',
    'ddddg',
    'dgggd'
]
columns, rows = len(tilemap[0]), len(tilemap)

isometric_tiles = {}
for key, color in colors.items():
    tile_surf = pygame.Surface((50, 50), pygame.SRCALPHA)
    tile_surf.fill(color)
    tile_surf = pygame.transform.rotate(tile_surf, 45)
    isometric_size = tile_surf.get_width()
    tile_surf = pygame.transform.scale(tile_surf, (isometric_size, isometric_size//2))
    isometric_tiles[key] = tile_surf
tile_size = (isometric_size, isometric_size//2)

def tileRect(column, row, tile_size):
    x = (column + row) * tile_size[0] // 2
    y = ((columns - column - 1) + row) * tile_size[1] // 2 
    return pygame.Rect(x, y, *tile_size)

game_map = pygame.Surface(((columns+rows) * isometric_size // 2, (columns+rows) * isometric_size // 4), pygame.SRCALPHA)
for column in range(columns):
    for row in range(rows):
        tile_surf = isometric_tiles[tilemap[row][column]]
        tile_rect = tileRect(column, row, tile_size)
        game_map.blit(tile_surf, tile_rect)

map_rect = game_map.get_rect(center = window.get_rect().center)
map_outline = [
    pygame.math.Vector2(0, columns * isometric_size / 4), 
    pygame.math.Vector2(columns * isometric_size / 2, 0),
    pygame.math.Vector2((columns+rows) * isometric_size // 2, rows * isometric_size / 4),
    pygame.math.Vector2(rows * isometric_size / 2, (columns+rows) * isometric_size // 4)
]
for pt in map_outline:
   pt += map_rect.topleft 

origin = map_outline[0]
x_axis = (map_outline[1] - map_outline[0]) / columns
y_axis = (map_outline[3] - map_outline[0]) / rows

def inverseMat2x2(m):
    a, b, c, d = m[0].x, m[0].y, m[1].x, m[1].y
    det = 1 / (a*d - b*c)
    return [(d*det, -b*det), (-c*det, a*det)]

point_to_grid = inverseMat2x2((x_axis, y_axis))

def transform(p, mat2x2):
    x = p[0] * mat2x2[0][0] + p[1] * mat2x2[1][0]
    y = p[0] * mat2x2[0][1] + p[1] * mat2x2[1][1]
    return pygame.math.Vector2(x, y)
    
font = pygame.font.SysFont(None, 30)
textO = font.render("O", True, (255, 255, 255))
textX = font.render("X", True, (255, 0, 0))
textY = font.render("Y", True, (0, 255, 0))

p_col, p_row = 2, 2

run = True 
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_a and p_col > 0:
                p_col -= 1
            if event.key == pygame.K_d and p_col < columns-1:
                p_col += 1
            if event.key == pygame.K_w and p_row > 0:
                p_row -= 1
            if event.key == pygame.K_s and p_row < rows-1:
                p_row += 1

    p_position = transform((p_col + 0.5, p_row + 0.5), (x_axis, y_axis)) + origin
    m_pos = pygame.mouse.get_pos()
    m_grid_pos = transform(pygame.math.Vector2(m_pos) - origin, point_to_grid)
    m_col, m_row = int(m_grid_pos[0]), int(m_grid_pos[1])
        
    window.fill((0, 0, 0))
    window.blit(game_map, map_rect)
    pygame.draw.lines(window, (164, 164, 164), True, map_outline, 3)
    pygame.draw.line(window, (255, 0, 0), origin, origin+x_axis, 3)
    pygame.draw.line(window, (0, 255, 0), origin, origin+y_axis, 3)
    pygame.draw.circle(window, (255, 255, 255), origin, 5)
    pygame.draw.circle(window, (255, 0, 0), origin+x_axis, 5)
    pygame.draw.circle(window, (0, 255, 0), origin+y_axis, 5)
    window.blit(textO, textO.get_rect(topright = origin+(-5, 5)))   
    window.blit(textX, textX.get_rect(bottomright = origin+x_axis+(-5, -5)))
    window.blit(textY, textX.get_rect(topright = origin+y_axis+(-5, 5))) 
    pygame.draw.ellipse(window, (255, 255, 0), (p_position[0]-16, p_position[1]-8, 32, 16))
    if 0 <= m_grid_pos[0] < columns and 0 <= m_grid_pos[1] < rows:
        tile_rect = tileRect(m_col, m_row, tile_size).move(map_rect.topleft)
        pts = [tile_rect.midleft, tile_rect.midtop, tile_rect.midright, tile_rect.midbottom]
        pygame.draw.lines(window, (255, 255, 255), True, pts, 4)
    pygame.display.update()

pygame.quit()
exit()

关于python - 在 Pygame 中获取等距平铺鼠标选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71336864/

相关文章:

php - 从 PHP 调用时 python 导入失败

python - NLTK : what is the correct lemma for "boss"? 中的 WordNet 词形还原器

python - 如何在 Mac 上将 Python 完全恢复为出厂设置

python - 跳得太快?

java - Spring Web MVC Tiles 属性 'header' 未找到

python - 是否可以创建具有可编辑默认值的输入?

python - 结合pygame和twisted

python - 为什么 Pygame 中的 Clock.tick() 不能准确测量时间?

java - 在 Spring 中创建可重用的 GUI 组件

tiles - Apache Tiles 2.1 - 如何防止继承的列表属性重复?