mirror of
https://github.com/csd4ni3l/ember-keeper.git
synced 2026-01-01 04:23:43 +01:00
fix best time not loading due to not using level num, update README, lock the game to 60 FPS, add hitboxes setting and camera shake
This commit is contained in:
@@ -1 +1,4 @@
|
||||
Ember Keeper is a platformer 2D game where you have to escape the outside temperatures, while collecting firewood to keep yourself warm while being out in the open.
|
||||
|
||||
The game is locked at 60 FPS since every interaction depends on that and i am lazy to introduce delta time.
|
||||
The game can be speedrunned and there is an option for hitboxes as well.
|
||||
43
game/play.py
43
game/play.py
@@ -40,6 +40,14 @@ class Game(arcade.gui.UIView):
|
||||
walls=[self.scene["walls"], self.scene["ice"]]
|
||||
)
|
||||
|
||||
self.camera_shake = arcade.camera.grips.ScreenShake2D(
|
||||
self.camera_sprites.view_data,
|
||||
max_amplitude=5,
|
||||
acceleration_duration=0.2,
|
||||
falloff_time=0.3,
|
||||
shake_frequency=10.0,
|
||||
)
|
||||
|
||||
self.warmth = 50
|
||||
self.direction = "right"
|
||||
self.last_jump = time.perf_counter()
|
||||
@@ -50,6 +58,7 @@ class Game(arcade.gui.UIView):
|
||||
self.restart_start = time.perf_counter()
|
||||
self.restarting = False
|
||||
self.won = False
|
||||
self.won_time = None
|
||||
|
||||
self.level_texts = []
|
||||
|
||||
@@ -74,7 +83,7 @@ class Game(arcade.gui.UIView):
|
||||
for level_num in range(AVAILABLE_LEVELS)
|
||||
})
|
||||
|
||||
self.best_time = self.data.get("best_time", 9999)
|
||||
self.best_time = self.data.get(f"{self.level_num}_best_time", 9999)
|
||||
self.tries = self.data.get("tries", 1)
|
||||
if self.best_time == 9999:
|
||||
self.no_besttime = True
|
||||
@@ -96,6 +105,8 @@ class Game(arcade.gui.UIView):
|
||||
self.info_label = self.anchor.add(arcade.gui.UILabel(text=f"Time took: 0s Best Time: {self.best_time}s Trees: 0 Tries: {self.tries}", font_size=20), anchor_x="center", anchor_y="top")
|
||||
|
||||
def reset(self, reached_end=False):
|
||||
self.camera_shake.start()
|
||||
|
||||
if not reached_end:
|
||||
self.warmth = 50
|
||||
self.trees = 0
|
||||
@@ -115,7 +126,7 @@ class Game(arcade.gui.UIView):
|
||||
if self.no_besttime:
|
||||
self.no_besttime = False
|
||||
|
||||
self.anchor.add(arcade.gui.UILabel(text=f"Level Complete! Time: {round(time.perf_counter() - self.start, 2)}s\nBest Time: {self.best_time}", multiline=True, font_size=30), anchor_x="center", anchor_y="center")
|
||||
self.anchor.add(arcade.gui.UILabel(text=f"Level Complete! Time: {self.won_time}s\nBest Time: {self.best_time}s", multiline=True, font_size=30), anchor_x="center", anchor_y="center")
|
||||
|
||||
self.back_button = arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text='<--', style=button_style, width=100, height=50)
|
||||
self.back_button.on_click = lambda event: self.main_exit()
|
||||
@@ -144,16 +155,23 @@ class Game(arcade.gui.UIView):
|
||||
def on_draw(self):
|
||||
self.clear()
|
||||
|
||||
self.camera_shake.update_camera()
|
||||
|
||||
if not self.won:
|
||||
with self.camera_sprites.activate():
|
||||
self.scene.draw()
|
||||
|
||||
if self.settings.get("hitboxes", False):
|
||||
self.scene.draw_hit_boxes(arcade.color.RED, 2)
|
||||
|
||||
for level_text in self.level_texts:
|
||||
level_text.draw()
|
||||
|
||||
arcade.draw_lbwh_rectangle_filled(self.window.width / 4, 0, (self.window.width / 2), self.window.height / 20, arcade.color.SKY_BLUE)
|
||||
arcade.draw_lbwh_rectangle_filled(self.window.width / 4, 0, (self.window.width / 2) * (self.warmth / 100), self.window.height / 20, arcade.color.RED)
|
||||
|
||||
self.camera_shake.readjust_camera()
|
||||
|
||||
self.ui.draw()
|
||||
|
||||
def center_camera_to_player(self):
|
||||
@@ -187,28 +205,33 @@ class Game(arcade.gui.UIView):
|
||||
|
||||
hit_list = self.physics_engine.update()
|
||||
self.center_camera_to_player()
|
||||
self.camera_shake.update(delta_time)
|
||||
|
||||
if self.player.collides_with_list(self.scene["end"]):
|
||||
end_time = round(time.perf_counter() - self.start, 2)
|
||||
end_time = round(time.perf_counter() - self.start, 4)
|
||||
|
||||
if self.no_besttime or end_time < self.best_time:
|
||||
self.best_time = end_time
|
||||
|
||||
self.won_time = end_time
|
||||
|
||||
self.reset(True)
|
||||
return
|
||||
|
||||
if self.no_besttime:
|
||||
self.best_time = round(time.perf_counter() - self.start, 2)
|
||||
self.best_time = round(time.perf_counter() - self.start, 4)
|
||||
|
||||
self.info_label.text = f"Time took: {round(time.perf_counter() - self.start, 2)}s Best Time: {self.best_time}s Trees: {self.trees} Tries: {self.tries}"
|
||||
self.info_label.text = f"Time took: {round(time.perf_counter() - self.start, 4)}s Best Time: {self.best_time}s Trees: {self.trees} Tries: {self.tries}"
|
||||
|
||||
if self.warmth <= 0 or self.player.collides_with_list(self.scene["spikes"]) or self.player.center_y < 0:
|
||||
self.reset()
|
||||
|
||||
if self.player.center_x + self.player.width / 2 < 0:
|
||||
self.camera_shake.start()
|
||||
self.player.center_x = self.player.width / 2
|
||||
|
||||
if self.player.center_x - self.player.width / 2 > tilemaps[self.level_num].width * GRID_PIXEL_SIZE:
|
||||
self.camera_shake.start()
|
||||
self.player.center_x = (tilemaps[self.level_num].width * GRID_PIXEL_SIZE) - self.player.width / 2
|
||||
|
||||
for tree in self.player.collides_with_list(self.scene["trees"]):
|
||||
@@ -222,7 +245,7 @@ class Game(arcade.gui.UIView):
|
||||
if checkpoint not in self.checkpoints_hit:
|
||||
self.scene["checkpoints"].remove(checkpoint)
|
||||
self.checkpoints_hit.add(checkpoint)
|
||||
self.spawn_position = checkpoint.position + arcade.math.Vec2(-GRID_PIXEL_SIZE / 8, GRID_PIXEL_SIZE / 2)
|
||||
self.spawn_position = checkpoint.position + arcade.math.Vec2(-GRID_PIXEL_SIZE / 4, GRID_PIXEL_SIZE)
|
||||
|
||||
moved = False
|
||||
ice_touch = any([ice_sprite in hit_list for ice_sprite in self.scene["ice"]]) and self.physics_engine.can_jump()
|
||||
@@ -248,9 +271,9 @@ class Game(arcade.gui.UIView):
|
||||
on_right_left_diagonal = any([True for hit in hit_list if hit in self.right_left_diagonal_sprites])
|
||||
|
||||
if on_left_right_diagonal or (self.direction == "right" and not on_right_left_diagonal):
|
||||
self.player.change_x = self.clamp(self.player.change_x * 0.75, PLAYER_MOVEMENT_SPEED * 0.4, PLAYER_MOVEMENT_SPEED)
|
||||
self.player.change_x = self.clamp(self.player.change_x * 0.75, PLAYER_MOVEMENT_SPEED * 0.3, PLAYER_MOVEMENT_SPEED)
|
||||
else:
|
||||
self.player.change_x = self.clamp(self.player.change_x * 0.75, -PLAYER_MOVEMENT_SPEED, -PLAYER_MOVEMENT_SPEED * 0.4)
|
||||
self.player.change_x = self.clamp(self.player.change_x * 0.75, -PLAYER_MOVEMENT_SPEED, -PLAYER_MOVEMENT_SPEED * 0.3)
|
||||
else:
|
||||
self.player.change_x = 0
|
||||
|
||||
@@ -260,6 +283,7 @@ class Game(arcade.gui.UIView):
|
||||
self.warmth = self.clamp(self.warmth - 0.15, 0, 100)
|
||||
|
||||
if self.warmth < 40:
|
||||
self.camera_shake.start()
|
||||
if self.settings.get("sfx", True) and not self.freeze_player.playing:
|
||||
self.freeze_player.play()
|
||||
else:
|
||||
@@ -268,13 +292,14 @@ class Game(arcade.gui.UIView):
|
||||
|
||||
if self.player.change_y > 0:
|
||||
self.change_player_animation(player_jump_animation)
|
||||
elif abs(self.player.change_x) > PLAYER_MOVEMENT_SPEED * 0.4:
|
||||
elif abs(self.player.change_x) > PLAYER_MOVEMENT_SPEED * 0.3:
|
||||
self.change_player_animation(player_walk_animation)
|
||||
else:
|
||||
self.change_player_animation(player_still_animation)
|
||||
|
||||
for level_text in self.level_texts:
|
||||
if level_text.change_to_when_hit and self.player.rect.intersection(level_text.rect):
|
||||
self.camera_shake.start()
|
||||
level_text.text = level_text.change_to_when_hit
|
||||
|
||||
self.player.update_animation()
|
||||
|
||||
@@ -147,22 +147,8 @@ class Settings(arcade.gui.UIView):
|
||||
width, height = map(int, self.settings_dict['resolution'].split('x'))
|
||||
self.window.set_size(width, height)
|
||||
|
||||
if self.settings_dict['vsync']:
|
||||
self.window.set_vsync(True)
|
||||
display_mode = self.window.display.get_default_screen().get_mode()
|
||||
refresh_rate = display_mode.rate
|
||||
self.window.set_update_rate(1 / refresh_rate)
|
||||
self.window.set_draw_rate(1 / refresh_rate)
|
||||
|
||||
elif not self.settings_dict['fps_limit'] == 0:
|
||||
self.window.set_vsync(False)
|
||||
self.window.set_update_rate(1 / self.settings_dict['fps_limit'])
|
||||
self.window.set_draw_rate(1 / self.settings_dict['fps_limit'])
|
||||
|
||||
else:
|
||||
self.window.set_vsync(False)
|
||||
self.window.set_update_rate(1 / 99999999)
|
||||
self.window.set_draw_rate(1 / 99999999)
|
||||
self.window.set_update_rate(1 / 60)
|
||||
self.window.set_draw_rate(1 / 60)
|
||||
|
||||
if self.settings_dict['discord_rpc']:
|
||||
if isinstance(self.pypresence_client, FakePyPresence): # the user has enabled RPC in the settings in this session.
|
||||
|
||||
27
run.py
27
run.py
@@ -14,13 +14,10 @@ pyglet.font.add_directory(os.path.join(script_dir, 'assets', 'fonts'))
|
||||
from utils.utils import get_closest_resolution, print_debug_info, on_exception
|
||||
from utils.constants import log_dir, menu_background_color
|
||||
from menus.main import Main
|
||||
# from utils.preload import theme_sound # needed for preload
|
||||
from arcade.experimental.controller_window import ControllerWindow
|
||||
|
||||
sys.excepthook = on_exception
|
||||
|
||||
# __builtins__.print = lambda *args, **kwargs: logging.debug(" ".join(map(str, args)))
|
||||
|
||||
if not log_dir in os.listdir():
|
||||
os.makedirs(log_dir)
|
||||
|
||||
@@ -58,7 +55,6 @@ if os.path.exists('settings.json'):
|
||||
fullscreen = settings['window_mode'] == 'Fullscreen'
|
||||
style = arcade.Window.WINDOW_STYLE_BORDERLESS if settings['window_mode'] == 'borderless' else arcade.Window.WINDOW_STYLE_DEFAULT
|
||||
vsync = settings['vsync']
|
||||
fps_limit = settings['fps_limit']
|
||||
else:
|
||||
resolution = get_closest_resolution()
|
||||
antialiasing = 4
|
||||
@@ -73,7 +69,6 @@ else:
|
||||
fullscreen = False
|
||||
style = arcade.Window.WINDOW_STYLE_DEFAULT
|
||||
vsync = True
|
||||
fps_limit = 0
|
||||
|
||||
settings = {
|
||||
"music": True,
|
||||
@@ -81,38 +76,20 @@ else:
|
||||
"resolution": f"{resolution[0]}x{resolution[1]}",
|
||||
"antialiasing": "4x MSAA",
|
||||
"window_mode": "Windowed",
|
||||
"vsync": True,
|
||||
"fps_limit": 60,
|
||||
"discord_rpc": True
|
||||
}
|
||||
|
||||
with open("settings.json", "w") as file:
|
||||
file.write(json.dumps(settings))
|
||||
|
||||
# if settings.get("music", True):
|
||||
# theme_sound.play(volume=settings.get("music_volume", 50) / 100, loop=True)
|
||||
|
||||
try:
|
||||
window = ControllerWindow(width=resolution[0], height=resolution[1], title='Ember Keeper', samples=antialiasing, antialiasing=antialiasing > 0, fullscreen=fullscreen, vsync=vsync, resizable=False, style=style, visible=False)
|
||||
except (FileNotFoundError, PermissionError) as e:
|
||||
logging.warning(f"Controller support unavailable: {e}. Falling back to regular window.")
|
||||
window = arcade.Window(width=resolution[0], height=resolution[1], title='Ember Keeper', samples=antialiasing, antialiasing=antialiasing > 0, fullscreen=fullscreen, vsync=vsync, resizable=False, style=style, visible=False)
|
||||
|
||||
if vsync:
|
||||
window.set_vsync(True)
|
||||
display_mode = window.display.get_default_screen().get_mode()
|
||||
if display_mode:
|
||||
refresh_rate = display_mode.rate
|
||||
else:
|
||||
refresh_rate = 60
|
||||
window.set_update_rate(1 / refresh_rate)
|
||||
window.set_draw_rate(1 / refresh_rate)
|
||||
elif not fps_limit == 0:
|
||||
window.set_update_rate(1 / fps_limit)
|
||||
window.set_draw_rate(1 / fps_limit)
|
||||
else:
|
||||
window.set_update_rate(1 / 99999999)
|
||||
window.set_draw_rate(1 / 99999999)
|
||||
window.set_update_rate(1 / 60)
|
||||
window.set_draw_rate(1 / 60)
|
||||
|
||||
arcade.set_background_color(menu_background_color)
|
||||
|
||||
|
||||
@@ -38,17 +38,14 @@ settings = {
|
||||
"Window Mode": {"type": "option", "options": ["Windowed", "Fullscreen", "Borderless"], "config_key": "window_mode", "default": "Windowed"},
|
||||
"Resolution": {"type": "option", "options": ["1366x768", "1440x900", "1600x900", "1920x1080", "2560x1440", "3840x2160"], "config_key": "resolution"},
|
||||
"Anti-Aliasing": {"type": "option", "options": ["None", "2x MSAA", "4x MSAA", "8x MSAA", "16x MSAA"], "config_key": "anti_aliasing", "default": "4x MSAA"},
|
||||
"VSync": {"type": "bool", "config_key": "vsync", "default": True},
|
||||
"FPS Limit": {"type": "slider", "min": 0, "max": 480, "config_key": "fps_limit", "default": 60},
|
||||
},
|
||||
"Sound": {
|
||||
"Music": {"type": "bool", "config_key": "music", "default": True},
|
||||
"SFX": {"type": "bool", "config_key": "sfx", "default": True},
|
||||
"Music Volume": {"type": "slider", "min": 0, "max": 100, "config_key": "music_volume", "default": 50},
|
||||
"SFX Volume": {"type": "slider", "min": 0, "max": 100, "config_key": "sfx_volume", "default": 50},
|
||||
},
|
||||
"Miscellaneous": {
|
||||
"Discord RPC": {"type": "bool", "config_key": "discord_rpc", "default": True},
|
||||
"Hitboxes": {"type": "bool", "config_key": "hitboxes", "default": False},
|
||||
},
|
||||
"Credits": {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user