You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
6.2 KiB

"""
ICE WALKER LEVEL EDITOR
Runs a level editor which can be used to create and edit levels
"""
import objects
import json
import pyxel
CELL_SIZE = 8
GRID_X = 11
GRID_Y = 6
WIN_X = CELL_SIZE*GRID_X
WIN_Y = CELL_SIZE*GRID_Y
def load_file(lvl: int) -> list:
"""
:param lvl: (int) The id of the level to load
:return: (list) The loaded level from the file
Reads the corresponding level file and returns its loaded content
"""
file = open(f"lvl/{lvl}.json", "r", encoding="utf-8")
decoder = json.JSONDecoder()
return decoder.decode(file.read())
def can_load(lvl: int) -> bool:
"""
:param lvl: (int) The id of the file to check for
:return: (bool) Returns if the level file exists and can be loaded
Tests if a level can be loaded, returns the result as a boolean
"""
try:
file = load_file(lvl)
return len(file) == GRID_Y
except FileNotFoundError:
return False
class LevelManager:
"""
This class is responsible for handling game data
"""
def __init__(self):
self.current_level = 0
self.cache = []
def load_level(self, lvl: int):
"""
:param lvl: (int) The id of the level to load
Loads the specified level and stores it into the cache
"""
if can_load(lvl):
self.cache = load_file(lvl)
self.current_level = lvl
print(f"editing level {lvl}")
def load_collection_from_cache(self, collection: objects.Collection) -> objects.Player | None:
"""
:param collection: (Collection) The collection to load the objects into
:return: The player object created by this method
Loads the level elements using the loaded cache, returns the new player instance
"""
new_player = None
for y in range(len(self.cache)):
for x in range(len(self.cache[y])):
element_id = self.cache[y][x]
elt = objects.get_type_from_id(element_id)
if elt:
new = elt()
new.position = objects.V2(x, y)
collection.add(new)
if element_id == 3:
new_player = new
return new_player
class Game:
"""
This class manages the game runtime
"""
def __init__(self):
self.level = None # An instance of the LevelManager class
self.main_collection = None # An instance of Collection, usually the main one
self.editor_collection = None # An instance of Collection used by the editor and ignored while saving
self.player = None # The current instance of the Player object
self.cursor = objects.Cursor()
self._drag = None # The current object being dragged in the editor
self._dragging = False # Determines is the mouse is currently in the dragging state
def _upd(self):
if self.player:
change_level = 0
# Update cursor position
mx, my = pyxel.mouse_x, pyxel.mouse_y
self.cursor.position = objects.clamp_in_range(objects.V2(mx//CELL_SIZE, my//CELL_SIZE))
if pyxel.btnp(pyxel.KEY_LEFT): # Level switch controls
change_level = -1
elif pyxel.btnp(pyxel.KEY_RIGHT):
change_level = 1
elif pyxel.btnp(pyxel.KEY_DELETE): # Object management controls
obj = self.main_collection.get_at_position(self.cursor.position)
if obj and obj.can_be_deleted:
self.main_collection.remove(obj, True)
if change_level != 0: # Clear cache and load another level
self._drag = None
self._dragging = False
self.level.load_level(self.level.current_level + change_level)
self.main_collection.clear_all()
new_player = self.level.load_collection_from_cache(self.main_collection)
self.player = new_player
self.main_collection.add(self.editor_collection)
return # Not handle the rest because not necessary
if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT):
if not self._dragging:
self._dragging = True
self._drag = self.main_collection.get_at_position(self.cursor.position)
elif self._drag:
self._drag.position = self.cursor.position
else:
self._dragging = False
self._drag = None
def _display(self):
pyxel.cls(0)
for v in self.main_collection.get_objects(): # Render all active objects
if isinstance(v, objects.MapObject):
v.draw()
elif isinstance(v, objects.Collection) and v.visible: # Render embedded collections, if visible
for embed_v in v.get_objects():
embed_v.draw()
# Draw cursor
pyxel.rect(pyxel.mouse_x, pyxel.mouse_y, 1, 1, 5)
def run(self, lvl_m: LevelManager, main_c: objects.Collection, editor_c: objects.Collection):
"""
:param lvl_m: (LevelManager) The instance of LevelManager to assign with the game instance
:param main_c: (Collection) The primary collection containing the entirety of the game
:param editor_c: (Collection) The editor's collection
Initializes and runs the editor
"""
self.level = lvl_m
self.main_collection = main_c
self.editor_collection = editor_c
editor_c.add(self.cursor)
main_c.add(editor_c)
pyxel.init(WIN_X, WIN_Y, title="ice_walker")
pyxel.run(self._upd, self._display)
if __name__ == "__main__":
main_collection = objects.Collection()
editor_collection = objects.Collection()
level = LevelManager() # Load the first level of the game
level.load_level(1)
player = level.load_collection_from_cache(main_collection)
game = Game() # Initializes the game engine
game.player = player
game.run(level, main_collection, editor_collection)