mirror of
https://github.com/csd4ni3l/simulator-games.git
synced 2026-01-01 12:23:44 +01:00
Added water splash simulator, made settings saving work for all simulators, moved all the simulators into their respective directories, improved the README, fixed too much logging
This commit is contained in:
449
game/physics_playground/game.py
Normal file
449
game/physics_playground/game.py
Normal file
@@ -0,0 +1,449 @@
|
||||
import arcade, arcade.gui, pymunk, pymunk.util, math, time, os, io, cairosvg, json, random
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar
|
||||
|
||||
from pymunk.autogeometry import convex_decomposition
|
||||
|
||||
from svgpathtools import svg2paths
|
||||
|
||||
from game.physics_playground.body_inventory import BodyInventory
|
||||
|
||||
from utils.constants import menu_background_color, button_style
|
||||
from utils.preload import button_texture, button_hovered_texture
|
||||
|
||||
class FakeShape():
|
||||
def __init__(self, body):
|
||||
self.body = body
|
||||
|
||||
class CustomPhysics(arcade.Sprite):
|
||||
def __init__(self, pymunk_obj, filename):
|
||||
super().__init__(filename, center_x=pymunk_obj.body.position.x, center_y=pymunk_obj.body.position.y)
|
||||
self.pymunk_obj = pymunk_obj
|
||||
|
||||
class SpritePhysics(arcade.Sprite):
|
||||
def __init__(self, pymunk_obj, filename):
|
||||
super().__init__(filename, center_x=pymunk_obj.body.position.x, center_y=pymunk_obj.body.position.y)
|
||||
self.pymunk_obj = pymunk_obj
|
||||
class PhysicsCoin(SpritePhysics):
|
||||
def __init__(self, pymunk_obj, filename):
|
||||
super().__init__(pymunk_obj, filename)
|
||||
self.width = pymunk_obj.radius * 2
|
||||
self.height = pymunk_obj.radius * 2
|
||||
|
||||
class PhysicsCrate(SpritePhysics):
|
||||
def __init__(self, pymunk_obj, filename, width, height):
|
||||
super().__init__(pymunk_obj, filename)
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
class Game(arcade.gui.UIView):
|
||||
def __init__(self, pypresence_client):
|
||||
super().__init__()
|
||||
|
||||
self.pypresence_client = pypresence_client
|
||||
self.pypresence_client.update(state='Playing a simulator', details='Physics Playground', start=self.pypresence_client.start_time)
|
||||
|
||||
arcade.set_background_color(arcade.color.WHITE)
|
||||
|
||||
if os.path.exists("data.json"):
|
||||
with open("data.json", "r") as file:
|
||||
self.settings = json.load(file)
|
||||
else:
|
||||
self.settings = {}
|
||||
|
||||
if not "physics_playground" in self.settings:
|
||||
self.settings["physics_playground"] = {
|
||||
"iterations": 50,
|
||||
"gravity_x": 0,
|
||||
"gravity_y": -930,
|
||||
|
||||
"crate_elasticity": 0.5,
|
||||
"crate_friction": 0.9,
|
||||
"crate_mass": 1,
|
||||
|
||||
"coin_elasticity": 0.5,
|
||||
"coin_friction": 0.9,
|
||||
"coin_mass": 1,
|
||||
|
||||
"custom_elasticity": 0.5,
|
||||
"custom_friction": 0.9,
|
||||
"custom_mass": 1
|
||||
}
|
||||
|
||||
self.space = pymunk.Space()
|
||||
|
||||
self.spritelist: arcade.SpriteList[SpritePhysics] = arcade.SpriteList()
|
||||
self.walls = []
|
||||
self.custom_bodies = []
|
||||
|
||||
self.custom_pymunk_objs = {}
|
||||
|
||||
self.dragged_shape = None
|
||||
self.last_mouse_position = 0, 0
|
||||
self.last_processing_time_update = time.perf_counter()
|
||||
|
||||
self.iterations = self.settings["physics_playground"].get("iterations", 35)
|
||||
self.space.iterations = self.iterations
|
||||
|
||||
self.gravity_x = self.settings["physics_playground"].get("gravity_x", 0)
|
||||
self.gravity_y = self.settings["physics_playground"].get("gravity_y", -930)
|
||||
self.space.gravity = (self.gravity_x, self.gravity_y)
|
||||
|
||||
self.crate_elasticity = self.settings["physics_playground"].get("crate_elasticity", 0.5)
|
||||
self.crate_friction = self.settings["physics_playground"].get("crate_friction", 0.9)
|
||||
self.crate_mass = self.settings["physics_playground"].get("crate_mass", 1)
|
||||
|
||||
self.coin_elasticity = self.settings["physics_playground"].get("coin_elasticity", 0.5)
|
||||
self.coin_friction = self.settings["physics_playground"].get("coin_friction", 0.9)
|
||||
self.coin_mass = self.settings["physics_playground"].get("coin_mass", 1)
|
||||
|
||||
self.custom_elasticity = self.settings["physics_playground"].get("custom_elasticity", 0.5)
|
||||
self.custom_friction = self.settings["physics_playground"].get("custom_friction", 0.9)
|
||||
self.custom_mass = self.settings["physics_playground"].get("custom_mass", 1)
|
||||
|
||||
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
|
||||
|
||||
self.info_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=3, align="left"), anchor_x="left", anchor_y="top")
|
||||
self.fps_label = self.info_box.add(arcade.gui.UILabel(text="FPS: 60", text_color=arcade.color.BLACK))
|
||||
self.object_count_label = self.info_box.add(arcade.gui.UILabel(text="Object count: 0", text_color=arcade.color.BLACK))
|
||||
self.processing_time_label = self.info_box.add(arcade.gui.UILabel(text="Processing time: 0 ms", text_color=arcade.color.BLACK))
|
||||
|
||||
self.settings_box = self.anchor.add(arcade.gui.UIBoxLayout(align="center", size_hint=(0.2, 1)).with_background(color=arcade.color.GRAY), anchor_x="right", anchor_y="bottom")
|
||||
self.settings_title_label = self.settings_box.add(arcade.gui.UILabel(text="Settings", font_size=24))
|
||||
|
||||
self.settings_box.add(arcade.gui.UISpace(size_hint=(0, 0.025)))
|
||||
|
||||
self.add_setting("Crate Elasticity: {value}", 0, 3, 0.1, "crate_elasticity", "elasticity", PhysicsCrate)
|
||||
self.add_setting("Coin Elasticity: {value}", 0, 3, 0.1, "coin_elasticity", "elasticity", PhysicsCoin)
|
||||
self.add_setting("Custom Elasticity: {value}", 0, 3, 0.1, "custom_elasticity", "elasticity", CustomPhysics)
|
||||
|
||||
self.add_setting("Crate Friction: {value}", 0, 10, 0.1, "crate_friction", "friction", PhysicsCrate)
|
||||
self.add_setting("Coin Friction: {value}", 0, 10, 0.1, "coin_friction", "friction", PhysicsCoin)
|
||||
self.add_setting("Custom Friction: {value}", 0, 10, 0.1, "custom_friction", "friction", CustomPhysics)
|
||||
|
||||
self.add_setting("Crate Mass: {value}kg", 1, 100, 1, "crate_mass", "mass", PhysicsCrate)
|
||||
self.add_setting("Coin Mass: {value}kg", 1, 100, 1, "coin_mass", "mass", PhysicsCoin)
|
||||
self.add_setting("Custom Mass: {value}kg", 1, 100, 1, "custom_mass", "mass", CustomPhysics)
|
||||
|
||||
self.add_setting("Gravity X: {value}", -900, 900, 100, "gravity_x", on_change=lambda label, value: self.change_gravity(label, value, "x"))
|
||||
self.add_setting("Gravity Y: {value}", -1800, 1800, 100, "gravity_y", on_change=lambda label, value: self.change_gravity(label, value, "y"))
|
||||
self.add_setting("Pymunk Iterations: {value}", 1, 200, 1, "iterations", on_change=lambda label, value: self.change_iterations(label, value))
|
||||
|
||||
self.settings_box.add(arcade.gui.UILabel("Inventory", font_size=18))
|
||||
|
||||
self.inventory_grid = self.settings_box.add(BodyInventory(self.window.width, self.window.height, "crate", {"crate": ":resources:images/tiles/boxCrate_double.png", "coin": ":resources:images/items/coinGold.png"}))
|
||||
|
||||
self.add_custom_body_button = self.settings_box.add(arcade.gui.UITextureButton(text="Add custom body from SVG", size_hint=(1, 0.1), width=self.window.width * 0.2, height=self.window.height * 0.1))
|
||||
self.add_custom_body_button.on_click = lambda event: self.custom_body_ui()
|
||||
|
||||
def save_data(self):
|
||||
with open("data.json", "w") as file:
|
||||
file.write(json.dumps(self.settings, indent=4))
|
||||
|
||||
def change_iterations(self, label, value):
|
||||
self.iterations = int(value)
|
||||
self.space.iterations = self.iterations
|
||||
label.text = f"Pymunk Iterations: {self.iterations}"
|
||||
|
||||
def change_gravity(self, label, value, gravity_type):
|
||||
if gravity_type == "x":
|
||||
self.gravity_x = value
|
||||
else:
|
||||
self.gravity_y = value
|
||||
|
||||
self.space.gravity = pymunk.Vec2d(self.gravity_x, self.gravity_y)
|
||||
label.text = f"Gravity {gravity_type.capitalize()}: {value}"
|
||||
|
||||
def add_setting(self, text, min_value, max_value, step, local_variable, pymunk_variable=None, instance=None, on_change=None):
|
||||
label = self.settings_box.add(arcade.gui.UILabel(text.format(value=getattr(self, local_variable))))
|
||||
slider = self.settings_box.add(arcade.gui.UISlider(value=getattr(self, local_variable), min_value=min_value, max_value=max_value, step=step, size_hint=(1, 0.05)))
|
||||
slider._render_steps = lambda surface: None
|
||||
|
||||
if pymunk_variable:
|
||||
slider.on_change = lambda event, label=label: self.change_value(label, text, local_variable, event.new_value, pymunk_variable, instance)
|
||||
elif on_change:
|
||||
slider.on_change = lambda event, label=label: on_change(label, event.new_value)
|
||||
else:
|
||||
slider.on_change = lambda event, label=label: self.change_value(label, text, local_variable, event.new_value)
|
||||
|
||||
def change_value(self, label, text, local_variable, value, pymunk_variable=None, instance=None):
|
||||
label.text = text.format(value=value)
|
||||
|
||||
setattr(self, local_variable, value)
|
||||
|
||||
self.settings["physics_playground"][local_variable] = value
|
||||
|
||||
if pymunk_variable:
|
||||
for sprite in self.spritelist:
|
||||
if isinstance(sprite, instance):
|
||||
setattr(sprite.pymunk_obj, pymunk_variable, value)
|
||||
|
||||
def create_wall(self, width, height, x, y):
|
||||
body = pymunk.Body(body_type=pymunk.Body.STATIC)
|
||||
body.position = pymunk.Vec2d(x, y)
|
||||
pymunk_obj = pymunk.Segment(body, [0, height], [width, height], 0.0)
|
||||
pymunk_obj.friction = 10
|
||||
self.space.add(pymunk_obj, body)
|
||||
self.walls.append(pymunk_obj)
|
||||
|
||||
def create_crate(self, x, y, size, mass):
|
||||
pymunk_moment = pymunk.moment_for_box(1.0, (size, size))
|
||||
|
||||
pymunk_body = pymunk.Body(mass, pymunk_moment)
|
||||
pymunk_body.position = pymunk.Vec2d(x, y)
|
||||
|
||||
pymunk_shape = pymunk.Poly.create_box(pymunk_body, (size, size))
|
||||
pymunk_shape.elasticity = self.crate_elasticity
|
||||
pymunk_shape.friction = self.crate_friction
|
||||
|
||||
self.space.add(pymunk_body, pymunk_shape)
|
||||
|
||||
sprite = PhysicsCrate(pymunk_shape, ":resources:images/tiles/boxCrate_double.png", width=size, height=size)
|
||||
self.spritelist.append(sprite)
|
||||
|
||||
def create_coin(self, x, y, radius, mass):
|
||||
inertia = pymunk.moment_for_circle(1.0, 0, radius, (0, 0))
|
||||
|
||||
body = pymunk.Body(mass, inertia)
|
||||
body.position = x, y
|
||||
body.velocity = 0, 0
|
||||
|
||||
shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
|
||||
shape.friction = self.coin_friction
|
||||
shape.elasticity = self.coin_elasticity
|
||||
|
||||
self.space.add(body, shape)
|
||||
|
||||
sprite = PhysicsCoin(shape, ":resources:images/items/coinGold.png")
|
||||
self.spritelist.append(sprite)
|
||||
|
||||
def on_draw(self):
|
||||
super().on_draw()
|
||||
|
||||
self.spritelist.draw()
|
||||
|
||||
for wall in self.walls:
|
||||
body = wall.body
|
||||
pv1 = body.position + wall.a.rotated(body.angle)
|
||||
pv2 = body.position + wall.b.rotated(body.angle)
|
||||
|
||||
arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.BLACK, 2)
|
||||
|
||||
for body in self.custom_bodies:
|
||||
for shape in body.shapes:
|
||||
if isinstance(shape, pymunk.Poly):
|
||||
verts = [v.rotated(body.angle) + body.position for v in shape.get_vertices()]
|
||||
arcade.draw_polygon_filled(verts, arcade.color.BLACK)
|
||||
|
||||
def on_mouse_press(self, x, y, button, modifiers):
|
||||
if button == arcade.MOUSE_BUTTON_LEFT:
|
||||
self.last_mouse_position = x, y
|
||||
|
||||
shape_list = self.space.point_query((x, y), 1, pymunk.ShapeFilter())
|
||||
|
||||
if len(shape_list) > 0:
|
||||
self.dragged_shape = shape_list[0]
|
||||
|
||||
def on_mouse_release(self, x, y, button, modifiers):
|
||||
self.dragged_shape = None
|
||||
|
||||
def on_mouse_motion(self, x, y, dx, dy):
|
||||
if self.dragged_shape is not None:
|
||||
self.last_mouse_position = x, y
|
||||
|
||||
self.dragged_shape.shape.body.position = self.last_mouse_position
|
||||
self.dragged_shape.shape.body.velocity = dx * 50, dy * 50
|
||||
|
||||
def clear_custom_body_ui(self):
|
||||
if hasattr(self, "custom_body_ui_box"):
|
||||
self.anchor.remove(self.custom_body_ui_box)
|
||||
self.custom_body_ui_box.clear()
|
||||
del self.custom_body_ui_box
|
||||
|
||||
if hasattr(self, "file_manager_ui_box"):
|
||||
self.anchor.remove(self.file_manager_ui_box)
|
||||
self.file_manager_ui_box.clear()
|
||||
del self.file_manager_ui_box
|
||||
|
||||
if hasattr(self, "scrollbar"):
|
||||
self.anchor.remove(self.scrollbar)
|
||||
del self.scrollbar
|
||||
|
||||
def sample_path(self, path, segments=50):
|
||||
pts = []
|
||||
|
||||
for i in range(segments + 1):
|
||||
point = path.point(i / segments)
|
||||
pts.append((point.real, point.imag))
|
||||
|
||||
return pts
|
||||
|
||||
def add_custom_body(self, file_path):
|
||||
paths, _ = svg2paths(file_path)
|
||||
|
||||
pts = self.sample_path(paths[0], 64)
|
||||
|
||||
png_bytes = cairosvg.svg2png(url=file_path, scale=1.0)
|
||||
original_image = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
||||
original_width, _ = original_image.size
|
||||
|
||||
desired_width = 32
|
||||
scale_factor = desired_width / original_width
|
||||
|
||||
pts = [(x * scale_factor, y * scale_factor) for x, y in pts]
|
||||
|
||||
try:
|
||||
convex_parts = convex_decomposition(pts, 0.1)
|
||||
except AssertionError:
|
||||
convex_parts = [pymunk.util.convex_hull(pts)]
|
||||
|
||||
total_moment = sum(pymunk.moment_for_poly(1.0, part) for part in convex_parts)
|
||||
|
||||
png_bytes = cairosvg.svg2png(url=file_path, scale=scale_factor)
|
||||
image = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
||||
texture = arcade.Texture(image)
|
||||
|
||||
self.custom_pymunk_objs[file_path] = (convex_parts, total_moment, texture)
|
||||
self.inventory_grid.add_item(file_path, texture)
|
||||
|
||||
self.clear_custom_body_ui()
|
||||
|
||||
def create_custom_body(self, file_path, x, y, mass):
|
||||
convex_parts, moment, image = self.custom_pymunk_objs[file_path]
|
||||
|
||||
body = pymunk.Body(mass, moment)
|
||||
body.position = pymunk.Vec2d(x, y)
|
||||
|
||||
self.space.add(body)
|
||||
|
||||
for part in convex_parts:
|
||||
shape = pymunk.Poly(body, part)
|
||||
self.space.add(shape)
|
||||
|
||||
sprite = CustomPhysics(FakeShape(body), image)
|
||||
|
||||
self.spritelist.append(sprite)
|
||||
|
||||
def get_directory_content(self, directory):
|
||||
try:
|
||||
entries = os.listdir(directory)
|
||||
except PermissionError:
|
||||
return None
|
||||
|
||||
filtered = [
|
||||
entry for entry in entries
|
||||
if (os.path.isdir(os.path.join(directory, entry)) and not "." in entry) or
|
||||
os.path.splitext(entry)[1].lower() == ".svg"
|
||||
]
|
||||
|
||||
sorted_entries = sorted(
|
||||
filtered,
|
||||
key=lambda x: (0 if os.path.isdir(os.path.join(directory, x)) else 1, x.lower())
|
||||
)
|
||||
|
||||
return sorted_entries
|
||||
|
||||
def file_manager(self, current_directory=None):
|
||||
self.clear_custom_body_ui()
|
||||
|
||||
if not current_directory:
|
||||
current_directory = os.getcwd()
|
||||
|
||||
self.scroll_area = UIScrollArea(size_hint=(0.5, 0.5)) # center on screen
|
||||
self.scroll_area.scroll_speed = -50
|
||||
self.anchor.add(self.scroll_area, anchor_x="center", anchor_y="center")
|
||||
|
||||
self.scrollbar = UIScrollBar(self.scroll_area)
|
||||
self.scrollbar.size_hint = (0.02, 0.5)
|
||||
self.anchor.add(self.scrollbar, anchor_x="center", anchor_y="center", align_x=self.window.width / 4)
|
||||
|
||||
self.file_manager_ui_box = self.scroll_area.add(arcade.gui.UIBoxLayout(space_between=10).with_background(color=arcade.color.GRAY))
|
||||
self.file_manager_ui_box.add(arcade.gui.UILabel(f"File Manager ({current_directory})", font_size=16))
|
||||
|
||||
go_up_button = self.file_manager_ui_box.add(arcade.gui.UITextureButton(text="Go up", texture=button_texture, texture_hovered=button_hovered_texture, style=button_style, width=self.window.width / 2, height=self.window.height / 10))
|
||||
go_up_button.on_click = lambda event, current_directory=current_directory: self.file_manager(os.path.dirname(current_directory))
|
||||
|
||||
for file in self.get_directory_content(current_directory):
|
||||
if os.path.isfile(f"{current_directory}/{file}"):
|
||||
file_button = self.file_manager_ui_box.add(arcade.gui.UITextureButton(text=file, texture=button_texture, texture_hovered=button_hovered_texture, style=button_style, width=self.window.width / 2, height=self.window.height / 10))
|
||||
file_button.on_click = lambda event, file=file: self.custom_body_ui(f"{current_directory}/{file}")
|
||||
else:
|
||||
file_button = self.file_manager_ui_box.add(arcade.gui.UITextureButton(text=file, texture=button_texture, texture_hovered=button_hovered_texture, style=button_style, width=self.window.width / 2, height=self.window.height / 10))
|
||||
file_button.on_click = lambda event, file=file: self.file_manager(f"{current_directory}/{file}")
|
||||
|
||||
def custom_body_ui(self, file_selected=None):
|
||||
self.clear_custom_body_ui()
|
||||
|
||||
self.custom_body_ui_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=10, size_hint=(0.5, 0.5)).with_background(color=arcade.color.GRAY), anchor_x="center")
|
||||
self.custom_body_ui_box.add(arcade.gui.UILabel("Add Custom Body from SVG File", font_size=24))
|
||||
|
||||
select_file_button = self.custom_body_ui_box.add(arcade.gui.UITextureButton(text=f"Select File ({file_selected})", texture=button_texture, texture_hovered=button_hovered_texture, style=button_style, width=self.window.width / 2, height=self.window.height / 10))
|
||||
select_file_button.on_click = lambda event: self.file_manager()
|
||||
|
||||
add_button = self.custom_body_ui_box.add(arcade.gui.UITextureButton(text="Add Body", texture=button_texture, texture_hovered=button_hovered_texture, style=button_style, width=self.window.width / 2, height=self.window.height / 10))
|
||||
|
||||
if file_selected:
|
||||
add_button.on_click = lambda event, file_selected=file_selected: self.add_custom_body(file_selected)
|
||||
|
||||
def on_update(self, delta_time):
|
||||
if self.window.keyboard[arcade.key.W]:
|
||||
if self.inventory_grid.selected_item == "crate":
|
||||
self.create_crate(self.window.mouse.data['x'], self.window.mouse.data['y'], 32, self.crate_mass)
|
||||
elif self.inventory_grid.selected_item == "coin":
|
||||
self.create_coin(self.window.mouse.data['x'], self.window.mouse.data['y'], 10, self.coin_mass)
|
||||
else:
|
||||
self.create_custom_body(self.inventory_grid.selected_item, self.window.mouse.data['x'], self.window.mouse.data['y'], self.custom_mass)
|
||||
|
||||
for sprite in self.spritelist:
|
||||
body = sprite.pymunk_obj.body
|
||||
x, y = body.position
|
||||
|
||||
if x < 0 or x > self.window.width * 0.775 or y < 0:
|
||||
body.position = (random.uniform(self.window.width * 0.1, self.window.width * 0.9), self.window.height * 0.9)
|
||||
body.velocity = (0, 0)
|
||||
|
||||
start = time.perf_counter()
|
||||
self.space.step(self.window._draw_rate)
|
||||
|
||||
if self.dragged_shape is not None:
|
||||
self.dragged_shape.shape.body.position = self.last_mouse_position
|
||||
self.dragged_shape.shape.body.velocity = 0, 0
|
||||
|
||||
for sprite in self.spritelist:
|
||||
sprite.position = sprite.pymunk_obj.body.position
|
||||
sprite.angle = -math.degrees(sprite.pymunk_obj.body.angle)
|
||||
|
||||
self.object_count_label.text = f"Object count: {len(self.spritelist)}"
|
||||
|
||||
current_time = time.perf_counter()
|
||||
if current_time - self.last_processing_time_update > 0.2:
|
||||
self.last_processing_time_update = current_time
|
||||
self.processing_time_label.text = f"Processing time: {round((current_time - start) * 1000, 2)} ms"
|
||||
|
||||
def on_key_press(self, symbol, modifiers):
|
||||
if symbol == arcade.key.ESCAPE:
|
||||
arcade.set_background_color(menu_background_color)
|
||||
|
||||
self.save_data()
|
||||
|
||||
from menus.main import Main
|
||||
self.window.show_view(Main(self.pypresence_client))
|
||||
elif symbol == arcade.key.D:
|
||||
self.create_wall((self.window.width * 0.8) / 10, 80, self.window.mouse.data["x"] - (self.window.width * 0.8) / 20, self.window.mouse.data["y"] - 80)
|
||||
elif symbol == arcade.key.C:
|
||||
for sprite in self.spritelist:
|
||||
if not isinstance(sprite.pymunk_obj, FakeShape):
|
||||
self.space.remove(sprite.pymunk_obj, sprite.pymunk_obj.body)
|
||||
else:
|
||||
for shape in sprite.pymunk_obj.body.shapes:
|
||||
self.space.remove(shape)
|
||||
self.space.remove(sprite.pymunk_obj.body)
|
||||
|
||||
self.spritelist.clear()
|
||||
|
||||
def on_show_view(self):
|
||||
super().on_show_view()
|
||||
|
||||
self.create_wall((self.window.width * 0.8), 80, 0, 0)
|
||||
Reference in New Issue
Block a user