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
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)
|
|
|