You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy/Python游戏地图块无法正确绘制问题求助

问题分析与解决

核心问题出在Tile坐标计算逻辑错误:你在绘制Tile时,把视口位置(viewport_pos)加到了绝对坐标上,而正确的做法应该是用Tile的绝对坐标减去视口位置,将其转换为Widget可视区域内的相对坐标。

当玩家移动后,viewport_pos会变成负数(比如玩家向右移动时,player.x减小,viewport_pos.x = player.x - 视口宽/2会是负数),此时加上viewport_pos会让Tile的绘制位置变成负数,超出了GameScreen Widget的显示范围(Widget的坐标从(0,0)开始),所以新生成的Tile无法被看到。

修改步骤

1. 修正Tile绘制坐标

在GameScreen.draw_chunks方法中,将Rectangle的pos计算从加法改为减法:

原代码:

Rectangle(pos=(
    (x * CHUNK_SIZE + i) * TILE_SIZE + self.viewport_pos[0],
    (y * CHUNK_SIZE + j) * TILE_SIZE + self.viewport_pos[1]),
    size=(TILE_SIZE, TILE_SIZE))

修改为:

Rectangle(pos=(
    (x * CHUNK_SIZE + i) * TILE_SIZE - self.viewport_pos[0],
    (y * CHUNK_SIZE + j) * TILE_SIZE - self.viewport_pos[1]),
    size=(TILE_SIZE, TILE_SIZE))

2. 优化帧率(可选但建议)

将ProceduralGenerationGameApp.build中的Clock调度间隔从1秒改为60帧每秒,提升流畅度:

原代码:

Clock.schedule_interval(game_screen.update, 1)# 1.0 / 60.0)

修改为:

Clock.schedule_interval(game_screen.update, 1.0 / 60.0)

3. 可选:修正移动方向(根据预期调整)

当前Player的移动逻辑可能和直觉相反(比如按左键实际向右移动),如果需要调整方向,可以修改Player类的移动方法:

def move_left(self):
    self.x -= self.speed  # 向左移动,x减小

def move_right(self):
    self.x += self.speed  # 向右移动,x增大

def move_up(self):
    self.y += self.speed  # 向上移动,y增大(Kivy y轴向上为正)

def move_down(self):
    self.y -= self.speed  # 向下移动,y减小

修改后的完整代码(关键部分已修正)

import kivy
import random

from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Ellipse, Line, Rectangle 
from kivy.uix.widget import Widget
from kivy.config import Config
from kivy.core.window import Window 
import enum

# Constants for the chunk size and map dimensions
CHUNK_SIZE = 48
TILE_SIZE = 16
MAP_WIDTH = 256
MAP_HEIGHT = 256

#**************************
#* Tile Int Enum Class *
#**************************
class TileEnum(enum.IntEnum):
    GRASS = 0
    DIRT = 1
    CONCRETE = 2
    ASPHALT = 3

# Class to represent the game map
class GameMap:
    def __init__(self, seed=None):
        self.seed = seed
        if seed is not None:
            random.seed(seed)
        self.chunks = {}
        self.first_chunk = False
    
    def generate_chunk(self, chunk_x, chunk_y):
        # Use the RNG to generate the terrain for this chunk
        terrain = []

        for x in range(0, CHUNK_SIZE):
            column = []
            for y in range(0, CHUNK_SIZE):
                column.append(random.randint(0, 3))
            terrain.append(column)
        
        return terrain
    
    def get_chunk(self, chunk_x, chunk_y):
        # Check if the chunk has already been generated
        if (chunk_x, chunk_y) in self.chunks:
            print("found it",chunk_x, chunk_y)
            return self.chunks[(chunk_x, chunk_y)]
        else:
            # Generate the chunk and store it in the chunk cache
            chunk = self.generate_chunk(chunk_x, chunk_y)
            self.chunks[(chunk_x, chunk_y)] = chunk
            print("made it",chunk_x,chunk_y)
            return chunk

# Class to represent the player
class Player:
    def __init__(self, pos=(0, 0)):
        self.x, self.y = pos
        self.speed = TILE_SIZE/2
    
    def move_left(self):
        self.x -= self.speed
    
    def move_right(self):
        self.x += self.speed
    
    def move_up(self):
        self.y += self.speed
    
    def move_down(self):
        self.y -= self.speed

class GameScreen(Widget):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.viewport_size = (TILE_SIZE*CHUNK_SIZE, TILE_SIZE*CHUNK_SIZE)
        self.viewport_pos = (0, 0)
        self.size = self.viewport_size

        self.map = GameMap(seed=123)
        self.player = Player((self.viewport_size[0]/2, self.viewport_size[1]/2))

        self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
        self._keyboard.bind(on_key_down=self._on_keyboard_down)

    def draw_chunks(self):
        # Determine the chunks that are currently in view
        viewport_left = int(self.viewport_pos[0] // (CHUNK_SIZE * TILE_SIZE))
        viewport_top = int(self.viewport_pos[1] // (CHUNK_SIZE * TILE_SIZE))
        viewport_right = int((self.viewport_pos[0] + self.viewport_size[0]) // (CHUNK_SIZE * TILE_SIZE))
        viewport_bottom = int((self.viewport_pos[1] + self.viewport_size[1]) // (CHUNK_SIZE * TILE_SIZE))

        print(viewport_left, viewport_top, viewport_right, viewport_bottom)
     
        # Iterate over the visible chunks and draw them
        for x in range(viewport_left, viewport_right + 1):
            for y in range(viewport_top, viewport_bottom + 1):
                chunk = self.map.get_chunk(x, y)
                #print(chunk)
                for i in range(len(chunk)):
                    for j in range(len(chunk[i])):
                        if chunk[i][j] == TileEnum.GRASS:
                            # Draw a green square for grass
                            with self.canvas:
                                Color(0.25, 0.75, 0.25)
                        elif chunk[i][j] == TileEnum.DIRT:
                            # Draw a brown square for dirt
                            with self.canvas:
                                Color(0.75, 0.5, 0.25)
                        elif chunk[i][j] == TileEnum.CONCRETE:
                            # Draw a gray square for concrete
                            with self.canvas:
                                Color(0.5, 0.5, 0.75)
                        elif chunk[i][j] == TileEnum.ASPHALT:
                            # Draw a black square for asphalt
                            with self.canvas:
                                Color(0.25, 0.25, 0.5)
                        with self.canvas:
                            Rectangle(pos=(
                                (x * CHUNK_SIZE + i) * TILE_SIZE - self.viewport_pos[0],
                                (y * CHUNK_SIZE + j) * TILE_SIZE - self.viewport_pos[1]),
                                size=(TILE_SIZE, TILE_SIZE))
    
    def draw_player(self):
        # Draw a circle for the player
        with self.canvas:
            Color(0, 0.5, 0)
            Ellipse(pos=(self.viewport_size[0]/2 - (TILE_SIZE/2), self.viewport_size[0]/2 - (TILE_SIZE/2)), size=(TILE_SIZE, TILE_SIZE))
    
    def update(self, dt):
        # Update the viewport position to keep the player centered
        self.viewport_pos = (self.player.x - self.viewport_size[0]/2, self.player.y - self.viewport_size[1]/2)
        print(self.viewport_pos)

        # Redraw the chunks and player
        self.canvas.clear()
        self.draw_chunks()
        self.draw_player()
    
    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        self._keyboard = None

    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        #print(keycode)
        if keycode[1] == 'left':
            self.player.move_left()
        elif keycode[1] == 'right':
            self.player.move_right()
        elif keycode[1] == 'up':
            self.player.move_up()
        elif keycode[1] == 'down':
            self.player.move_down()
        
# Main application class
class ProceduralGenerationGameApp(App):
    def build(self):
        
        self.title = "Procedural Generation Game"
        Config.set("graphics", "width", "768")
        Config.set("graphics", "height", "768")
        Config.set("graphics", "resizable", False)
        Config.set("graphics", "borderless", False)
        Config.set("graphics", "fullscreen", False)
        Config.set("graphics", "window_state", "normal")
        Config.set("graphics", "show_cursor", True)
        Config.write()

        window_width = Config.getint("graphics", "width")
        window_height = Config.getint("graphics", "height")

        # Create the game screen and schedule the update function to be called every frame
        game_screen = GameScreen()

        Window.size = (window_width, window_height)

        Clock.schedule_interval(game_screen.update, 1.0 / 60.0)
        
        return game_screen

if __name__ == "__main__":
    ProceduralGenerationGameApp().run()

内容的提问来源于stack exchange,提问作者HelpMeSomePlease

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:25:17