Skip to content

Commit e9a2f21

Browse files
committed
Score and joystick code refactored.
1 parent d60f319 commit e9a2f21

File tree

2 files changed

+833
-0
lines changed

2 files changed

+833
-0
lines changed
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
#
2+
# Arcade Platformer
3+
#
4+
# Demonstrating the capbilities of arcade in a platformer game
5+
# Supporting the Arcade Platformer article on https://realpython.com
6+
#
7+
# All game artwork and sounds, except the tile map and victory sound,
8+
# from www.kenney.nl
9+
10+
11+
# Import libraries
12+
import arcade
13+
import pathlib
14+
15+
# Game constants
16+
# Window dimensions
17+
SCREEN_WIDTH = 1000
18+
SCREEN_HEIGHT = 650
19+
SCREEN_TITLE = "Arcade Platformer"
20+
21+
# Scaling Constants
22+
MAP_SCALING = 1.0
23+
24+
# Player constants
25+
GRAVITY = 1.0
26+
PLAYER_START_X = 65
27+
PLAYER_START_Y = 256
28+
PLAYER_MOVE_SPEED = 10
29+
PLAYER_JUMP_SPEED = 20
30+
31+
# Viewport margins
32+
# How close do we have to be to scroll the viewport?
33+
LEFT_VIEWPORT_MARGIN = 50
34+
RIGHT_VIEWPORT_MARGIN = 300
35+
TOP_VIEWPORT_MARGIN = 150
36+
BOTTOM_VIEWPORT_MARGIN = 150
37+
38+
# Assets path
39+
ASSETS_PATH = pathlib.Path(__file__).resolve().parent.parent / "assets"
40+
41+
42+
# Classes
43+
class Platformer(arcade.Window):
44+
"""PlatformerView class. Derived from arcade.View, provides all functionality
45+
from arcade.Window, plus managing different views for our game.
46+
"""
47+
48+
def __init__(self) -> None:
49+
"""Create the game view"""
50+
# First initialize the parent
51+
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
52+
53+
# These lists will hold different sets of sprites
54+
self.coins_list = None
55+
self.background_list = None
56+
self.walls_list = None
57+
self.ladders_list = None
58+
self.goals_list = None
59+
self.enemies_list = None
60+
61+
# One sprite for the player, no more is needed
62+
self.player = None
63+
64+
# We need a physics engine as well
65+
self.physics_engine = None
66+
67+
# Someplace to keep score
68+
self.score = 0
69+
70+
# Which level are we on?
71+
self.level = 1
72+
73+
# Load up our sounds here
74+
self.coin_sound = arcade.load_sound(
75+
str(ASSETS_PATH / "sounds" / "coin.wav")
76+
)
77+
self.jump_sound = arcade.load_sound(
78+
str(ASSETS_PATH / "sounds" / "jump.wav")
79+
)
80+
self.victory_sound = arcade.load_sound(
81+
str(ASSETS_PATH / "sounds" / "victory.wav")
82+
)
83+
84+
def setup(self) -> None:
85+
"""Sets up the game for the current level"""
86+
87+
# Get the current map based on the level
88+
map_name = f"platform_level_{self.level:02}.tmx"
89+
map_path = ASSETS_PATH / map_name
90+
91+
# What are the names of the layers?
92+
wall_layer = "ground"
93+
coin_layer = "coins"
94+
goal_layer = "goal"
95+
background_layer = "background"
96+
ladders_layer = "ladders"
97+
98+
# Load the current map
99+
map = arcade.tilemap.read_tmx(str(map_path))
100+
101+
# Load the layers
102+
self.background_list = arcade.tilemap.process_layer(
103+
map, layer_name=background_layer, scaling=MAP_SCALING
104+
)
105+
self.goals_list = arcade.tilemap.process_layer(
106+
map, layer_name=goal_layer, scaling=MAP_SCALING
107+
)
108+
self.walls_list = arcade.tilemap.process_layer(
109+
map, layer_name=wall_layer, scaling=MAP_SCALING
110+
)
111+
self.ladders_list = arcade.tilemap.process_layer(
112+
map, layer_name=ladders_layer, scaling=MAP_SCALING
113+
)
114+
self.coins_list = arcade.tilemap.process_layer(
115+
map, layer_name=coin_layer, scaling=MAP_SCALING
116+
)
117+
118+
# Set the background color
119+
background_color = arcade.color.AERO_BLUE
120+
if map.background_color:
121+
background_color = map.background_color
122+
arcade.set_background_color(background_color)
123+
124+
# Find the edge of the map to control viewport scrolling
125+
self.map_width = (map.map_size.width - 1) * map.tile_size.width
126+
127+
# Create the player sprite, if they're not already setup
128+
if not self.player:
129+
self.player = self.create_player_sprite()
130+
131+
# If we have a player sprite, we need to move it back to the beginning
132+
self.player.center_x = PLAYER_START_X
133+
self.player.center_y = PLAYER_START_Y
134+
self.player.change_x = 0
135+
self.player.change_y = 0
136+
137+
# Reset the viewport
138+
self.view_left = 0
139+
self.view_bottom = 0
140+
141+
# Load the physics engine for this map
142+
self.physics_engine = arcade.PhysicsEnginePlatformer(
143+
player_sprite=self.player,
144+
platforms=self.walls_list,
145+
gravity_constant=GRAVITY,
146+
ladders=self.ladders_list,
147+
)
148+
149+
def create_player_sprite(self) -> arcade.AnimatedWalkingSprite:
150+
"""Creates the animated player sprite
151+
152+
Returns:
153+
The properly setup player sprite
154+
"""
155+
# Where are the player images stored?
156+
texture_path = ASSETS_PATH / "images" / "player"
157+
158+
# Setup the appropriate textures
159+
walking_paths = [
160+
texture_path / f"alienGreen_walk{x}.png" for x in (1, 2)
161+
]
162+
climbing_paths = [
163+
texture_path / f"alienGreen_climb{x}.png" for x in (1, 2)
164+
]
165+
standing_path = texture_path / "alienGreen_stand.png"
166+
167+
# Load them all now
168+
walking_right_textures = [
169+
arcade.load_texture(texture) for texture in walking_paths
170+
]
171+
walking_left_textures = [
172+
arcade.load_texture(texture, mirrored=True)
173+
for texture in walking_paths
174+
]
175+
176+
walking_up_textures = [
177+
arcade.load_texture(texture) for texture in climbing_paths
178+
]
179+
walking_down_textures = [
180+
arcade.load_texture(texture) for texture in climbing_paths
181+
]
182+
183+
standing_right_textures = [arcade.load_texture(standing_path)]
184+
185+
standing_left_textures = [
186+
arcade.load_texture(standing_path, mirrored=True)
187+
]
188+
189+
# Create the sprite
190+
player = arcade.AnimatedWalkingSprite()
191+
192+
# Add the proper textures
193+
player.stand_left_textures = standing_left_textures
194+
player.stand_right_textures = standing_right_textures
195+
player.walk_left_textures = walking_left_textures
196+
player.walk_right_textures = walking_right_textures
197+
player.walk_up_textures = walking_up_textures
198+
player.walk_down_textures = walking_down_textures
199+
200+
# Set the player defaults
201+
player.center_x = PLAYER_START_X
202+
player.center_y = PLAYER_START_Y
203+
player.state = arcade.FACE_RIGHT
204+
205+
# Set the initial texture
206+
player.texture = player.stand_right_textures[0]
207+
208+
return player
209+
210+
def on_key_press(self, key: int, modifiers: int) -> None:
211+
"""Processes key presses
212+
213+
Arguments:
214+
key -- Which key was pressed
215+
modifiers -- Which modifiers were down at the time
216+
"""
217+
218+
# Check for player left/right movement
219+
if key in [arcade.key.LEFT, arcade.key.J]:
220+
self.player.change_x = -PLAYER_MOVE_SPEED
221+
elif key in [arcade.key.RIGHT, arcade.key.L]:
222+
self.player.change_x = PLAYER_MOVE_SPEED
223+
224+
# Check if player can climb up or down
225+
elif key in [arcade.key.UP, arcade.key.I]:
226+
if self.physics_engine.is_on_ladder():
227+
self.player.change_y = PLAYER_MOVE_SPEED
228+
elif key in [arcade.key.DOWN, arcade.key.K]:
229+
if self.physics_engine.is_on_ladder():
230+
self.player.change_y = -PLAYER_MOVE_SPEED
231+
232+
# Check if we can jump
233+
elif key == arcade.key.SPACE:
234+
if self.physics_engine.can_jump():
235+
self.player.change_y = PLAYER_JUMP_SPEED
236+
# Play the jump sound
237+
arcade.play_sound(self.jump_sound)
238+
239+
def on_key_release(self, key: int, modifiers: int) -> None:
240+
"""Processes key releases
241+
242+
Arguments:
243+
key -- The key which was released
244+
modifiers -- Which modifiers were down at the time
245+
"""
246+
247+
# Check for player left/right movement
248+
if key in [
249+
arcade.key.LEFT,
250+
arcade.key.J,
251+
arcade.key.RIGHT,
252+
arcade.key.L,
253+
]:
254+
self.player.change_x = 0
255+
256+
# Check if player can climb up or down
257+
elif key in [
258+
arcade.key.UP,
259+
arcade.key.I,
260+
arcade.key.DOWN,
261+
arcade.key.K,
262+
]:
263+
if self.physics_engine.is_on_ladder():
264+
self.player.change_y = 0
265+
266+
def on_update(self, delta_time: float) -> None:
267+
"""Updates the position of all screen objects
268+
269+
Arguments:
270+
delta_time -- How much time since the last call
271+
"""
272+
273+
# Update the player animation
274+
self.player.update_animation(delta_time)
275+
276+
# Update player movement based on the physics engine
277+
self.physics_engine.update()
278+
279+
# Restrict user movement so they can't walk off screen
280+
if self.player.left < 0:
281+
self.player.left = 0
282+
283+
# Check if we've picked up a coin
284+
coins_hit = arcade.check_for_collision_with_list(
285+
sprite=self.player, sprite_list=self.coins_list
286+
)
287+
288+
for coin in coins_hit:
289+
# Add the coin score to our score
290+
self.score += int(coin.properties["point_value"])
291+
292+
# Play the coin sound
293+
arcade.play_sound(self.coin_sound)
294+
295+
# Remove the coin
296+
coin.remove_from_sprite_lists()
297+
298+
# Now check if we're at the ending goal
299+
goals_hit = arcade.check_for_collision_with_list(
300+
sprite=self.player, sprite_list=self.goals_list
301+
)
302+
303+
if goals_hit:
304+
# Play the victory sound
305+
self.victory_sound.play()
306+
307+
# Setup the next level
308+
self.level += 1
309+
self.setup()
310+
311+
# Set the viewport, scrolling if necessary
312+
self.scroll_viewport()
313+
314+
def scroll_viewport(self) -> None:
315+
"""Scrolls the viewport when the player gets close to the edges"""
316+
# Scroll left
317+
# Find the current left boundary
318+
left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
319+
320+
# Are we to the left of this boundary? Then we should scroll left
321+
if self.player.left < left_boundary:
322+
self.view_left -= left_boundary - self.player.left
323+
# But don't scroll past the left edge of the map
324+
if self.view_left < 0:
325+
self.view_left = 0
326+
327+
# Scroll right
328+
# Find the current right boundary
329+
right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
330+
331+
# Are we right of this boundary? Then we should scroll right
332+
if self.player.right > right_boundary:
333+
self.view_left += self.player.right - right_boundary
334+
# Don't scroll past the right edge of the map
335+
if self.view_left > self.map_width - SCREEN_WIDTH:
336+
self.view_left = self.map_width - SCREEN_WIDTH
337+
338+
# Scroll up
339+
top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
340+
if self.player.top > top_boundary:
341+
self.view_bottom += self.player.top - top_boundary
342+
343+
# Scroll down
344+
bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
345+
if self.player.bottom < bottom_boundary:
346+
self.view_bottom -= bottom_boundary - self.player.bottom
347+
348+
# Only scroll to integers. Otherwise we end up with pixels that
349+
# don't line up on the screen
350+
self.view_bottom = int(self.view_bottom)
351+
self.view_left = int(self.view_left)
352+
353+
# Do the scrolling
354+
arcade.set_viewport(
355+
left=self.view_left,
356+
right=SCREEN_WIDTH + self.view_left,
357+
bottom=self.view_bottom,
358+
top=SCREEN_HEIGHT + self.view_bottom,
359+
)
360+
361+
def on_draw(self) -> None:
362+
"""Draws everything"""
363+
364+
arcade.start_render()
365+
366+
# Draw all the sprites
367+
self.background_list.draw()
368+
self.walls_list.draw()
369+
self.coins_list.draw()
370+
self.goals_list.draw()
371+
self.ladders_list.draw()
372+
self.player.draw()
373+
374+
# Draw the score in the lower left
375+
score_text = f"Score: {self.score}"
376+
377+
# First a black background for a shadow effect
378+
arcade.draw_text(
379+
score_text,
380+
start_x=10 + self.view_left,
381+
start_y=10 + self.view_bottom,
382+
color=arcade.csscolor.BLACK,
383+
font_size=40,
384+
)
385+
# Now in white slightly shifted
386+
arcade.draw_text(
387+
score_text,
388+
start_x=15 + self.view_left,
389+
start_y=15 + self.view_bottom,
390+
color=arcade.csscolor.WHITE,
391+
font_size=40,
392+
)
393+
394+
# Main
395+
if __name__ == "__main__":
396+
window = Platformer()
397+
window.setup()
398+
arcade.run()

0 commit comments

Comments
 (0)