当前位置:   article > 正文

【pygame实现星露谷物语风格游戏】9.绘制地图中的其他事物,并且打造伪3D效果_星露谷物语tmx文件如何打开

星露谷物语tmx文件如何打开

一.目的

本次的目的是将地图中的其他东西,比如房子,栅栏,水流,树木等等加载并绘制到地图上。

并且打造出伪3D效果

所谓的伪3D效果,就是当玩家在树(也可以是地图中的一切东西,这里用树举例)的前面时,系统会将玩家的图层放到树的图层的上面,造成视觉上的玩家在树前面的效果

当玩家在树的后面时,系统会把树的图层放到玩家的图层之上,造成视觉上玩家在数后面的效果

二.代码实现

1.tmx文件的使用

这里,原作者用免费软件Tiled制作了整个地图,并把地图放到了如图所示的位置

其中map.tmx就是tiled导出的地图文件

在这里,我们不学习怎么用它绘制地图,因为作者已经绘制好了

我们直接学习如何对给出的.tmx文件进行使用

tiled的官网下载地址如下Download | Tiled

下载完后打开map.tmx,界面如下,我们先只关心箭头所指的两个部分

右边的框表示图层,就比如HouseFloor就是把房间的地板绘制到了这一个图层上

我们点击图中右侧箭头所指的地方的按钮,可以隐藏这个图层。我们隐藏了HouseFloor这个图层后,发现地图中房间的地板不见了

我们可以通过这个方法,来了解每一个图层代表地图中的什么图形

接下来我们回到pycharm中,回到level.py文件

python给我们提供了一个名为pytmx的包,让我们能够使用.tmx文件,我们利用pip install 把它下载下来,并且import导入其中的load_pygame模块

接下来我们可以通过如下的方法导入.tmx文件

之后可以利用这个包给我们的.get_layer_by_name("图层名").tiles():方法,来获取该图层中每一个像素块的位置信息和图形信息

就以绘制房间的地板为例

由于房间的地板的图层是由很多块单独的地板拼接成的,因此我们需要用for循环来接收它的返回值

它的返回值有3个,分别为每一块地板的坐标x值,坐标y值和图像,

但是,这里返回的x和y的坐标值,是在tiled地图中的坐标值,在tiled的坐标系中,每一个小方块是由64*64个像素组成的,因此转换成pygame的坐标系,还需要让x和y分别乘上64。

这里的TILE_SIZE是settings.py中的一个常量,大小是64

由于玩家并不会与房间的地板产生交互,并且房间的地板并没有什么动画效果,所以房间的地板的性质与我们之前写的Generic类是相同的,因此可以把房间地板当作是Generic对象,所以我们可以利用这些返回值来创建Generic对象

同样的,房间的地毯也是具有相同的性质,所以可以用相同的代码来写,不过为了节省空间,利用一个for循环把地板和地毯都创建出来了

  1. for layer in ['HouseFloor', 'HouseFurnitureBottom']:
  2. for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
  3. Generic((x * TILE_SIZE,y * TILE_SIZE), surf, self.all_sprites, LAYERS['house bottom'])

 

同样的还有房间的墙壁和家具,不同的是,墙壁和家具并不是一直被玩家踩在脚下的,而是能和玩家产生伪3D效果的,所以要把它的z轴设为和玩家相同的LAYERS["main"],也就是缺省值

  1. for layer in ['HouseWalls', 'HouseFurnitureTop']:
  2. for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
  3. Generic((x * TILE_SIZE,y * TILE_SIZE), surf, self.all_sprites)

 

具有同样性质的还有栅栏,由于栅栏并不属于房间的一部分,所以原作则并没有把栅栏与家具和墙壁写到同一个for循环里,实际上写到同一个for循环里也并没有影响

  1. for x, y, surf in tmx_data.get_layer_by_name('Fence').tiles():
  2. Generic((x * TILE_SIZE,y * TILE_SIZE), surf, self.all_sprites)

 

这里我把他们都写道同一个循环里去了,直到目前为止,改动的内容如下图所示(当然别忘了from pytmx.util_pygame import load_pygame)

这样我们就可以再地图上看到房间和栅栏了

接下来就是水流和树木和野花的制作

由于水流具有动画效果,树木和野花会与玩家产生交互,它们显然在Generic的基础上又多了一些性质,因此我们需要在sprites.py中写这些类,它们都是继承自Generic的

水流类:

  1. class Water(Generic):
  2. def __init__(self, pos, frames, groups):
  3. #传入的参数,fream是一个列表,存放了水流不同动作的五张图片
  4. self.frames = frames
  5. #fream_index是正在播放第几张图片
  6. self.frame_index = 0
  7. super().__init__(
  8. pos = pos,
  9. surf = self.frames[self.frame_index],
  10. groups = groups,
  11. z = LAYERS['water'])
  12. def animate(self,dt):
  13. self.frame_index += 5 * dt
  14. if self.frame_index >= len(self.frames):
  15. self.frame_index = 0
  16. self.image = self.frames[int(self.frame_index)]
  17. def update(self,dt):
  18. self.animate(dt)

 

水流只是在父类的基础上增加了动画的绘制,这一点在编写Player类的时候已经详细描述过了,就不再多说了

接着回到level.py中,创建水流这个类的对象

由于其中一个参数是一个存放图片的列表,我们想到了之前自己写的一个函数,给它一个文件夹的路径,他会返回一个存放该文件夹下的所有图片的列表,因此我们导入这个文件,并且直接调用它的import_folder函数即可

  1. #水流
  2. water_frames = import_folder('../graphics/water')
  3. for x, y, surf in tmx_data.get_layer_by_name('Water').tiles():
  4. Water((x * TILE_SIZE, y * TILE_SIZE), water_frames, self.all_sprites)

 

接下来就是树和野花类的书写,这里我们还没用到它们与玩家的交互,因此只写它的初始化部分即可

  1. class WildFlower(Generic):
  2. def __init__(self, pos, surf, groups):
  3. super().__init__(pos, surf, groups)
  4. class Tree(Generic):
  5. def __init__(self, pos, surf, groups, name):
  6. super().__init__(pos, surf, groups)

 

接着回到level.py创建它们的对象

  1. #树木
  2. for obj in tmx_data.get_layer_by_name('Trees'):
  3. Tree((obj.x, obj.y), obj.image, self.all_sprites, obj.name)
  4. #野花
  5. for obj in tmx_data.get_layer_by_name('Decoration'):
  6. WildFlower((obj.x, obj.y), obj.image, self.all_sprites)

 

这里使用的是tmx_data.get_layer_by_name('图层名'),后面并没有加.tiles(),它的作用与加上.tiles()大致相同,只不过返回值不太相同

这里用obj来接收返回值,它一样有x坐标和y坐标,只不过它的x坐标和y坐标不用再乘上TILE_SIZE了,

它还有一个image,就是图像,此外,还多了一个属性叫name,就是图层中每一张单独的图片的名字

原作者一会用这种方法来接收图层中的坐标和图像,一会又用之前的方法,让我写的很难受

实际上不加.tiles()的这种方法能完全替代上面加上.tiles()的方法,并且写起来还更加方便

建议统一都用这种方法来写

这里多出来的name属性,是为了给不同大小的树木赋予不同的性质做准备的

如图中的箭头所指,不同的树木根据大小有不同的名字,分别是large和small

而接受的name属性就是指的这些树木单独的名字

至于这部分功能以后再实现,在这里只是先把这个属性接收下来,并且当作参数传递进Tree类里。

这样,我们的树木,野花和水流也都能出现在屏幕上了,并且水还是有动态效果的

2.伪3D效果的打造

目前还有一个问题,就是虽然树木,墙壁野花等等这些东西的z值和玩家是相同的,但是其绘制顺序还是得分个先后的,现在我们是无论什么情况都把玩家画在最上面,这就造成了如下图所示的问题:我们实际上在树的后面,但是视觉效果在树的树顶

要解决这个问题很简单,只要玩家的坐标的y值比树(这里拿树距离,实际上是跟玩家在同一个z值上的所有的精灵)小,就说明玩家在树的后面,就先绘制树再绘制玩家。相反,玩家的坐标y值比树大,就说明玩家在树的前面,就先绘制树再绘制玩家。

再简化一下这个问题,对于z值相同的精灵,先绘制y坐标小的,再绘制y坐标大的

这样,我们仅需在绘制的时候,对同一个z值的精灵们,根据它们的y值进行升序排序,然后按照这个顺序绘制就可以了

只需要修改一句代码即可,这里利用的是python基础的lambda表达式。

如此,我们便完成了伪3D效果

三.完整代码:

sprites.py:

  1. import pygame
  2. from settings import *
  3. class Generic(pygame.sprite.Sprite):
  4. def __init__(self,pos,surf,groups,z = LAYERS['main']):
  5. super().__init__(groups)
  6. self.image = surf
  7. self.rect = self.image.get_rect(topleft = pos)
  8. self.z = z
  9. class Water(Generic):
  10. def __init__(self, pos, frames, groups):
  11. #传入的参数,fream是一个列表,存放了水流不同动作的五张图片
  12. self.frames = frames
  13. #fream_index是正在播放第几张图片
  14. self.frame_index = 0
  15. super().__init__(
  16. pos = pos,
  17. surf = self.frames[self.frame_index],
  18. groups = groups,
  19. z = LAYERS['water'])
  20. def animate(self,dt):
  21. self.frame_index += 5 * dt
  22. if self.frame_index >= len(self.frames):
  23. self.frame_index = 0
  24. self.image = self.frames[int(self.frame_index)]
  25. def update(self,dt):
  26. self.animate(dt)
  27. class WildFlower(Generic):
  28. def __init__(self, pos, surf, groups):
  29. super().__init__(pos, surf, groups)
  30. class Tree(Generic):
  31. def __init__(self, pos, surf, groups, name):
  32. super().__init__(pos, surf, groups)

 

level.py

  1. import pygame
  2. from settings import *
  3. from player import Player
  4. from overlay import Overlay
  5. from sprites import *
  6. from pytmx.util_pygame import load_pygame
  7. from support import *
  8. class Level():
  9. def __init__(self):
  10. #得到屏幕的画面,得到的这个画面与main.py中的screen相同
  11. self.display_surface = pygame.display.get_surface()
  12. #创建精灵组
  13. self.all_sprites = CameraGroup()
  14. #调用setup方法
  15. self.setup()
  16. #创建工具和种子显示图层
  17. self.overlay = Overlay(self.player)
  18. def setup(self):
  19. #载入.tmx文件
  20. tmx_data = load_pygame('../data/map.tmx')
  21. #绘制房子与栅栏,他们都属于Generic类
  22. for layer in ['HouseFloor', 'HouseFurnitureBottom']:
  23. for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
  24. Generic((x * TILE_SIZE, y * TILE_SIZE), surf, self.all_sprites, LAYERS['house bottom'])
  25. for layer in ['HouseWalls', 'HouseFurnitureTop','Fence']:
  26. for x, y, surf in tmx_data.get_layer_by_name(layer).tiles():
  27. Generic((x * TILE_SIZE, y * TILE_SIZE), surf, self.all_sprites)
  28. #水流
  29. water_frames = import_folder('../graphics/water')
  30. for x, y, surf in tmx_data.get_layer_by_name('Water').tiles():
  31. Water((x * TILE_SIZE, y * TILE_SIZE), water_frames, self.all_sprites)
  32. #树木
  33. for obj in tmx_data.get_layer_by_name('Trees'):
  34. Tree((obj.x, obj.y), obj.image, self.all_sprites, obj.name)
  35. #野花
  36. for obj in tmx_data.get_layer_by_name('Decoration'):
  37. WildFlower((obj.x, obj.y), obj.image, self.all_sprites)
  38. self.player = Player((640,360),self.all_sprites)
  39. Generic(
  40. pos = (0,0),
  41. surf = pygame.image.load('../graphics/world/ground.png').convert_alpha(),
  42. groups = self.all_sprites,
  43. z = LAYERS['ground']
  44. )
  45. def run(self,dt):
  46. #窗口的背景设为黑色
  47. self.display_surface.fill('black')
  48. #调用精灵组的draw方法
  49. self.all_sprites.custom_draw(self.player)
  50. #调用精灵组的update方法
  51. self.all_sprites.update(dt)
  52. self.overlay.display()
  53. class CameraGroup(pygame.sprite.Group):
  54. def __init__(self):
  55. super().__init__()
  56. #获取窗口
  57. self.display_surface = pygame.display.get_surface()
  58. #这是一个偏移量,代表的是玩家的实际位置与屏幕中间的矢量
  59. self.offset = pygame.math.Vector2()
  60. def custom_draw(self,player):
  61. self.offset.x = player.rect.centerx - SCREEN_WIDTH / 2
  62. self.offset.y = player.rect.centery - SCREEN_HEIGHT / 2
  63. for layer in LAYERS.values():#按照z轴从小到达绘制
  64. for sprite in sorted(self.sprites(),key = lambda sprite: sprite.rect.centery):
  65. if sprite.z == layer:#如果该精灵的z值等于当前要绘制的z值,才绘制
  66. offset_rect = sprite.rect.copy()
  67. offset_rect.center -= self.offset
  68. #if sprite == player:
  69. #print("player.rect.center为:(" +
  70. # str(player.rect.centerx)+"," + str(player.rect.centery)+")")
  71. #print("offset_rect为:(" + str(offset_rect.x)
  72. # +"," +str(offset_rect.y)+")")
  73. self.display_surface.blit(sprite.image,offset_rect)

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/834674
推荐阅读
相关标签
  

闽ICP备14008679号