Pygame碰撞异常:分数/生命值不更新及KeyError问题求助
Pygame开发问题排查:碰撞交互与KeyError修复
问题描述
玩家碰撞ID为163的金币或掉入陷阱时,分数与生命值既不在控制台也不在屏幕上更新,同时触发KeyError: id。已确认金币ID与Tiled地图及控制台输出一致,自行排查一周未找到原因。
原代码
主函数run
def run(self): player_right_frame = 0 # index to track members of right image list player_left_frame = 0 # index to track members of left image list player_jump_frame = 0 # index to track jumping image quit = False player_speed = 10 player_health = 100 #allows decrement of health of player player_points = 0 clock = pygame.time.Clock() direction = 'stand' # by default, sprite starts by standing ground_position = self.screen.get_height() - 355 x = 170 y = ground_position while not quit: self.redraw() points_image = self.font.render(f"Points: {player_points}", 1, white) health_image = self.font.render(f'Health: {player_health}', 1, yellow) self.screen.blit(points_image, (100, 5)) self.screen.blit(health_image, (200, 5)) for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: quit = True if event.key == K_RETURN: pause = False elif event.type == KEYUP: direction = 'stand' elif event.type == QUIT: quit = True keys = pygame.key.get_pressed() # checking pressed keys if keys[K_UP]: # check to see if solid tile is above the player above_tile = fetch_tile_properties(self.tmx_data, x + image_width, y - player_speed,self.view_hole) if not above_tile['solid']: y -= player_speed # check to see if player is on ground to allow a jump if on_tile['ground']: player_jump_frame = 10 elif keys[K_DOWN]: pass # no longer needed since gravity takes care of coming down if keys[K_RIGHT]: right_tile = fetch_tile_properties(self.tmx_data, x + image_width + player_speed, y + image_height/2, self.view_hole) if not right_tile['solid']: # move right x += player_speed direction = 'right' # check property of tile player is standing on now elif keys[K_LEFT]: left_tile = fetch_tile_properties(self.tmx_data, x - player_speed, y + image_height/2, self.view_hole) if not left_tile['solid']: # move left x -= player_speed direction = 'left' # check property of tile player is standing on now on_tile = fetch_tile_properties( self.tmx_data, x + image_width/2, y + image_height, self.view_hole) # Check player collision collided = fetch_tile_properties(self.tmx_data, x + image_width, y, self.view_hole) player_health = player_health + collided['health'] player_points = player_points + collided['points'] # Quit game once player is out of life if player_health <= 0: quit = True # Remove coin that is collected by player via collision check if collided['id'] == 163: player_points = player_points + collided['points'] tile_x = collided['x'] tile_y = collided['y'] # remove coin touched from tmx_data self.tmx_data.layers[0].data[tile_y][tile_y] = 0 # self.tmx_data.layers[0].remove(tile_x, tile_y) print(on_tile) # testing to see if points or health change values # check if player is jumping up if player_jump_frame > 0: y -= player_speed direction = 'up' player_jump_frame = player_jump_frame - 1 # check if player is on a solid otherwise fall down elif on_tile['ground'] == False: direction = 'down' y += player_speed
fetch_tile_properties函数
def fetch_tile_properties(tmxdata, x, y, view_hole): view_x = x - view_hole[0] view_y = y - view_hole[1] tile_x = view_x // 35 # scaling by size of the tile tile_y = view_y // 35 # ensuring only the integer value of division try: properties = tmxdata.get_tile_properties( tile_x, tile_y, 0) # this would be a dictionary returned except ValueError: properties = {id: -1, 'canClimb': False, 'ground': False, 'health': -99999, 'points': 0, 'provide': '', 'require': '', 'solid' : False } # check if player is moving in free space, no tiles beneath it if properties is None: # specify defined custom properties since the get_properties function only # works for where there is a tile. Use id of -1 to check error # and free space motion properties = {id: -1, 'canClimb': False, 'ground': False, 'health': 0, 'points': 0, 'provide': '', 'require': '', 'solid': False} # Since properties is a dictionary, we can append extra data to it # useful data would be the position of the tile player is colliding wit # or standing on properties['x'] = tile_x properties['y'] = tile_y return properties
问题分析
- KeyError根源:
fetch_tile_properties中字典的键id未加引号,使用了Python内置函数id作为键名,导致后续访问collided['id']时找不到对应键。 - 分数重复累加:碰撞检测时已执行分数累加,金币判断中又重复加分,逻辑冗余。
- 碰撞区域不准确:当前碰撞检测点位于玩家右上角,未覆盖玩家主体,可能无法触发有效碰撞。
- 金币移除索引错误:
self.tmx_data.layers[0].data[tile_y][tile_y] = 0使用了两次tile_y,行列索引混淆,无法正确移除金币。 - 变量提前引用:跳跃逻辑中访问了未定义的
on_tile变量,会触发NameError。
修复方案
修复fetch_tile_properties函数
def fetch_tile_properties(tmxdata, x, y, view_hole): view_x = x - view_hole[0] view_y = y - view_hole[1] tile_x = view_x // 35 # 按tile尺寸缩放 tile_y = view_y // 35 # 取整数部分 try: properties = tmxdata.get_tile_properties( tile_x, tile_y, 0) # 返回字典类型属性 except ValueError: # 修复键名:将id改为字符串'id' properties = {'id': -1, 'canClimb': False, 'ground': False, 'health': -99999, 'points': 0, 'provide': '', 'require': '', 'solid' : False } # 处理无tile的空白区域 if properties is None: # 修复键名:将id改为字符串'id' properties = {'id': -1, 'canClimb': False, 'ground': False, 'health': 0, 'points': 0, 'provide': '', 'require': '', 'solid': False} # 添加tile的坐标信息 properties['x'] = tile_x properties['y'] = tile_y return properties
修复主函数run
def run(self): player_right_frame = 0 # 右侧动画帧索引 player_left_frame = 0 # 左侧动画帧索引 player_jump_frame = 0 # 跳跃动画帧索引 quit = False player_speed = 10 player_health = 100 # 玩家生命值 player_points = 0 clock = pygame.time.Clock() direction = 'stand' # 默认初始状态为站立 ground_position = self.screen.get_height() - 355 x = 170 y = ground_position # 提前初始化on_tile,避免未定义错误 on_tile = {'ground': False} while not quit: self.redraw() points_image = self.font.render(f"Points: {player_points}", 1, white) health_image = self.font.render(f'Health: {player_health}', 1, yellow) self.screen.blit(points_image, (100, 5)) self.screen.blit(health_image, (200, 5)) for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: quit = True if event.key == K_RETURN: pause = False elif event.type == KEYUP: direction = 'stand' elif event.type == QUIT: quit = True keys = pygame.key.get_pressed() # 获取当前按下的键 if keys[K_UP]: # 检测玩家上方是否有实心tile above_tile = fetch_tile_properties(self.tmx_data, x + image_width, y - player_speed,self.view_hole) if not above_tile['solid']: y -= player_speed # 只有在地面上才能跳跃 if on_tile['ground']: player_jump_frame = 10 elif keys[K_DOWN]: pass # 下落由重力逻辑处理 if keys[K_RIGHT]: right_tile = fetch_tile_properties(self.tmx_data, x + image_width + player_speed, y + image_height/2, self.view_hole) if not right_tile['solid']: x += player_speed direction = 'right' elif keys[K_LEFT]: left_tile = fetch_tile_properties(self.tmx_data, x - player_speed, y + image_height/2, self.view_hole) if not left_tile['solid']: x -= player_speed direction = 'left' # 检测玩家脚下的tile on_tile = fetch_tile_properties( self.tmx_data, x + image_width//2, y + image_height, self.view_hole) # 调整碰撞检测点为玩家中心,提升检测准确性 collided = fetch_tile_properties(self.tmx_data, x + image_width//2, y + image_height//2, self.view_hole) # 更新生命值和分数,移除重复累加 player_health += collided['health'] # 仅在碰撞到有效tile时更新分数 if collided['id'] != -1: player_points += collided['points'] # 生命值为0时退出游戏 if player_health <= 0: quit = True # 处理金币收集逻辑 if collided['id'] == 163: tile_x = collided['x'] tile_y = collided['y'] # 修复索引错误,正确定位tile行列 self.tmx_data.layers[0].data[tile_y][tile_x] = 0 print(on_tile) # 调试输出tile属性 # 跳跃逻辑 if player_jump_frame > 0: y -= player_speed direction = 'up' player_jump_frame -= 1 # 重力下落逻辑 elif not on_tile['ground']: direction = 'down' y += player_speed # 控制帧率为60,稳定游戏速度 clock.tick(60)
修复说明
- KeyError修复:将字典中的
id改为字符串'id',确保键名与访问逻辑一致。 - 变量初始化:提前初始化
on_tile,避免跳跃逻辑中出现未定义变量错误。 - 碰撞区域优化:将碰撞检测点改为玩家中心,确保更准确地触发与金币/陷阱的交互。
- 移除重复逻辑:删除金币判断中的重复加分代码,避免分数异常。
- 索引错误修复:修正金币移除时的行列索引,正确定位并移除目标tile。
- 帧率控制:添加
clock.tick(60),稳定游戏运行帧率,避免移动速度异常。
内容的提问来源于stack exchange,提问作者Obisi7
相关产品推荐
相关产品推荐

