2 # -*- coding: utf-8 -*-
20 from hero
import avatar_hero
22 # -----------------------------------------------------------------------------
24 def start(screen
, map_filename
, hero
):
26 Use the paralax scrolling feature.
29 # parser the map (it is done here to initialize the
30 # window the same size as the map if it is small enough)
31 world_map
= tiledtmxloader
.tmxreader
.TileMapParser().parse_decode(map_filename
)
33 print world_map
.properties
35 pygame
.display
.set_caption("Map viewer: " + map_filename
+ " ( " + str(world_map
.pixel_width
) +" x "+ str(world_map
.pixel_height
) + " )")
37 screen_width
, screen_height
= screen
.get_size()
38 hero
.moveto(1024, 768)
40 # load the images using pygame
41 resources
= tiledtmxloader
.helperspygame
.ResourceLoaderPygame()
42 resources
.load(world_map
)
44 # prepare map rendering
45 assert world_map
.orientation
== "orthogonal"
48 renderer
= tiledtmxloader
.helperspygame
.RendererPygame()
50 # get position of hero
51 hero_pos_x
= hero
.rect
.centerx
52 hero_pos_y
= hero
.rect
.bottom
54 # dimensions of the hero for collision detection
55 hero_width
= hero
.rect
.width
58 # cam_offset is for scrolling
59 cam_world_pos_x
= hero
.rect
.centerx
60 cam_world_pos_y
= hero
.rect
.centery
62 # set initial cam position and size
63 renderer
.set_camera_position_and_size(cam_world_pos_x
, cam_world_pos_y
, \
64 screen_width
, screen_height
)
67 sprite_layers
= tiledtmxloader
.helperspygame
.get_layers_from_map(resources
)
70 sprite_layers
= [layer
for layer
in sprite_layers
if not layer
.is_object_group
]
72 # detect metadata layer
75 for layer
in world_map
.layers
:
76 if not layer
.is_object_group
:
77 if layer
.properties
.has_key('metalayer'):
78 metadata_layer
= layer
79 if layer
.properties
.has_key('player'):
80 hero_layer
= layer_num
83 # add the hero the the right layer, it can be changed using 0-9 keys
84 sprite_layers
[hero_layer
].add_sprite(hero
)
86 # create dictionary with the properties of every tile, indexed by gid
88 for tile_set
in world_map
.tile_sets
:
89 for tile
in tile_set
.tiles
:
90 tile_properties
[int(tile_set
.firstgid
) + int(tile
.id)] = tile
.properties
93 # layer add/remove hero keys
94 num_keys
= [pygame
.K_0
, pygame
.K_1
, pygame
.K_2
, pygame
.K_3
, pygame
.K_4
, \
95 pygame
.K_5
, pygame
.K_6
, pygame
.K_7
, pygame
.K_8
, pygame
.K_9
]
97 # variables for the main loop
98 clock
= pygame
.time
.Clock()
101 # set up timer for fps printing
102 pygame
.time
.set_timer(pygame
.USEREVENT
, 1000)
105 action_type
= actor
.WALK
112 for event
in pygame
.event
.get():
113 if event
.type == pygame
.QUIT
:
115 elif event
.type == pygame
.USEREVENT
:
116 print("fps: ", clock
.get_fps())
117 elif event
.type == pygame
.KEYDOWN
:
118 if event
.key
== pygame
.K_ESCAPE
:
120 elif event
.key
in num_keys
:
121 # find out which layer to manipulate
122 idx
= num_keys
.index(event
.key
)
123 # make sure this layer exists
124 if idx
< len(world_map
.layers
):
125 if sprite_layers
[idx
].contains_sprite(hero
):
126 sprite_layers
[idx
].remove_sprite(hero
)
127 print("removed hero sprite from layer", idx
)
129 sprite_layers
[idx
].add_sprite(hero
)
130 print("added hero sprite to layer", idx
)
132 print("no such layer or more than 10 layers: " + str(idx
))
133 elif event
.key
in (pygame
.K_LSHIFT
, pygame
.K_RSHIFT
):
135 action_type
= actor
.RUN
136 elif event
.type == pygame
.KEYUP
:
137 if event
.key
in (pygame
.K_LSHIFT
, pygame
.K_RSHIFT
):
139 action_type
= actor
.WALK
142 direction_x
= pygame
.key
.get_pressed()[pygame
.K_RIGHT
] - \
143 pygame
.key
.get_pressed()[pygame
.K_LEFT
]
144 direction_y
= pygame
.key
.get_pressed()[pygame
.K_DOWN
] - \
145 pygame
.key
.get_pressed()[pygame
.K_UP
]
147 # make sure the hero moves with same speed in all directions (diagonal!)
148 dir_len
= math
.hypot(direction_x
, direction_y
)
149 dir_len
= dir_len
if dir_len
else 1.0
152 step_x
= speed_factor
* speed
* dt
* direction_x
/ dir_len
153 step_y
= speed_factor
* speed
* dt
* direction_y
/ dir_len
154 if metadata_layer
is not None:
156 step_x
, step_y
= check_collision(hero_pos_x
, hero_pos_y
, step_x
, step_y
, hero_width
, hero_height
, metadata_layer
, tile_properties
)
161 hero
.rect
.midbottom
= (hero_pos_x
, hero_pos_y
)
162 hero
.update(step_x
, step_y
, action_type
)
164 # adjust camera according to the hero's position, follow him
165 # (don't make the hero follow the cam, maybe later you want different
166 # objects to be followd by the cam)
167 renderer
.set_camera_position(hero
.rect
.centerx
, hero
.rect
.centery
)
169 # clear screen, might be left out if every pixel is redrawn anyway
170 screen
.fill((0, 0, 0))
173 for sprite_layer
in sprite_layers
:
174 if sprite_layer
.is_object_group
:
175 # we dont draw the object group layers
176 # you should filter them out if not needed
179 renderer
.render_layer(screen
, sprite_layer
)
181 pygame
.display
.flip()
183 # -----------------------------------------------------------------------------
185 def check_collision(hero_pos_x
, hero_pos_y
, step_x
, step_y
, \
186 hero_width
, hero_height
, metadata_layer
, tile_properties
):
188 Checks collision of the hero against the world. Its not the best way to
189 handle collision detection but for this demo it is good enough.
191 :Returns: steps to add to heros current position.
194 hero_rect
= pygame
.Rect(0, 0, hero_width
, hero_height
)
195 hero_rect
.midbottom
= (hero_pos_x
, hero_pos_y
)
197 # find the tile location of the hero
198 tile_x
= int((hero_pos_x
) // metadata_layer
.tilewidth
)
199 tile_y
= int((hero_pos_y
) // metadata_layer
.tileheight
)
201 # find the tiles around the hero and extract their rects for collision
203 for diry
in (-1, 0 , 1):
204 for dirx
in (-1, 0, 1):
205 gid
= metadata_layer
.content2D
[tile_x
+ dirx
][tile_y
+ diry
]
206 if gid
in tile_properties
:
207 tile_width
= metadata_layer
.tilewidth
208 tile_pos_x
= (tile_x
+ dirx
) * tile_width
209 tile_height
= metadata_layer
.tileheight
210 tile_pos_y
= (tile_y
+ diry
) * tile_height
211 tile_rect
= pygame
.Rect(tile_pos_x
, tile_pos_y
, tile_width
, tile_height
)
212 tile_rects
.append(tile_rect
)
214 # save the original steps and return them if not canceled
218 # x direction, floor or ceil depending on the sign of the step
219 step_x
= special_round(step_x
)
221 # detect a collision and dont move in x direction if colliding
222 if hero_rect
.move(step_x
, 0).collidelist(tile_rects
) > -1:
225 # y direction, floor or ceil depending on the sign of the step
226 step_y
= special_round(step_y
)
228 # detect a collision and dont move in y direction if colliding
229 if hero_rect
.move(0, step_y
).collidelist(tile_rects
) > -1:
232 # return the step the hero should do
233 return res_step_x
, res_step_y
235 # -----------------------------------------------------------------------------
237 def special_round(value
):
239 For negative numbers it returns the value floored,
240 for positive numbers it returns the value ceiled.
242 # same as: math.copysign(math.ceil(abs(x)), x)
244 # ## versus this, which could save many function calls
246 # ceil_or_floor = { True : math.ceil, False : math.floor, }
248 # x = floor_or_ceil[val<0.0](val)
251 return math
.floor(value
)
252 return math
.ceil(value
)