diff --git a/docs/changelog/v3.md b/docs/changelog/v3.md index fafa6ddf..63288a9c 100644 --- a/docs/changelog/v3.md +++ b/docs/changelog/v3.md @@ -3,7 +3,7 @@ hide: - navigation --- -## 3.0.0 Todo/Changelog +## V3 Todo/Changelog - [x] Engine - [x] Window @@ -28,4 +28,4 @@ hide: - [x] Sound system - [x] Sound player - [x] File support - - [x] And more... \ No newline at end of file + - [x] And more... diff --git a/docs/changelog/v4.md b/docs/changelog/v4.md index 8d93d085..3368f75c 100644 --- a/docs/changelog/v4.md +++ b/docs/changelog/v4.md @@ -2,33 +2,37 @@ hide: - navigation --- +# Version 4 Todo/Changelog -## 4.0.0 Todo/Changelog - +## V4 - [x] Rewrite codebase - [x] Cleaner api - [x] Rewrite documentation - [x] Better documentation - [x] Easier usage -## 4.1.0 Todo/Changelog - +## V4.1 - [x] Scenes managment - [x] Creating different scenes - [x] Scene manager - [x] Scenes classes - [x] Main scene class (SceneManager) - - [ ] Docs for it + - [x] Docs for it - [x] Paths Rewrite -## 4.2.0 Todo/Changelog -- [ ] New way of a applcation (Application class) - - [ ] Inheritance based - - [ ] Build in loop - - [ ] Make this optional and not the main way -- [ ] Animation system - - [ ] Load images - - [ ] Play animation +## V4.2 +- [x] Animation system + - [x] Load images + - [x] Play animation + - [x] Entities have frames that you can manipulate +- [x] New keys system + +## V4.3 +- [ ] Physics engine + - [ ] Box2D + - [ ] Static Body + - [ ] Rigid Body + - [ ] Integration with entities diff --git a/docs/index.md b/docs/index.md index 91598aa8..328ed4d2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,10 +55,6 @@ And your done! - [Wiki](v4/wiki/index.md) -### Api - - [Setting up](#setting-up) - - [API](v4/api/api.md) - ### Tutorials - [Tutorials](v4/tutorials/index.md) @@ -71,13 +67,22 @@ The wiki, api and tutorials to the old v3 version of fusion, which is not being - [Wiki](v3/wiki/index.md) +### Tutorials + - [Tutorials](v3/tutorials/index.md) + + +## Api +There pages aren't maintained anymore and won't be. Please head over to the Wiki pages, tutorials or examples. ### Api + - [Setting up](#setting-up) + - [API](v4/api/api.md) + + +### V3 Api - [Setting up](#setting-up-v3) - [API](v3/api/api.md) -### Tutorials - - [Tutorials](v3/tutorials/index.md) - + ## 💻 Setting up v3 diff --git a/docs/v4/wiki/wiki.md b/docs/v4/wiki/wiki.md index ed58de17..a4c71415 100644 --- a/docs/v4/wiki/wiki.md +++ b/docs/v4/wiki/wiki.md @@ -145,7 +145,7 @@ Then you need to render it (In the best situation this will happen in your loop) your_image.draw() ``` -## Create entity WARNING: PRE ALPHA (It's in really early stages) +## Entities If you want a player or an enemy or some moving object in your game, you can use an entity, thats an object that helps you manage things in your game: @@ -182,6 +182,51 @@ Then you can draw it with: your_entity.draw_image() ``` +### Custom animations with entities +Fusion has some build-in features into entity system to make animations more easy, here are some ways to use it + +#### Load frames +First of all, you need to load frames, and you can do this using this way: +```python +your_entity.load_animation(images: tuple) +``` + +#### Setting current frame +You can set the current frame with this function +```python +your_entity.set_frame(frame: int) +``` + +#### Getting current frame +To get the current frame, run: +```python +my_frame_var = your_entity.get_frame() +``` + +#### Drawing current frame +To draw current frame, use this function +```python +your_entity.draw_animation() +``` + +## Animation system (Early stages) +If you want to draw a animation, then you can do it this way + +### Loading the animations +To load the animation, run +```python +my_anim = fusion.Animation(your_window, your_images: tuple, fps: int) +``` + +### Drawing animation +To draw it then, run: +```python +my_anim.draw() +``` + +## Scene manager +See in [this example](https://github.com/dimkauzh/fusion-engine/blob/main/examples/example5.py) how to use the scene manager. + ## Sound ### Load sound @@ -291,7 +336,7 @@ The code shows how to save the modified data back to the storage file on disk. if you need keyboard input, then use this if statement with your own key (see key tab for all key names): ```python - if fusion.key_down(fusion.KEY_a): + if fusion.Key(fusion.KEY_a).key_down(): print("Key A pressed") ``` @@ -300,7 +345,7 @@ if you need keyboard input, then use this if statement with your own key (see ke If you need keydown to be only once, then you use this: ```python -if fusion.key_down_once(fusion.KEY_a): +if fusion.Key(fusion.KEY_a).key_down_once(): print("Key A pressed") ``` diff --git a/examples/example2.py b/examples/example2.py index e04eb4a1..3f52a1e6 100644 --- a/examples/example2.py +++ b/examples/example2.py @@ -8,5 +8,5 @@ def loop(): fusion.set_background_color(window, fusion.VIOLET) fusion.draw_rect(window, 100, 100, 400, 400, fusion.BLUE) - if fusion.key_down_once(fusion.KEY_a): + if fusion.Key(fusion.KEY_a).key_down(): print("Key A pressed") diff --git a/examples/example3.py b/examples/example3.py index 0d9d3e95..bb0b854a 100644 --- a/examples/example3.py +++ b/examples/example3.py @@ -14,16 +14,16 @@ def loop(): player.load_rect(fusion.AQUA) - if fusion.key_down(fusion.KEY_UP): + if fusion.Key(fusion.KEY_UP).key_down(): player.y = int(player.y - speed) - elif fusion.key_down(fusion.KEY_DOWN): + elif fusion.Key(fusion.KEY_DOWN).key_down(): player.y = int(player.y + speed) - elif fusion.key_down(fusion.KEY_RIGHT): + elif fusion.Key(fusion.KEY_RIGHT).key_down(): player.x = int(player.x + speed) - elif fusion.key_down(fusion.KEY_LEFT): + elif fusion.Key(fusion.KEY_LEFT).key_down(): player.x = int(player.x - speed) player.draw_rect() diff --git a/mkdocs.yml b/mkdocs.yml index 56b226b1..2d8391c4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,16 +24,62 @@ theme: - toc.follow - navigation.expand + - content.code.copy + + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default + primary: indigo + accent: indigo toggle: - icon: material/brightness-7 + icon: material/toggle-switch name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" scheme: slate + primary: black + accent: indigo toggle: - icon: material/brightness-4 - name: Switch to light mode + icon: material/toggle-switch-off + name: Switch to system preference +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.magiclink: + normalize_issue_symbols: true + repo_url_shorthand: true + user: squidfunk + repo: mkdocs-material + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde diff --git a/src/fusionengine/__init__.py b/src/fusionengine/__init__.py index 19e0f234..f4709942 100644 --- a/src/fusionengine/__init__.py +++ b/src/fusionengine/__init__.py @@ -1,5 +1,5 @@ __author__ = "Dimkauzh" -__version__ = "4.1.0" +__version__ = "4.2.0" import sys import os @@ -20,6 +20,9 @@ from fusionengine.colors.color import * from fusionengine.colors.colortools import * +# Entity +from fusionengine.entity.entity import * + # Physics from fusionengine.physics.body import * @@ -38,14 +41,19 @@ # Sound from fusionengine.sound.sound import * +from fusionengine.sound.background import * # Scene from fusionengine.scene.scene import * +from fusionengine.scene.manager import * # Tools from fusionengine.tools.systems import * from fusionengine.tools.debug import * +# Animation +from fusionengine.animation.animation import * + import pygame as pg import pygame_gui as gui diff --git a/src/fusionengine/animation/animation.py b/src/fusionengine/animation/animation.py new file mode 100644 index 00000000..d683efc5 --- /dev/null +++ b/src/fusionengine/animation/animation.py @@ -0,0 +1,27 @@ +from fusionengine.core.window import Window + +class Animation: + def __init__(self, window: Window, images: tuple, speed: int) -> None: + """ + + """ + self.frame = 0 + self.anim = images + + self.speed = speed + self.window = window + + def draw(self): + #default_fps = self.window.get_fps() + self.window.set_fps(self.speed) + + if self.frame >= len(self.anim): + self.frame = 0 + + image = self.anim[self.frame] + + image.draw() + + self.frame += 1 + #self.window.set_fps(default_fps) + diff --git a/src/fusionengine/core/image.py b/src/fusionengine/core/image.py index 4a63d374..8deb9801 100644 --- a/src/fusionengine/core/image.py +++ b/src/fusionengine/core/image.py @@ -1,11 +1,11 @@ -import fusionengine.core.window as window +from fusionengine.core.window import Window import pygame as pg class Image: def __init__( self, - window: window.Window, + window: Window, image_path, x: int, y: int, @@ -27,7 +27,7 @@ def draw(self) -> None: def draw_image_file( - window: window.Window, path: str, x: int, y: int, width: int, height: int + window: Window, path: str, x: int, y: int, width: int, height: int ): """Draw image directly from provided path.""" texture = pg.image.load(path) diff --git a/src/fusionengine/core/window.py b/src/fusionengine/core/window.py index 44d9c05f..6cc47af1 100644 --- a/src/fusionengine/core/window.py +++ b/src/fusionengine/core/window.py @@ -9,7 +9,7 @@ def __init__(self, title: str, width: int, height: int) -> None: self._running = False self._fps = 60 self._quittable = True - self.clock = pg.time.Clock() + self._clock = pg.time.Clock() self.title = title self.width = width @@ -69,6 +69,9 @@ def set_fps(self, fps: int) -> None: """ self._fps = fps + def get_fps(self) -> int: + return self._fps + def force_quit(self) -> None: """Force quits the window. Specifically, stops and deletes window. @@ -89,7 +92,7 @@ def _refresh(self) -> None: window: Your window """ - self.DELTATIME = self.clock.tick(self._fps) + self.DELTATIME = self._clock.tick(self._fps) for event in pg.event.get(): if event.type == pg.QUIT and self._quittable: diff --git a/src/fusionengine/entity/entity.py b/src/fusionengine/entity/entity.py new file mode 100644 index 00000000..915d3013 --- /dev/null +++ b/src/fusionengine/entity/entity.py @@ -0,0 +1,52 @@ +from fusionengine.core.window import Window +from fusionengine.core.image import Image +from fusionengine.core.shape import Rect + +class Entity: + def __init__( + self, + window: Window, + x: int, + y: int, + width: int, + height: int, + ) -> None: + """A class that creates a new entity.""" + self.x = x + self.y = y + self.width = width + self.height = height + self.window = window + self.gravity = 0 + self.frame = 0 + + def load_image( + self, + image_path: str, + ) -> None: + """Gives the entity an image and laters draws it on the screen.""" + self.main_image = Image( + self.window, image_path, self.x, self.y, self.width, self.height + ) + + def load_animation(self, images: tuple): + self.images = images + + def draw_animation(self): + self.images[self.frame].draw() + + def draw_image(self) -> None: + self.main_image.draw() + + def set_frame(self, frame: int): + self.frame = frame + + def get_frame(self, frame: int) -> int: + return frame + + def load_rect(self, color: tuple) -> None: + """Gives the entity a rectangle and later draws it on the screen.""" + self.main_rect = Rect(self.window, self.x, self.y, self.width, self.height, color) + + def draw_rect(self) -> None: + self.main_rect.draw() diff --git a/src/fusionengine/events/event.py b/src/fusionengine/events/event.py index e05ad331..22d3a5fd 100644 --- a/src/fusionengine/events/event.py +++ b/src/fusionengine/events/event.py @@ -1,14 +1,32 @@ import pygame as pg +from fusionengine.tools.deprecations import deprecated clicked = False +class Key: + def __init__(self, key) -> None: + self.key = key + def key_down(self): + keys = pg.key.get_pressed() + return keys[self.key] + + + def key_down_once(self): + if self.key_down() and not self.clicked: + self.clicked = True + return True + elif not self.key_down(): + self.clicked = False + return False + +@deprecated def key_down(key): keys = pg.key.get_pressed() return keys[key] - +@deprecated def key_down_once(key): global clicked if key_down(key) and not clicked: @@ -17,6 +35,3 @@ def key_down_once(key): elif not key_down(key): clicked = False return False - - - diff --git a/src/fusionengine/files/shape.py b/src/fusionengine/files/shape.py deleted file mode 100644 index 5ec8ed0b..00000000 --- a/src/fusionengine/files/shape.py +++ /dev/null @@ -1,22 +0,0 @@ -import pygame as pg - -import fusionengine.files.window as fe_window -import fusionengine.files.color as fe_color - - -class Rect: - def __init__( - self, window: fe_window.Window, x: int, y: int, width: int, height: int, color=fe_color.BLUE - ) -> None: - """A class that creates a new custom shape. (Not for the user)""" - self.x = x - self.y = y - self.width = width - self.height = height - self.color = color - self.rect = pg.Rect(x, y, width, height) - self.window = window - - def draw(self) -> None: - """Creates a new rectangle. Can be later rendered with draw_own_rect.""" - pg.draw.rect(self.window.window, self.color, self.rect) diff --git a/src/fusionengine/files/ui.py b/src/fusionengine/files/ui.py deleted file mode 100644 index 4c3827ad..00000000 --- a/src/fusionengine/files/ui.py +++ /dev/null @@ -1,47 +0,0 @@ -import fusionengine.files.window as fe_window -import fusionengine.files.shape as fe_shape - -import pygame as pg -import pygame_gui as gui -import os - - -class Button: - def __init__(self, rect: fe_shape.Rect, text: str, anchors=None, ) -> None: - """Creates a button.""" - self.manager = rect.window.manager - self.text = text - self.x = rect.x - self.y = rect.y - self.width = rect.width - self.height = rect.height - - self.button = gui.elements.UIButton( - relative_rect=rect.rect, text=text, manager=self.manager, anchors=anchors - ) - - def button_pressed(self) -> bool: - return self.button.check_pressed() - - -class Text: - def __init__( - self, - window: fe_window.Window, - text: str, - x: int, - y: int, - font_path: str, - font_size: int, - color: tuple, - ) -> None: - """Prints text on the screen.""" - - if os.path.exists(font_path): - font = pg.font.Font(font_path, font_size) - else: - font = pg.font.SysFont(font_path, font_size) - - txtsurf = font.render(text, True, color) - - window.window.blit(txtsurf, (x, y)) diff --git a/src/fusionengine/files/window.py b/src/fusionengine/files/window.py deleted file mode 100644 index 806c66db..00000000 --- a/src/fusionengine/files/window.py +++ /dev/null @@ -1,105 +0,0 @@ -import fusionengine.files.debug as fe_debug -import pygame as pg -import pygame_gui as gui -from pygame.locals import DOUBLEBUF - - -class Window: - def __init__(self, title="Fusion Engine", width=800, height=600) -> None: - self._running = False - self._fps = 60 - self._quittable = True - self.clock = pg.time.Clock() - - self.title = title - self.width = width - self.height = height - - try: - self.window = pg.display.set_mode((width, height), DOUBLEBUF, 16) - pg.display.set_caption(title) - - self.manager = gui.UIManager((width, height)) - - program_icon = pg.image.load(fe_debug.DEBUGIMAGE) - pg.display.set_icon(program_icon) - - self._running = True - - except Exception: - print("Error: Can't create a window.") - - def change_icon(self, image_path): - """Changes icon - - Args: - Icon_Path (str): Path to your icon - - """ - - programIcon = pg.image.load(image_path) - pg.display.set_icon(programIcon) - - def loop(self, your_loop) -> None: - """A while loop decorator function. - - Args: - your_loop (callable): Your main loop function - """ - while self.running(): - your_loop() - - def running(self) -> bool: - """Returns if the window is running. Used for the main loop. - - Args: - window: Your window - - Returns: - bool: returns true if the window is running else false - """ - self._refresh() - return self._running - - def set_fps(self, fps: int) -> None: - """Sets the desired frames per second for the game loop. - - Args: - fps (int): The desired frames per second - """ - self._fps = fps - - def force_quit(self) -> None: - """Force quits the window. - Specifically, stops and deletes window. - Args: - window: Your window - """ - self._running = False - del self.window - - def toggle_quittable(self) -> None: - """Toggles whether the window is quittable.""" - self._quittable = not self._quittable - - def _refresh(self) -> None: - """Does all things for refreshing window. (Not for the user) - - Args: - window: Your window - """ - - self.DELTATIME = self.clock.tick(self._fps) - - for event in pg.event.get(): - if event.type == pg.QUIT and self._quittable: - self._running = False - - self.manager.process_events(event) - - self.manager.update(self.DELTATIME) - self.manager.draw_ui(self.window) - - pg.display.update() - - self.window.fill((0, 0, 0)) diff --git a/src/fusionengine/physics/body.py b/src/fusionengine/physics/body.py index 988f2d78..e69de29b 100644 --- a/src/fusionengine/physics/body.py +++ b/src/fusionengine/physics/body.py @@ -1,40 +0,0 @@ -import fusionengine.core.window as window -import fusionengine.core.image as image -import fusionengine.core.shape as shape - - -class Entity: - def __init__( - self, - window: window.Window, - x: int, - y: int, - width: int, - height: int, - ) -> None: - """A class that creates a new entity.""" - self.x = x - self.y = y - self.width = width - self.height = height - self.window = window - self.gravity = 0 - - def load_image( - self, - image_path: str, - ) -> None: - """Gives the entity an image and laters draws it on the screen.""" - self.main_image = image.Image( - self.window, image_path, self.x, self.y, self.width, self.height - ) - - def draw_image(self) -> None: - self.main_image.draw() - - def load_rect(self, color: tuple) -> None: - """Gives the entity a rectangle and later draws it on the screen.""" - self.main_rect = shape.Rect(self.window, self.x, self.y, self.width, self.height, color) - - def draw_rect(self) -> None: - self.main_rect.draw() diff --git a/src/fusionengine/scene/manager.py b/src/fusionengine/scene/manager.py new file mode 100644 index 00000000..3b9e7f8a --- /dev/null +++ b/src/fusionengine/scene/manager.py @@ -0,0 +1,28 @@ +import fusionengine.core.window as fe_window +from fusionengine.scene.scene import Scene + +class SceneManager: + def init(self, window: fe_window.Window): + self.window = window + self.scenes = {} + + def add_scene(self, scene: Scene): + self.scenes[scene.name] = scene + + def remove_scene(self, name): + del self.scenes[name] + + def change_scene(self, name, scene: Scene): + self.scenes[name] = scene + + def get_scene(self, name): + return self.scenes[name] + + def start(self): + for scene in list(self.scenes.values()): + scene.run() + + def loop(self, function, window): + @window.loop + def wrapper(): + function() diff --git a/src/fusionengine/scene/scene.py b/src/fusionengine/scene/scene.py index 47a9c244..8f46238d 100644 --- a/src/fusionengine/scene/scene.py +++ b/src/fusionengine/scene/scene.py @@ -1,6 +1,3 @@ -import fusionengine.core.window as fe_window - - class Scene: def __init__(self, name, function): self.name = name @@ -12,29 +9,3 @@ def run(self): def change_function(self, function): self.function = function - -class SceneManager: - def init(self, window: fe_window.Window): - self.window = window - self.scenes = {} - - def add_scene(self, scene: Scene): - self.scenes[scene.name] = scene - - def remove_scene(self, name): - del self.scenes[name] - - def change_scene(self, name, scene: Scene): - self.scenes[name] = scene - - def get_scene(self, name): - return self.scenes[name] - - def start(self): - for scene in list(self.scenes.values()): - scene.run() - - def loop(self, function, window): - @window.loop - def wrapper(): - function() diff --git a/src/fusionengine/sound/background.py b/src/fusionengine/sound/background.py new file mode 100644 index 00000000..e5e1f0fa --- /dev/null +++ b/src/fusionengine/sound/background.py @@ -0,0 +1,9 @@ +import pygame as pg + +class BackgroundMusic: + def __init__(self, sound_path): + pg.mixer.music.load(sound_path) + pg.mixer.music.play(-1) + + def set_volume(self, volume): + pg.mixer.music.set_volume(volume) diff --git a/src/fusionengine/sound/sound.py b/src/fusionengine/sound/sound.py index 7e4c3f8d..aa6cf015 100644 --- a/src/fusionengine/sound/sound.py +++ b/src/fusionengine/sound/sound.py @@ -20,11 +20,3 @@ def set_volume(self, volume: int): def fadeout(self, time: int): self.sound.fadeout(time) - -class BackgroundMusic: - def __init__(self, sound_path): - pg.mixer.music.load(sound_path) - pg.mixer.music.play(-1) - - def set_volume(self, volume): - pg.mixer.music.set_volume(volume) diff --git a/src/fusionengine/tools/deprecations.py b/src/fusionengine/tools/deprecations.py new file mode 100644 index 00000000..e7eeb9fa --- /dev/null +++ b/src/fusionengine/tools/deprecations.py @@ -0,0 +1,16 @@ +import warnings +import functools + +def deprecated(func): + """This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used.""" + @functools.wraps(func) + def new_func(*args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) # turn off filter + warnings.warn("Call to deprecated function {}.".format(func.__name__), + category=DeprecationWarning, + stacklevel=2) + warnings.simplefilter('default', DeprecationWarning) # reset filter + return func(*args, **kwargs) + return new_func diff --git a/tests/anim.py b/tests/anim.py new file mode 100644 index 00000000..6000454d --- /dev/null +++ b/tests/anim.py @@ -0,0 +1,13 @@ +import fusionengine as fusion + +window = fusion.Window("Example: 1", 600, 600) +image1 = fusion.Image(window, fusion.DEBUGIMAGE, 0, 0, 600, 600) +image2 = fusion.Image(window, fusion.DEBUGIMAGE, 0, 0, 400, 400) + +anim = fusion.Animation(window, [image1, image2], 3) + + +@window.loop +def loop(): + anim.draw() +