mirror of
https://github.com/csd4ni3l/chaos-protocol.git
synced 2026-01-01 04:23:43 +01:00
Fix file manager sprite add submitting with / at the end, fix change_size not working for textured, make texturedrectangles actually work, fix importing breaking rule system and refresh sprites after import, add separate import and export file managers, reset submitted content of file managers after submit, fix TexturedRectangles staying at 0, 0, fix some performance issues and update README
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
Chaos Protocol is a simulation game where you have a rule engine and objects, which you can apply rules to! By default, the game launches with random rules.
|
||||
**Chaos Protocol** is a simulation game where you have a rule engine and objects, which you can apply rules to! By default, the game launches with random rules.
|
||||
Basically a framework of sorts, which can even be random!
|
||||
|
||||
The project is a huge WIP! You can't do much yet, but you have basic rules and simulation.
|
||||
The game has Scratch-like blocks and conditions, and everything you probably need for a simple game.
|
||||
The problem: it hasn't been well tested.
|
||||
|
||||
## Speed is a big problem of the project, especially collision detections, which are very expensive.
|
||||
I am not using spatial hashing right now, and i had no time to optimize the code. I'm sorry.
|
||||
|
||||
[](https://youtu.be/iMB4mmjTIB4)
|
||||
@@ -55,7 +55,7 @@ class FileManager(arcade.gui.UIAnchorLayout):
|
||||
self.submit_button.visible = self.mode == "export"
|
||||
|
||||
def submit(self, content):
|
||||
self.submitted_content = content if self.mode == "import" else f"{content}/{self.filename_input.text}"
|
||||
self.submitted_content = content if self.mode == "import" else os.path.join(content, self.filename_input.text)
|
||||
|
||||
def get_content(self, directory):
|
||||
if not directory in self.content_cache or time.perf_counter() - self.content_cache[directory][-1] >= 30:
|
||||
@@ -113,10 +113,10 @@ class FileManager(arcade.gui.UIAnchorLayout):
|
||||
|
||||
for file in self.get_content(self.current_directory):
|
||||
self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=file, style=button_style, width=self.filemanager_width / 1.5, height=self.filemanager_height * 0.05,)))
|
||||
if os.path.isdir(f"{self.current_directory}/{file}"):
|
||||
self.file_buttons[-1].on_click = lambda event, directory=f"{self.current_directory}/{file}": self.change_directory(directory)
|
||||
if os.path.isdir(os.path.join(self.current_directory, file)):
|
||||
self.file_buttons[-1].on_click = lambda event, directory=os.path.join(self.current_directory, file): self.change_directory(directory)
|
||||
else:
|
||||
self.file_buttons[-1].on_click = lambda event, file=f"{self.current_directory}/{file}": self.submit(file)
|
||||
self.file_buttons[-1].on_click = lambda event, file=os.path.join(self.current_directory, file): self.submit(file)
|
||||
|
||||
def change_directory(self, directory):
|
||||
if directory.startswith("//"): # Fix / paths
|
||||
|
||||
92
game/play.py
92
game/play.py
@@ -6,7 +6,7 @@ from utils.preload import SPRITE_TEXTURES, button_texture, button_hovered_textur
|
||||
from utils.constants import button_style, DO_RULES, IF_RULES, SPRITES, ALLOWED_INPUT
|
||||
|
||||
from game.rules import RuleUI, Block, VarBlock
|
||||
from game.sprites import BaseShape, Rectangle, Circle, Triangle, TexturedRectangle
|
||||
from game.sprites import BaseRectangle, BaseShape, Rectangle, Circle, Triangle, TexturedRectangle
|
||||
from game.file_manager import FileManager
|
||||
|
||||
class Game(arcade.gui.UIView):
|
||||
@@ -23,7 +23,11 @@ class Game(arcade.gui.UIView):
|
||||
|
||||
self.rules_box = RuleUI(self.window)
|
||||
|
||||
self.file_manager = FileManager(self.window.width * 0.95, self.window.height * 0.875, (0.95, 0.875), [".json"]).with_border()
|
||||
self.import_file_manager = FileManager(self.window.width * 0.95, self.window.height * 0.875, (0.95, 0.875), [".json"]).with_border()
|
||||
self.import_file_manager.change_mode("import")
|
||||
|
||||
self.export_file_manager = FileManager(self.window.width * 0.95, self.window.height * 0.875, (0.95, 0.875), [".json"]).with_border()
|
||||
self.export_file_manager.change_mode("export")
|
||||
|
||||
self.ui_selector_box = self.anchor.add(arcade.gui.UIBoxLayout(vertical=False, space_between=self.window.width / 100), anchor_x="left", anchor_y="bottom", align_y=5, align_x=self.window.width / 100)
|
||||
self.add_ui_selector("Simulation", lambda event: self.simulation())
|
||||
@@ -40,6 +44,7 @@ class Game(arcade.gui.UIView):
|
||||
self.rulesets = self.rules_box.rulesets
|
||||
|
||||
self.sprite_add_filemanager = FileManager(self.window.width * 0.9, self.window.height * 0.75, (0.9, 0.75), [".png", ".jpg", ".jpeg", ".bmp", ".gif"])
|
||||
self.sprite_add_filemanager.change_mode("import")
|
||||
self.sprite_add_ui = arcade.gui.UIBoxLayout(size_hint=(0.95, 0.9), space_between=10)
|
||||
self.sprite_add_ui.add(arcade.gui.UILabel(text="Add Sprite", font_size=24, text_color=arcade.color.WHITE))
|
||||
|
||||
@@ -137,7 +142,7 @@ class Game(arcade.gui.UIView):
|
||||
a = float(a)
|
||||
if isinstance(shape, Circle):
|
||||
shape.radius = a
|
||||
elif isinstance(shape, Rectangle):
|
||||
elif isinstance(shape, BaseRectangle):
|
||||
shape.width = a
|
||||
shape.height = a
|
||||
elif isinstance(shape, Triangle):
|
||||
@@ -167,10 +172,9 @@ class Game(arcade.gui.UIView):
|
||||
self.shapes.append(Triangle(x, y, x + 10, y, x + 5, y + 10, color=arcade.color.WHITE, batch=self.shape_batch))
|
||||
|
||||
else:
|
||||
self.shapes.append(TexturedRectangle(shape_type, img=SPRITE_TEXTURES.get(shape_type, SPRITE_TEXTURES["rectangle"]), x=x, y=y, batch=self.shape_batch))
|
||||
self.shapes.append(TexturedRectangle(pyglet.image.load(self.sprite_types[shape_type]), x, y, batch=self.shape_batch, shape_type=shape_type))
|
||||
|
||||
shape = self.shapes[-1]
|
||||
|
||||
self.triggered_events.append(["spawn", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}])
|
||||
|
||||
def add_sprite(self):
|
||||
@@ -284,11 +288,19 @@ class Game(arcade.gui.UIView):
|
||||
|
||||
return max_num
|
||||
|
||||
def dict_to_block(self, block_dict):
|
||||
kwargs = block_dict.copy()
|
||||
kwargs["children"] = [self.dict_to_block(child) for child in block_dict.get("children", [])]
|
||||
kwargs["vars"] = [VarBlock(**var) for var in block_dict.get("vars", [])]
|
||||
return Block(**kwargs)
|
||||
|
||||
def on_update(self, delta_time):
|
||||
if self.mode == "import" and self.file_manager.submitted_content:
|
||||
with open(self.file_manager.submitted_content, "r") as file:
|
||||
if self.mode == "import" and self.import_file_manager.submitted_content:
|
||||
with open(self.import_file_manager.submitted_content, "r") as file:
|
||||
data = json.load(file)
|
||||
|
||||
self.import_file_manager.submitted_content = None
|
||||
|
||||
self.triggered_events = []
|
||||
self.rulesets = {}
|
||||
|
||||
@@ -296,26 +308,34 @@ class Game(arcade.gui.UIView):
|
||||
self.add_widget(arcade.gui.UIMessageBox(message_text="Invalid file. Could not import rules.", width=self.window.width * 0.5, height=self.window.height * 0.25))
|
||||
return
|
||||
|
||||
for rule_num, ruleset in data["rulesets"].items():
|
||||
kwargs = ruleset
|
||||
kwargs["children"] = [Block(**child) for child in ruleset["children"]]
|
||||
kwargs["vars"] = [VarBlock(**var) for var in ruleset["vars"]]
|
||||
block = Block(**kwargs)
|
||||
self.rulesets[rule_num] = block
|
||||
for rule_num, ruleset in data["rules"].items():
|
||||
block = self.dict_to_block(ruleset)
|
||||
self.rulesets[int(rule_num)] = block
|
||||
|
||||
self.sprite_types = data.get("sprites", SPRITES)
|
||||
self.sprite_types = data["sprites"]
|
||||
for sprite_name, sprite_path in self.sprite_types.items():
|
||||
if not sprite_name in SPRITE_TEXTURES:
|
||||
SPRITE_TEXTURES[sprite_name] = arcade.load_texture(sprite_path)
|
||||
|
||||
SPRITES[sprite_name] = sprite_path
|
||||
|
||||
self.sprites_grid.clear()
|
||||
|
||||
for n, shape in enumerate(SPRITES):
|
||||
row, col = n % 8, n // 8
|
||||
box = self.sprites_grid.add(arcade.gui.UIBoxLayout(), row=row, column=col)
|
||||
box.add(arcade.gui.UILabel(text=shape, font_size=16, text_color=arcade.color.WHITE))
|
||||
box.add(arcade.gui.UIImage(texture=SPRITE_TEXTURES[shape], width=self.window.width / 15, height=self.window.width / 15))
|
||||
|
||||
self.rules_box.rulesets = self.rulesets
|
||||
self.rules_box.block_renderer.blocks = self.rulesets
|
||||
self.rules_box.current_rule_num = self.get_max_rule_num() + 1
|
||||
self.rules_box.block_renderer.refresh()
|
||||
|
||||
self.rules()
|
||||
|
||||
if self.mode == "export" and self.file_manager.submitted_content:
|
||||
with open(self.file_manager.submitted_content, "w") as file:
|
||||
if self.mode == "export" and self.export_file_manager.submitted_content:
|
||||
with open(self.export_file_manager.submitted_content, "w") as file:
|
||||
file.write(json.dumps(
|
||||
{
|
||||
"rules": {
|
||||
@@ -326,6 +346,8 @@ class Game(arcade.gui.UIView):
|
||||
},
|
||||
indent=4))
|
||||
|
||||
self.export_file_manager.submitted_content = None
|
||||
|
||||
self.add_widget(arcade.gui.UIMessageBox(message_text="Rules and Sprites exported successfully!", width=self.window.width * 0.5, height=self.window.height * 0.25))
|
||||
|
||||
if not self.mode == "simulation":
|
||||
@@ -342,13 +364,29 @@ class Game(arcade.gui.UIView):
|
||||
|
||||
self.recursive_execute_rule(rule, trigger_args)
|
||||
|
||||
for shape in self.shapes:
|
||||
for shape_b in self.shapes:
|
||||
if shape.check_collision(shape_b):
|
||||
self.triggered_events.append(["collision", {"event_a_type": shape.shape_type, "event_b_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.color}])
|
||||
has_collision_rules = any(
|
||||
rule.rule_type == "trigger" and rule.rule == "collision"
|
||||
for rule in self.rulesets.values()
|
||||
)
|
||||
|
||||
for shape in self.shapes:
|
||||
shape.update(self.x_gravity, self.y_gravity)
|
||||
|
||||
if has_collision_rules:
|
||||
for i, shape in enumerate(self.shapes):
|
||||
for shape_b in self.shapes[i+1:]:
|
||||
if shape.check_collision(shape_b):
|
||||
self.triggered_events.append(["collision", {
|
||||
"event_a_type": shape.shape_type,
|
||||
"event_b_type": shape_b.shape_type,
|
||||
"shape_size": shape.shape_size,
|
||||
"shape_x": shape.x,
|
||||
"shape_y": shape.y,
|
||||
"shape": shape,
|
||||
"shape_color": shape.shape_color
|
||||
}])
|
||||
|
||||
for shape in self.shapes[:]:
|
||||
if shape.x < 0 or shape.x > self.window.width or shape.y < 0 or shape.y > self.window.height:
|
||||
self.destroy(shape)
|
||||
|
||||
@@ -386,8 +424,10 @@ class Game(arcade.gui.UIView):
|
||||
self.rules_box.camera.zoom *= 1 + scroll_y * 0.1
|
||||
|
||||
def disable_previous(self):
|
||||
if self.mode in ["import", "export"]:
|
||||
self.anchor.remove(self.file_manager)
|
||||
if self.mode == "import":
|
||||
self.anchor.remove(self.import_file_manager)
|
||||
elif self.mode == "export":
|
||||
self.anchor.remove(self.export_file_manager)
|
||||
elif self.mode == "rules":
|
||||
self.anchor.remove(self.rules_box)
|
||||
elif self.mode == "sprites":
|
||||
@@ -408,17 +448,13 @@ class Game(arcade.gui.UIView):
|
||||
self.disable_previous()
|
||||
|
||||
self.mode = "export"
|
||||
|
||||
self.file_manager.change_mode("export")
|
||||
self.anchor.add(self.file_manager, anchor_x="center", anchor_y="top", align_y=-self.window.height * 0.025)
|
||||
self.anchor.add(self.export_file_manager, anchor_x="center", anchor_y="top", align_y=-self.window.height * 0.025)
|
||||
|
||||
def import_file(self):
|
||||
self.disable_previous()
|
||||
|
||||
self.mode = "import"
|
||||
|
||||
self.file_manager.change_mode("import")
|
||||
self.anchor.add(self.file_manager, anchor_x="center", anchor_y="top", align_y=-self.window.height * 0.025)
|
||||
self.anchor.add(self.import_file_manager, anchor_x="center", anchor_y="top", align_y=-self.window.height * 0.025)
|
||||
|
||||
def sprites(self):
|
||||
self.disable_previous()
|
||||
|
||||
@@ -31,7 +31,7 @@ class BaseShape():
|
||||
def check_collision(self, other):
|
||||
if isinstance(other, Circle):
|
||||
return self._collides_with_circle(other)
|
||||
elif isinstance(other, Rectangle):
|
||||
elif isinstance(other, BaseRectangle):
|
||||
return self._collides_with_rectangle(other)
|
||||
elif isinstance(other, Triangle):
|
||||
return self._collides_with_triangle(other)
|
||||
@@ -106,7 +106,6 @@ class Circle(pyglet.shapes.Circle, BaseShape):
|
||||
|
||||
return not (has_neg and has_pos)
|
||||
|
||||
|
||||
class BaseRectangle(BaseShape):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -182,15 +181,18 @@ class BaseRectangle(BaseShape):
|
||||
|
||||
class Rectangle(pyglet.shapes.Rectangle, BaseRectangle):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
BaseRectangle.__init__(self)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.shape_type = "rectangle"
|
||||
|
||||
class TexturedRectangle(pyglet.sprite.Sprite, BaseRectangle):
|
||||
def __init__(self, shape_type, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, img, x=0, y=0, *args, **kwargs):
|
||||
BaseRectangle.__init__(self)
|
||||
self.shape_type = shape_type
|
||||
self.shape_type = kwargs.pop("shape_type", "textured_rectangle")
|
||||
super().__init__(img, x, y, *args, **kwargs)
|
||||
|
||||
def update(self, x_gravity, y_gravity):
|
||||
BaseShape.update(self, x_gravity, y_gravity)
|
||||
|
||||
class Triangle(pyglet.shapes.Triangle, BaseShape):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user