Started writing new level creation

main
Yan Wittmann 2023-03-27 11:46:03 +02:00
parent fd29b7f1df
commit e53c1084df
19 changed files with 311 additions and 143 deletions

View File

@ -0,0 +1,12 @@
{
"level": {
"block_size": [
12,
12
],
"level_size": [
71,
40
]
}
}

View File

@ -26,7 +26,7 @@
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,D,,,# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,x,x,x,x,x,x,x,x,x,,,,,,,,,,,,,,,,,,,,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
@ -39,3 +39,4 @@
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,,,,,,,,,,,,,,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
3,3,id=HEBEL
Can't render this file because it has a wrong number of fields in line 42.

View File

@ -93,7 +93,7 @@
"id": "tutorial_block_full", "id": "tutorial_block_full",
"subsheets": [ "subsheets": [
{ {
"id": "1", "id": "on",
"delays": [ "delays": [
1 1
], ],
@ -101,7 +101,7 @@
"height": 16 "height": 16
}, },
{ {
"id": "2", "id": "off",
"delays": [ "delays": [
1 1
], ],
@ -146,5 +146,153 @@
"height": 12 "height": 12
} }
] ]
},
{
"id": "castle_block_wall_left",
"subsheets": [
{
"id": "1",
"delays": [
1
],
"width": 12,
"height": 12
},
{
"id": "2",
"delays": [
1
],
"width": 12,
"height": 12
},
{
"id": "3",
"delays": [
1
],
"width": 12,
"height": 12
}
]
},
{
"id": "castle_block_wall_right",
"subsheets": [
{
"id": "1",
"delays": [
1
],
"width": 12,
"height": 12
},
{
"id": "2",
"delays": [
1
],
"width": 12,
"height": 12
},
{
"id": "3",
"delays": [
1
],
"width": 12,
"height": 12
}
]
},
{
"id": "ghost_character",
"subsheets": [
{
"id": "idle",
"delays": [
40,
10
],
"width": 24,
"height": 36
},
{
"id": "walk_l",
"delays": [
1,
1
],
"width": 24,
"height": 36
},
{
"id": "walk_r",
"delays": [
1,
1
],
"width": 24,
"height": 36
},
{
"id": "jump",
"delays": [
1,
1
],
"width": 24,
"height": 36
}
]
},
{
"id": "test_1",
"subsheets": [
{
"id": "walk_l",
"delays": [
1,
2,
3,
1
],
"width": 16,
"height": 16
},
{
"id": "idle",
"delays": [
1,
1,
3,
1,
1
],
"width": 16,
"height": 16
},
{
"id": "walk_r",
"delays": [
1,
1,
1,
1
],
"width": 16,
"height": 16
},
{
"id": "other_test",
"delays": [
1,
1,
1
],
"width": 16,
"height": 16
}
]
} }
] ]

View File

@ -1,4 +1,5 @@
LEVEL_SIZE = (71, 40) from physics import ConstantsParser
class Level: class Level:
@ -15,13 +16,13 @@ class Level:
tiles = [] tiles = []
for line_number, line in enumerate(csv_grid): for line_number, line in enumerate(csv_grid):
if line_number <= LEVEL_SIZE[1]: if line_number <= ConstantsParser.CONFIG.level_size[1]:
tiles.append([]) tiles.append([])
for item_number, item in enumerate(line): for item_number, item in enumerate(line):
if item_number <= LEVEL_SIZE[0]: if item_number <= ConstantsParser.CONFIG.level_size[0]:
tiles[line_number].append({'name': item}) tiles[line_number].append({'name': item})
else: else:
print('Level is too wide:', item_number, '>', LEVEL_SIZE[0]) print('Level is too wide:', item_number, '>', ConstantsParser.CONFIG.level_size[0])
break break
elif line[0] != '': elif line[0] != '':

View File

@ -1,7 +0,0 @@
from level.elements.StaticLevelElement import StaticLevelElement
from sprite.Spritesheet import Spritesheet
class BlockElement(StaticLevelElement):
def __init__(self, spritesheet: Spritesheet):
super().__init__(spritesheet)

View File

@ -0,0 +1,20 @@
from level.Level import Level
from level.elements.InputLevelElement import InputLevelElement
from physics.SpriteManager import SpriteManager
from physics.TickData import TickData
from sprite.SpritesheetManager import SpritesheetManager
class ButtonInputLevelElement(InputLevelElement):
def load(self, sprite_manager: SpriteManager, spritesheet_manager: SpritesheetManager, level: Level):
name = self.tile['name']
position = self.tile['position']
self.spritesheet = spritesheet_manager.get_sheet('pressureplate')
self.set_animation_state('on' if self.emitter_state else 'off')
self.position_scale.position = position
def tick(self, tick_data: TickData):
collisions = self.get_collides_with()
print(collisions)

View File

@ -1,14 +0,0 @@
from level.elements.StaticLevelElement import StaticLevelElement
from sprite.Spritesheet import Spritesheet
class DoorElement(StaticLevelElement):
def __init__(self, spritesheet: Spritesheet, id : str, requires : list[str]):
super().__init__(spritesheet)
self.id = id
self.requires = requires
def openDoor(self):
pass
def closeDoor(self):
pass

View File

@ -1,8 +0,0 @@
from level.elements.DoorElement import DoorElement
from sprite.Spritesheet import Spritesheet
class GateElement(DoorElement):
def __int__(self, spritesheet: Spritesheet, id : str, requires : list[str]):
super().__int__(spritesheet, id, requires)

View File

@ -1,7 +0,0 @@
from level.elements.DoorElement import DoorElement
from sprite.Spritesheet import Spritesheet
class GoalDoorElement(DoorElement):
def __int__(self, spritesheet: Spritesheet, id : str, requires : list[str]):
super().__int__(spritesheet, id, requires)

View File

@ -0,0 +1,14 @@
from abc import ABC
from level.elements.StaticLevelElement import StaticLevelElement
class InputLevelElement(StaticLevelElement, ABC):
def __init__(self, tile: dict, loaded_level):
super().__init__(tile, loaded_level)
if 'emitter_state' in tile:
self.emitter_state = tile['emitter_state'] == 'true'
else:
self.emitter_state = False

View File

@ -1,12 +0,0 @@
from level.elements.StaticLevelElement import StaticLevelElement
from sprite.Spritesheet import Spritesheet
class InteractableElement(StaticLevelElement):
def __init__(self, spritesheet: Spritesheet, block_id: str):
super().__init__(spritesheet)
self.block_id = block_id
self.element = []
def connect_element(self, element: StaticLevelElement):
self.element.append(element)

View File

@ -0,0 +1,20 @@
from abc import abstractmethod
from level.Level import Level
from physics.SpriteManager import SpriteManager
from sprite.SpritesheetManager import SpritesheetManager
class LevelElement:
def __init__(self, tile: dict, loaded_level):
self.tile = tile
self.loaded_level = loaded_level
self.sprites = []
@abstractmethod
def load(self, sprite_manager: SpriteManager, spritesheet_manager: SpritesheetManager, level: Level):
pass
def destroy(self, sprite_manager: SpriteManager):
for sprite in self.sprites:
sprite_manager.remove_ui_element(sprite)

View File

@ -1,16 +1,17 @@
import random import random
from level.Level import Level from level.Level import Level
from level.LevelElementSymbols import LevelElementSymbols from level.elements.ButtonInputLevelElement import ButtonInputLevelElement
from level.elements.BlockElement import BlockElement from level.elements.SimpleBlockLevelElement import SimpleBlockLevelElement
from level.elements.GateElement import GateElement from physics import ConstantsParser
from level.elements.GoalDoorElement import GoalDoorElement
from level.elements.InteractableElement import InteractableElement
from level.elements.StaticLevelElement import StaticLevelElement
from physics.SpriteManager import SpriteManager, DrawLayers from physics.SpriteManager import SpriteManager, DrawLayers
from sprite.SpritesheetManager import SpritesheetManager from sprite.SpritesheetManager import SpritesheetManager
BLOCK_SIZE = (12, 12) TILES = {
'#': SimpleBlockLevelElement,
'L': ButtonInputLevelElement, # TODO
'P': ButtonInputLevelElement
}
class LoadedLevel: class LoadedLevel:
@ -20,53 +21,21 @@ class LoadedLevel:
self.blocks = [] self.blocks = []
def load_level(self, level: Level): def load_level(self, level: Level):
requires_dict: dict[str, list[StaticLevelElement]] = {}
for row_number, row in enumerate(level.tiles): for row_number, row in enumerate(level.tiles):
for tile_number, tile in enumerate(row): for tile_number, tile in enumerate(row):
position = (tile_number * BLOCK_SIZE[0], row_number * BLOCK_SIZE[1]) position = (tile_number * ConstantsParser.CONFIG.block_size[0],
row_number * ConstantsParser.CONFIG.block_size[1])
tile['position'] = position
tile_element = LevelElementSymbols.dict[tile['name']] name = tile['name']
if name in TILES:
element = TILES[name](tile, self)
self.blocks.append(element)
self.sprite_manager.add_ui_element(DrawLayers.OBJECTS, element)
element.load(self.sprite_manager, self.spritesheet_manager, level)
sprite = None elif not name == '':
print(f"Unknown tile: {name}")
if tile_element in LevelElementSymbols.BLOCKS_LIST:
spritesheet = self.spritesheet_manager.get_sheet(level.theme + tile_element['sprite_id'])
sprite = BlockElement(spritesheet)
sprite.set_animation_state(str(random.randint(1, 3)))
sprite.position_scale.position = position
elif tile_element == LevelElementSymbols.GOAL_DOOR:
spritesheet = self.spritesheet_manager.get_sheet(tile_element['sprite_id'])
sprite = GoalDoorElement(spritesheet, tile['id'], tile['requires'])
sprite.set_animation_state('closed')
sprite.position_scale.position = position
for required in tile['requires']:
requires_dict[required] = requires_dict.get(required, []).append(sprite)
elif tile_element == LevelElementSymbols.GATE:
spritesheet = self.spritesheet_manager.get_sheet(tile_element['sprite_id'])
sprite = GateElement(spritesheet, tile['id'], tile['requires'])
sprite.set_animation_state('closed')
sprite.position_scale.position = position
for required in tile['requires']:
requires_dict[required] = requires_dict.get(required, []).append(sprite)
elif tile_element in LevelElementSymbols.INTERACTABLE_LIST:
spritesheet = self.spritesheet_manager.get_sheet(tile_element['sprite_id'])
sprite = InteractableElement(spritesheet, tile['id'])
sprite.set_animation_state('on')
sprite.position_scale.position = position
# tile['id'], tile['requirements']
if sprite is not None:
self.blocks.append(sprite)
self.sprite_manager.add_ui_element(DrawLayers.OBJECTS, sprite)
for block in self.blocks:
if isinstance(block, InteractableElement):
for requirement in requires_dict:
block.connect_element(requirement)
def destroy_level(self): def destroy_level(self):
for block in self.blocks: for block in self.blocks:

View File

@ -0,0 +1,24 @@
import random
from level.Level import Level
from level.elements.StaticLevelElement import StaticLevelElement
from physics.SpriteManager import SpriteManager
from sprite.SpritesheetManager import SpritesheetManager
BLOCK_TYPES = {
'#': 'block_full'
}
class SimpleBlockLevelElement(StaticLevelElement):
def __init__(self, tile: dict, loaded_level):
super().__init__(tile, loaded_level)
def load(self, sprite_manager: SpriteManager, spritesheet_manager: SpritesheetManager, level: Level):
name = self.tile['name']
position = self.tile['position']
block_type = BLOCK_TYPES[name]
self.spritesheet = spritesheet_manager.get_sheet(level.theme + '_' + block_type)
self.set_animation_state(str(random.randint(1, 3)))
self.position_scale.position = position

View File

@ -1,7 +1,16 @@
from sprite.Spritesheet import Spritesheet import uuid
from abc import ABC
from level.elements.LevelElement import LevelElement
from sprite.StaticSprite import StaticSprite from sprite.StaticSprite import StaticSprite
class StaticLevelElement(StaticSprite): class StaticLevelElement(StaticSprite, LevelElement, ABC):
def __init__(self, spritesheet: Spritesheet): def __init__(self, tile: dict, loaded_level):
super().__init__(spritesheet) StaticSprite.__init__(self)
LevelElement.__init__(self, tile, loaded_level)
if 'id' in tile:
self.id = tile['id']
else:
self.id = uuid.uuid4()

View File

@ -0,0 +1,11 @@
import json
class ConstantsParser:
def __init__(self, file: str):
parsed = json.load(open(file))
self.level_size = (parsed['level']['level_size'][0], parsed['level']['level_size'][1])
self.block_size = (parsed['level']['block_size'][0], parsed['level']['block_size'][1])
CONFIG = ConstantsParser("data/config/config.json")

View File

@ -1,3 +1,5 @@
from typing import Optional
from physics.CollisionDirection import CollisionDirection from physics.CollisionDirection import CollisionDirection
from sprite.Spritesheet import Spritesheet from sprite.Spritesheet import Spritesheet
from sprite.StaticSprite import StaticSprite from sprite.StaticSprite import StaticSprite
@ -5,7 +7,7 @@ from physics.TickData import TickData
class DynamicSprite(StaticSprite): class DynamicSprite(StaticSprite):
def __init__(self, spritesheet: Spritesheet): def __init__(self, spritesheet: Optional[Spritesheet] = None):
super().__init__(spritesheet) super().__init__(spritesheet)
self.motion = (0, 0) self.motion = (0, 0)

View File

@ -12,11 +12,14 @@ from ui_elements.UiElement import UiElement
class Sprite(UiElement): class Sprite(UiElement):
def __init__(self, spritesheet: Spritesheet): def __init__(self, spritesheet: Optional[Spritesheet] = None):
super().__init__() super().__init__()
self.spritesheet = spritesheet self.spritesheet = spritesheet
if self.spritesheet is not None:
self.animation_state = list(self.spritesheet.animations.keys())[0] self.animation_state = list(self.spritesheet.animations.keys())[0]
else:
self.animation_state = None
self.animation_frame = 0 self.animation_frame = 0
self.animation_delay = 0 self.animation_delay = 0
@ -43,6 +46,10 @@ class Sprite(UiElement):
return None return None
def tick(self, tick_data: TickData): def tick(self, tick_data: TickData):
if self.spritesheet is None:
self.image = None
return
animation = self.spritesheet.animations[self.animation_state] animation = self.spritesheet.animations[self.animation_state]
if self.animated: if self.animated:
@ -55,10 +62,15 @@ class Sprite(UiElement):
self.image = animation['images'][self.animation_frame % len(animation['images'])] self.image = animation['images'][self.animation_frame % len(animation['images'])]
def set_animation_state(self, state: str): def set_animation_state(self, state: str):
if self.spritesheet is None:
return
if state in self.spritesheet.animations: if state in self.spritesheet.animations:
self.animation_state = state self.animation_state = state
self.animation_delay = 0 self.animation_delay = 0
self.tick(self.empty_tick_data()) self.tick(self.empty_tick_data())
else:
print('Unknown animation state: ' + state)
def set_animation_frame(self, frame: int): def set_animation_frame(self, frame: int):
self.animation_frame = frame self.animation_frame = frame
@ -77,32 +89,3 @@ class Sprite(UiElement):
self.image.get_width() * self.position_scale.scale[0], self.image.get_width() * self.position_scale.scale[0],
self.image.get_height() * self.position_scale.scale[1] self.image.get_height() * self.position_scale.scale[1]
) )
def dump(self, file):
# re-attach all the images to a single surface and save it to a file
width = 0
height = 0
for animation in self.spritesheet.animations.values():
max_height = 0
total_width = 0
for image in animation['images']:
total_width += image.get_width()
max_height = max(max_height, image.get_height())
width = max(width, total_width)
height += max_height
sheet = pygame.Surface((width, height))
x = 0
y = 0
for animation in self.spritesheet.animations.values():
max_height = 0
for image in animation['images']:
sheet.blit(image, (x, y))
x += image.get_width()
max_height = max(max_height, image.get_height())
y += max_height
x = 0
pygame.image.save(sheet, file)

View File

@ -1,9 +1,11 @@
from typing import Optional
from sprite.Sprite import Sprite from sprite.Sprite import Sprite
from sprite.Spritesheet import Spritesheet from sprite.Spritesheet import Spritesheet
class StaticSprite(Sprite): class StaticSprite(Sprite):
def __init__(self, spritesheet: Spritesheet): def __init__(self, spritesheet: Optional[Spritesheet] = None):
super().__init__(spritesheet) super().__init__(spritesheet)
def collides_with(self, collider: 'StaticSprite', tolerance: float = 0.0): def collides_with(self, collider: 'StaticSprite', tolerance: float = 0.0):