"""
Platformer Game
"""
import os
import arcade
# Constants
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 650
SCREEN_TITLE = "Platformer"
# Constants used to scale our sprites from their original size
CHARACTER_SCALING = 1
TILE_SCALING = 0.5
COIN_SCALING = 0.5
SPRITE_PIXEL_SIZE = 128
GRID_PIXEL_SIZE = SPRITE_PIXEL_SIZE * TILE_SCALING
# Movement speed of player, in pixels per frame
PLAYER_MOVEMENT_SPEED = 7
GRAVITY = 1.5
PLAYER_JUMP_SPEED = 30
PLAYER_START_X = 64
PLAYER_START_Y = 256
# Layer Names from our TileMap
LAYER_NAME_MOVING_PLATFORMS = "Moving Platforms"
LAYER_NAME_PLATFORMS = "Platforms"
LAYER_NAME_COINS = "Coins"
LAYER_NAME_BACKGROUND = "Background"
LAYER_NAME_LADDERS = "Ladders"
class MyGame(arcade.Window):
"""
Main application class.
"""
def __init__(self):
"""
Initializer for the game
"""
# Call the parent class and set up the window
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
# Set the path to start with this program
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Our TileMap Object
self.tile_map = None
# Our Scene Object
self.scene = None
# Separate variable that holds the player sprite
self.player_sprite = None
# Our 'physics' engine
self.physics_engine = None
# A Camera that can be used for scrolling the screen
self.camera = None
# A Camera that can be used to draw GUI elements
self.gui_camera = None
self.end_of_map = 0
# Keep track of the score
self.score = 0
# Load sounds
self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
def setup(self):
"""Set up the game here. Call this function to restart the game."""
# Set up the Cameras
self.camera = arcade.Camera(self.width, self.height)
self.gui_camera = arcade.Camera(self.width, self.height)
# Map name
map_name = ":resources:tiled_maps/map_with_ladders.json"
# Layer Specific Options for the Tilemap
layer_options = {
LAYER_NAME_PLATFORMS: {
"use_spatial_hash": True,
},
LAYER_NAME_MOVING_PLATFORMS: {
"use_spatial_hash": False,
},
LAYER_NAME_LADDERS: {
"use_spatial_hash": True,
},
LAYER_NAME_COINS: {
"use_spatial_hash": True,
},
}
# Load in TileMap
self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
# Initiate New Scene with our TileMap, this will automatically add all layers
# from the map as SpriteLists in the scene in the proper order.
self.scene = arcade.Scene.from_tilemap(self.tile_map)
# Keep track of the score
self.score = 0
# Set up the player, specifically placing it at these coordinates.
image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
self.player_sprite.center_x = PLAYER_START_X
self.player_sprite.center_y = PLAYER_START_Y
self.scene.add_sprite("Player", self.player_sprite)
# Calculate the right edge of the my_map in pixels
self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
# --- Other stuff
# Set the background color
if self.tile_map.background_color:
arcade.set_background_color(self.tile_map.background_color)
# Create the 'physics engine'
self.physics_engine = arcade.PhysicsEnginePlatformer(
self.player_sprite,
platforms=self.scene[LAYER_NAME_MOVING_PLATFORMS],
gravity_constant=GRAVITY,
ladders=self.scene[LAYER_NAME_LADDERS],
walls=self.scene[LAYER_NAME_PLATFORMS]
)
def on_draw(self):
"""Render the screen."""
# Clear the screen to the background color
self.clear()
# Activate the game camera
self.camera.use()
# Draw our Scene
self.scene.draw()
# Activate the GUI camera before drawing GUI elements
self.gui_camera.use()
# Draw our score on the screen, scrolling it with the viewport
score_text = f"Score: {self.score}"
arcade.draw_text(
score_text,
10,
10,
arcade.csscolor.BLACK,
18,
)
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed."""
if key == arcade.key.UP or key == arcade.key.W:
if self.physics_engine.is_on_ladder():
self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
elif self.physics_engine.can_jump():
self.player_sprite.change_y = PLAYER_JUMP_SPEED
arcade.play_sound(self.jump_sound)
elif key == arcade.key.DOWN or key == arcade.key.S:
if self.physics_engine.is_on_ladder():
self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
def on_key_release(self, key, modifiers):
"""Called when the user releases a key."""
if key == arcade.key.UP or key == arcade.key.W:
if self.physics_engine.is_on_ladder():
self.player_sprite.change_y = 0
elif key == arcade.key.DOWN or key == arcade.key.S:
if self.physics_engine.is_on_ladder():
self.player_sprite.change_y = 0
elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_sprite.change_x = 0
elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = 0
def center_camera_to_player(self):
screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
screen_center_y = self.player_sprite.center_y - (
self.camera.viewport_height / 2
)
if screen_center_x < 0:
screen_center_x = 0
if screen_center_y < 0:
screen_center_y = 0
player_centered = screen_center_x, screen_center_y
self.camera.move_to(player_centered, 0.2)
def update(self, delta_time):
"""Movement and game logic"""
# Move the player with the physics engine
self.physics_engine.update()
# Update animations
self.scene.update_animation(
delta_time, [LAYER_NAME_COINS, LAYER_NAME_BACKGROUND]
)
# Update walls, used with moving platforms
self.scene.update([LAYER_NAME_MOVING_PLATFORMS])
# See if we hit any coins
coin_hit_list = arcade.check_for_collision_with_list(
self.player_sprite, self.scene[LAYER_NAME_COINS]
)
# Loop through each coin we hit (if any) and remove it
for coin in coin_hit_list:
# Figure out how many points this coin is worth
if "Points" not in coin.properties:
print("Warning, collected a coin without a Points property.")
else:
points = int(coin.properties["Points"])
self.score += points
# Remove the coin
coin.remove_from_sprite_lists()
arcade.play_sound(self.collect_coin_sound)
# Position the camera
self.center_camera_to_player()
def main():
"""Main function"""
window = MyGame()
window.setup()
arcade.run()
if __name__ == "__main__":
main()
这是一个基于arcade库创建的平台游戏。代码主要通过使用TileMap来生成游戏场景和精灵,并通过arcade.PhysicsEnginePlatformer实现玩家角色的移动。以下是对代码的逐行描述:
导入所需的库和模块:
- os:用于处理文件路径和当前工作目录。
- arcade:用于创建游戏窗口、精灵和场景等。
设置游戏窗口的常量:
- SCREEN_WIDTH:窗口的宽度。
- SCREEN_HEIGHT:窗口的高度。
- SCREEN_TITLE:窗口的标题。
定义一个名为MyGame的类,继承自arcade.Window。这是主应用程序类。
在MyGame类的构造函数中,调用父类的构造函数并设置窗口的大小和标题。然后初始化一些变量。
设置游戏的初始设置。
创建一个名为setup的方法,用于设置游戏的初始状态。这个方法会在开始游戏和重新开始游戏时调用。
34-42. 设置摄像机对象,用于控制屏幕的滚动和绘制GUI元素。
- 设置地图的名称。
48-57. 定义一个字典,存储各个图层的设置选项。
使用arcade库的load_tilemap方法加载地图。
使用从tilemap创建场景对象。
初始化分数变量。
69-73. 设置玩家精灵的图像和初始位置。
计算地图右边缘的像素位置。
设置背景颜色。
创建一个物理引擎,用于处理玩家精灵与平台和梯子的碰撞。
创建一个名为on_draw的方法,用于绘制屏幕。
92-93. 清空屏幕,并设置背景颜色。
96-99. 切换到游戏摄像机,并绘制场景。
102-104. 切换到GUI摄像机,并绘制分数。
- 创建一个名为on_key_press的方法,用于处理按键按下事件。
110-111. 检查玩家是否在梯子上,如果是,则改变竖直速度。如果可以跳跃,则进行跳跃操作。
115-116. 检查玩家是否在梯子上,如果是,则改变竖直速度。
118-119. 修改玩家横向速度。
122-123. 修改玩家横向速度。
- 创建一个名为on_key_release的方法,用于处理按键释放事件。
130-131. 检查玩家是否在梯子上,如果是,则将竖直速度设为0。
133-134. 检查玩家是否在梯子上,如果是,则将竖直速度设为0。
136-137. 将玩家横向速度设为0。
139-140. 将玩家横向速度设为0。
143-145. 将摄像机的位置设置为以玩家为中心。
创建一个名为update的方法,用于更新游戏逻辑和精灵的移动。
使用物理引擎更新玩家精灵的位置。
使用scene对象的update_animation方法更新动画。
使用scene对象的update方法更新墙壁。
162-169. 检查玩家是否与硬币精灵碰撞,并根据硬币的属性增加得分,并移除硬币精灵。
更新相机的位置。
创建一个名为main的方法。
180-182. 创建MyGame对象并调用setup方法。
启动游戏循环。
检查脚本是否作为主程序运行,如果是,则调用main方法。