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.
170 lines
6.2 KiB
170 lines
6.2 KiB
1 month ago
|
"""
|
||
|
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)
|