Browse Source

Refactor engine and rendering system: move SDL2 logic to render.py, update entity/game structure, clean up unused files, and fix event handling. Major codebase reorganization for maintainability.

master
Matteo Benedetto 4 months ago
parent
commit
17f895fa08
  1. 26
      Entities/Units/marine.py
  2. 11
      Entities/entity.py
  3. BIN
      aa113cea-0f75-4fdd-b40a-09d79ce33ab0.jpg
  4. BIN
      aa113cea-0f75-4fdd-b40a-09d79ce33ab0.png
  5. 21
      brain.crt
  6. 21
      brainw.crt
  7. 2
      enne2engine/controls.py
  8. 15
      enne2engine/engine.py
  9. 46
      enne2engine/pyglet_wrapper.py
  10. 4
      enne2engine/render.py
  11. 3
      enne2engine/sdl2_utils/gui.py
  12. 0
      enne2engine/sdl2_utils/isogeometry.py
  13. BIN
      fg.png
  14. BIN
      final_image.png
  15. 92
      game_rts.py
  16. BIN
      logo.png
  17. 2
      requirements.txt
  18. 62
      shadow.py
  19. 35
      textarget.py

26
Entities/Units/marine.py

@ -28,9 +28,9 @@ class Marine(Entity):
self.selected = True
# Play a random voice response when selected
sound_file = f"marine/tmawht0{random.randint(0, 3)}.wav"
if not self.engine.cmd_sound_effects:
if not self.game.cmd_sound_effects:
self.graphics.play_sound(sound_file)
self.engine.cmd_sound_effects = True
self.game.cmd_sound_effects = True
def move(self):
# Aquisisci la posizione dell'unità
@ -98,22 +98,22 @@ class Marine(Entity):
# Verifica che le coordinate siano valide
if not (0 <= y < map_height and 0 <= x < map_width):
if not self.engine.cmd_sound_effects:
self.engine.graphics.play_sound("sounds/perror.wav")
self.engine.cmd_sound_effects = True
if not self.game.cmd_sound_effects:
self.graphics.play_sound("sounds/perror.wav")
self.game.cmd_sound_effects = True
return
# Ora puoi controllare il contenuto della cella in sicurezza
if self.engine.map[y][x]["wall"]:
if not self.engine.cmd_sound_effects:
self.engine.graphics.play_sound("sounds/perror.wav")
self.engine.cmd_sound_effects = True
if not self.game.cmd_sound_effects:
self.graphics.play_sound("sounds/perror.wav")
self.game.cmd_sound_effects = True
return
self.target_cell = target_cell
if not self.engine.cmd_sound_effects:
if not self.game.cmd_sound_effects:
self.graphics.play_sound(f"marine/tmayes0{random.randint(0, 3)}.wav")
self.engine.cmd_sound_effects = True
self.game.cmd_sound_effects = True
def cast_rays_to_distance(self, distance):
# Cast rays to check for visibility
@ -126,12 +126,12 @@ class Marine(Entity):
v_x, v_y = self.graphics.inv_iso_transform(ray_x, ray_y)
if v_x>= 0 and v_x < len(self.engine.map[0]) and v_y >= 0 and v_y < len(self.engine.map):
if (v_x, v_y) not in done_cells.keys():
done_cells.update({(v_x, v_y): self.engine.map_shadow[v_y][v_x]})
done_cells.update({(v_x, v_y): self.game.map_shadow[v_y][v_x]})
self.engine.map[v_y][v_x]["visited"] = True
self.engine.map_shadow[v_y][v_x] = 0
self.game.map_shadow[v_y][v_x] = 0
if i == 1:
self.engine.map_shadow[v_y][v_x] = max(done_cells.get((v_x, v_y), 0)-0.5, 0)
self.game.map_shadow[v_y][v_x] = max(done_cells.get((v_x, v_y), 0)-0.5, 0)
print(done_cells.get((v_x, v_y), 0))
#self.graphics.draw_line((self.centered_position[0], self.centered_position[1], ray_x, ray_y), color=(0, 255, 0, 255))
continue

11
Entities/entity.py

@ -1,8 +1,8 @@
class Entity:
def __init__(self, asset, x, y, action, direction, speed, engine):
def __init__(self, asset, x, y, action, direction, speed, game):
self.asset = asset
self.graphics = engine.graphics
self.graphics = game.engine.graphics
self.x = x
self.y = y
self.next_cell = (x, y)
@ -12,12 +12,13 @@ class Entity:
self.direction = direction
self.speed = speed
self.frame = 0
self.engine = engine
self.game = game
self.engine = game.engine
self.selected = True
self.movement = 0
def update(self):
occlusion = self.graphics.get_distance((self.x, self.y), self.engine.cursor_pos) / 4
occlusion = self.graphics.get_distance((self.x, self.y), self.game.cursor_pos) / 4
# Set color based on selection status
color = (0, 255, 0, 255)
@ -27,7 +28,7 @@ class Entity:
# Draw target indicator if target is set
occlusion = self.graphics.engine.map_shadow[self.y][self.x]
occlusion = self.game.map_shadow[self.y][self.x]
if occlusion >= 0.8:
return

BIN
aa113cea-0f75-4fdd-b40a-09d79ce33ab0.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

BIN
aa113cea-0f75-4fdd-b40a-09d79ce33ab0.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

21
brain.crt

@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDdjCCAl6gAwIBAgIUO+7fPRqW/ZlLcjJc7abg7zY0dMswDQYJKoZIhvcNAQEL
BQAwNTEOMAwGA1UECwwFV2F6dWgxDjAMBgNVBAoMBVdhenVoMRMwEQYDVQQHDApD
YWxpZm9ybmlhMB4XDTI1MDQxOTE0NTQwM1oXDTM1MDQxNzE0NTQwM1owXDELMAkG
A1UEBhMCVVMxEzARBgNVBAcMCkNhbGlmb3JuaWExDjAMBgNVBAoMBVdhenVoMQ4w
DAYDVQQLDAVXYXp1aDEYMBYGA1UEAwwPd2F6dWguZGFzaGJvYXJkMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmIj4rPiA56NxFJDDjwMyE41wUzojw5X
zfHOvvn22/G7I5pT7TKebFJcjMhLdj3oP2l3owYC/9SkwXD9S3nIkApLUMeg44LK
LxuDdluneKa8hCm74zzZxP4D8lrSN4GBgCWB4D2UgOBEEBQXqD2bHp8Q/3JZHq44
yRVEBYHRHNKNV8V+X/jwsRQX/AXxrZB5+88+E/qyWA20SFxHu+Ts4qBLbH5WN7sP
i4ZeSYsWOjD4tarF2rCs+2BW08hinYh4gE+AzTlkhpz3AbwHpjx3W/LU1i3zr82T
WvnDzfA89jmT3e+wYHk13b2JvAOFVW1TrEjo5B567Bq4WL3Ff7zoXwIDAQABo1cw
VTAfBgNVHSMEGDAWgBTuFh2D9FW9b4yWpdSgKEgQi6ReHjAJBgNVHRMEAjAAMAsG
A1UdDwQEAwIE8DAaBgNVHREEEzARgg93YXp1aC5kYXNoYm9hcmQwDQYJKoZIhvcN
AQELBQADggEBAJyUnuD7Ss6hHtLlgugYLLKAGCiaTdlkSxzpzjRxka3hMqMVwSaQ
XA+iUuxPZAFA3mMQX/jtgzD60CFJNK9lkHbh9n7uFCKqQwmQrFRwISdmoHI0M46B
nJ6zUsarB7rovg1Mbdt1/1oFtRFq+AblnXlNh0IWrNL8U/kEzCeiR1f6DZZUqzTq
UYmLLp3PVpL4U4TiUhExmpjSwrXrNt6Lus/tfP8ad0jVPBb728LhP0cbZlehRHH6
8KjaSSwwgQ4p3yNC5puTbuSSFFgzbA7SawDkaH0KI4ZTSQHDPsmq2ZQpOXAeAJTC
UXdyb7GGWuxsKjzzCcL2D90mZCsHlK3kjgM=
-----END CERTIFICATE-----

21
brainw.crt

@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDdjCCAl6gAwIBAgIUO+7fPRqW/ZlLcjJc7abg7zY0dMswDQYJKoZIhvcNAQEL
BQAwNTEOMAwGA1UECwwFV2F6dWgxDjAMBgNVBAoMBVdhenVoMRMwEQYDVQQHDApD
YWxpZm9ybmlhMB4XDTI1MDQxOTE0NTQwM1oXDTM1MDQxNzE0NTQwM1owXDELMAkG
A1UEBhMCVVMxEzARBgNVBAcMCkNhbGlmb3JuaWExDjAMBgNVBAoMBVdhenVoMQ4w
DAYDVQQLDAVXYXp1aDEYMBYGA1UEAwwPd2F6dWguZGFzaGJvYXJkMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmIj4rPiA56NxFJDDjwMyE41wUzojw5X
zfHOvvn22/G7I5pT7TKebFJcjMhLdj3oP2l3owYC/9SkwXD9S3nIkApLUMeg44LK
LxuDdluneKa8hCm74zzZxP4D8lrSN4GBgCWB4D2UgOBEEBQXqD2bHp8Q/3JZHq44
yRVEBYHRHNKNV8V+X/jwsRQX/AXxrZB5+88+E/qyWA20SFxHu+Ts4qBLbH5WN7sP
i4ZeSYsWOjD4tarF2rCs+2BW08hinYh4gE+AzTlkhpz3AbwHpjx3W/LU1i3zr82T
WvnDzfA89jmT3e+wYHk13b2JvAOFVW1TrEjo5B567Bq4WL3Ff7zoXwIDAQABo1cw
VTAfBgNVHSMEGDAWgBTuFh2D9FW9b4yWpdSgKEgQi6ReHjAJBgNVHRMEAjAAMAsG
A1UdDwQEAwIE8DAaBgNVHREEEzARgg93YXp1aC5kYXNoYm9hcmQwDQYJKoZIhvcN
AQELBQADggEBAJyUnuD7Ss6hHtLlgugYLLKAGCiaTdlkSxzpzjRxka3hMqMVwSaQ
XA+iUuxPZAFA3mMQX/jtgzD60CFJNK9lkHbh9n7uFCKqQwmQrFRwISdmoHI0M46B
nJ6zUsarB7rovg1Mbdt1/1oFtRFq+AblnXlNh0IWrNL8U/kEzCeiR1f6DZZUqzTq
UYmLLp3PVpL4U4TiUhExmpjSwrXrNt6Lus/tfP8ad0jVPBb728LhP0cbZlehRHH6
8KjaSSwwgQ4p3yNC5puTbuSSFFgzbA7SawDkaH0KI4ZTSQHDPsmq2ZQpOXAeAJTC
UXdyb7GGWuxsKjzzCcL2D90mZCsHlK3kjgM=
-----END CERTIFICATE-----

2
enne2engine/controls.py

@ -16,7 +16,7 @@ class UserControls:
def handle_events(self, mapping, event):
if event.startswith("MOUSEMOTION"):
x, y = event.split(":")[1:]
self.set_cursor(int(x), int(y))
self.game.set_cursor(int(x), int(y))
if method := self.configs[mapping].get(event):
getattr(self, method)()

15
enne2engine/engine.py

@ -0,0 +1,15 @@
from enne2engine.render import SDL2Renderer
from enne2engine.controls import UserControls
import sys
import os
import json
class GameEngine(UserControls):
def __init__(self, map, game):
super().__init__()
self.map = map
self.game = game
self.graphics = SDL2Renderer(self)

46
enne2engine/pyglet_wrapper.py

@ -1,46 +0,0 @@
import pyglet
from .tilemanager import TileManager
class PygletWrapper:
def __init__(self):
self.window = pyglet.window.Window(800, 600, "My Game")
self.window.push_handlers(self)
self.running = True
def on_draw(self):
self.window.clear()
# Render your game here
def on_close(self):
self.running = False
def handle_events(self):
pyglet.clock.tick()
self.window.dispatch_events()
return not self.running
def clear_screen(self, color=(0, 0, 0, 255)):
pyglet.gl.glClearColor(color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3] / 255.0)
self.window.clear()
def present_renderer(self):
self.window.flip()
def quit(self):
pyglet.app.exit()
def load_spritesheet(self, name, path):
image = pyglet.image.load(path)
self.spritesheets[name] = image
self.tile_managers[name] = TileManager(path.replace('.png', '.xml'))
def render_sprite(self, name, x, y):
sprite = pyglet.sprite.Sprite(self.spritesheets[name], x, y)
sprite.draw()
def render_tile(self, spritesheet_name, tile_name, x, y):
tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile_name)
if tile_rect is not None:
image = self.spritesheets[spritesheet_name].get_region(*tile_rect)
sprite = pyglet.sprite.Sprite(image, x, y)
sprite.draw()

4
enne2engine/sdl2_wrapper.py → enne2engine/render.py

@ -8,10 +8,10 @@ import sdl2.ext
import sdl2.sdlmixer as sdlmixer
from .tilemanager import TileManager
from .spritemanager import SpriteManager
from .isogeometry import IsometricGeometry
from .sdl2_utils.isogeometry import IsometricGeometry
from .sdl2_utils.gui import SDL2Gui
class SDL2Wrapper(IsometricGeometry, SDL2Gui):
class SDL2Renderer(IsometricGeometry, SDL2Gui):
def __init__(self, engine):
self.engine = engine
sdl2.ext.init()

3
enne2engine/sdl2_utils/gui.py

@ -56,8 +56,7 @@ class SDL2Gui:
sprite = self.factory.from_text(text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font)
text_width, text_height = sprite.size
self.renderer.fill((3, 3, text_width + 10, text_height + 4), sdl2.ext.Color(255, 255, 255))
self.draw_text(text, font, (8, 5), sdl2.ext.Color(0, 0, 0))
self.renderer.copy(sprite, dstrect=(5, 5))
def start_dialog(self, **kwargs):
self.dialog("Welcome to the Mice!", subtitle="A game by Matteo because was bored", **kwargs)

0
enne2engine/isogeometry.py → enne2engine/sdl2_utils/isogeometry.py

BIN
fg.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
final_image.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

92
engine_demo.py → game_rts.py

@ -1,25 +1,13 @@
from enne2engine.sdl2_wrapper import SDL2Wrapper
from enne2engine.controls import UserControls
import json
import sys
import os
import json
from Entities.Units.marine import Marine
from enne2engine import engine
from Entities.Units.marine import Marine
class GameEngine(UserControls):
class Game:
def __init__(self):
super().__init__()
# Load map from JSON file
try:
with open("assets/maps/map.json", "r") as map_file:
self.map = json.load(map_file)
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Error loading map file: {e}")
print("Exiting program.")
sys.exit(0)
self.graphics = SDL2Wrapper(self)
self.engine = engine.GameEngine(self.load_map(), self)
self.frame_time = 0
self.cursor_pos = (0, 0)
@ -31,29 +19,29 @@ class GameEngine(UserControls):
def run(self):
running = True
# Set a custom scale if needed
self.graphics.set_scaling_factor(1.0) # 50% scale
self.engine.graphics.set_scaling_factor(1.0) # 50% scale
self.entities.append(Marine("knight", 0, 0, "idle", 1, 1, self))
self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
#5 more marines
self.entities.append(Marine("knight", 0, 5, "idle", 1, 1, self))
self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
self.entities.append(Marine("knight", 1,1, "idle", 1, 1, self))
self.entities.append(Marine("knight", 2,2, "idle", 1, 1, self))
self.entities.append(Marine("knight", 3,3, "idle", 1, 1, self))
# self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
# #5 more marines
# self.entities.append(Marine("knight", 0, 5, "idle", 1, 1, self))
# self.entities.append(Marine("knight", 5, 5, "idle", 1, 1, self))
# self.entities.append(Marine("knight", 1,1, "idle", 1, 1, self))
# self.entities.append(Marine("knight", 2,2, "idle", 1, 1, self))
# self.entities.append(Marine("knight", 3,3, "idle", 1, 1, self))
while running:
self.cmd_sound_effects = False
# Start the frame timer
perf_counter = self.graphics.get_perf_counter()
perf_counter = self.engine.graphics.get_perf_counter()
# Initialize the map shadow and entities positions
self.map_shadow = [ [1 for _ in range(len(self.map[0]))] for _ in range(len(self.map)) ]
self.map_shadow = [ [1 for _ in range(len(self.engine.map[0]))] for _ in range(len(self.engine.map)) ]
self.entities_positions.clear()
for entity in self.entities:
self.entities_positions[entity.next_cell] = entity
self.entities_positions[(entity.x, entity.y)] = entity
# Handle events
event = self.graphics.handle_events()
event = self.engine.graphics.handle_events()
if event:
#print(f"Event detected: {event}")
if event.startswith("MOUSEDOWN"):
@ -71,36 +59,36 @@ class GameEngine(UserControls):
if event[-1] == "1":
self.select_entity_at_cursor()
self.handle_events("keymap_game", event)
self.engine.handle_events("keymap_game", event)
running = False if event == "QUIT" else True
self.graphics.clear_screen()
self.engine.graphics.clear_screen()
for entity in self.entities:
entity.update()
# Create the map background texture with tiles
self.graphics.create_background(self.map, "tiles", self.map_shadow)
self.cursor_pos = self.graphics.draw_cursor()
self.graphics.render_background()
self.graphics.render_sprites()
self.graphics.draw_selection_rectangle()
self.graphics.update_status(f"Frame time: {round(self.frame_time)}ms - FPS: {round(1000/self.frame_time if self.frame_time != 0 else 1)}")
self.graphics.present_renderer()
self.frame_time = self.graphics.get_frame_time(perf_counter)
self.graphics.delay_frame(self.frame_time,50)
self.graphics.quit()
self.engine.graphics.create_background(self.engine.map, "tiles", self.map_shadow)
self.cursor_pos = self.engine.graphics.draw_cursor()
self.engine.graphics.render_background()
self.engine.graphics.render_sprites()
self.engine.graphics.draw_selection_rectangle()
self.engine.graphics.update_status(f"Frame time: {round(self.frame_time)}ms - FPS: {round(1000/self.frame_time if self.frame_time != 0 else 1)}")
self.engine.graphics.present_renderer()
self.frame_time = self.engine.graphics.get_frame_time(perf_counter)
self.engine.graphics.delay_frame(self.frame_time,50)
self.engine.graphics.quit()
def set_cursor(self, x, y):
self.graphics.cursor = (x, y)
self.engine.graphics.cursor = (x, y)
def load_assets(self):
self.graphics.load_tilesheet("tiles", "assets/tiles/landscapeTiles_sheet.png")
self.engine.graphics.load_tilesheet("tiles", "assets/tiles/landscapeTiles_sheet.png")
for dir in os.listdir("assets/KnightBasic"):
for file in os.listdir(f"assets/KnightBasic/{dir}"):
if file.endswith(".json"):
self.graphics.load_spritesheet(file[:-5].lower(), f"assets/KnightBasic/{dir}/{file}")
self.engine.graphics.load_spritesheet(file[:-5].lower(), f"assets/KnightBasic/{dir}/{file}")
def select_entity_at_cursor(self):
cursor_x, cursor_y = self.cursor_pos
@ -136,13 +124,23 @@ class GameEngine(UserControls):
# Select entities within the rectangle
for entity in self.entities:
# Convert entity position to screen coordinates
screen_x, screen_y = self.graphics.iso_transform(entity.x, entity.y)
screen_x, screen_y = self.engine.graphics.iso_transform(entity.x, entity.y)
# Check if entity is within selection rectangle
if min_x <= screen_x <= max_x and min_y <= screen_y <= max_y:
entity.select_unit()
print(f"Selected entity in area: {entity.asset} at position {entity.x}, {entity.y}")
def load_map(self):
# Load map from JSON file
try:
with open("assets/maps/map.json", "r") as map_file:
return json.load(map_file)
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Error loading map file: {e}")
print("Exiting program.")
sys.exit(0)
if __name__ == "__main__":
engine = GameEngine()
engine.run()
game = Game()
game.run()

BIN
logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

2
requirements.txt

@ -0,0 +1,2 @@
pysdl2
Pillow

62
shadow.py

@ -1,62 +0,0 @@
from PIL import ImageOps, Image, ImageFilter
FG_IMG_PATH = "fg.png"
BG_IMG_PATH = "bg.jpeg"
def load_image(path):
"""Load an image using PIL."""
return Image.open(path)
def extract_alpha(image):
"""Extract the alpha channel from an image."""
return image.split()[-1]
def create_shadow_from_alpha(alpha, blur_radius):
"""Create a shadow based on a blurred version of the alpha channel."""
alpha_blur = alpha.filter(ImageFilter.BoxBlur(blur_radius))
shadow = Image.new(mode="RGB", size=alpha_blur.size)
shadow.putalpha(alpha_blur)
return shadow
def composite_images(fg, shadow):
"""Composite the shadow and foreground onto the background."""
shadow.paste(fg, (-5, 4), fg)
return shadow
if __name__ == "__main__":
# Load the images
fg = load_image(FG_IMG_PATH)
# Create the shadow based on the alpha channel of the foreground
alpha = extract_alpha(fg)
shadow = create_shadow_from_alpha(alpha, blur_radius=1)
# Composite the shadow and foreground onto the background
final_image = composite_images(fg, shadow)
# Display the final image (optional)
final_image.show()
# Save the final image
final_image.save(f"final_image.png")

35
textarget.py

@ -1,35 +0,0 @@
import sdl2
import sdl2.ext
def run():
sdl2.ext.init()
window = sdl2.ext.Window("SDL2 Texture Example", size=(640, 480))
renderer = sdl2.ext.Renderer(window)
target_surface = sdl2.SDL_CreateRGBSurface(0, 640, 480, 32, 0, 0, 0, 0)
# Crea texture target usando il metodo del renderer
target_tex = sdl2.ext.Texture(renderer, surface=target_surface)
# Disegna sulla texture
sdl2.SDL_SetRenderTarget(renderer.renderer, target_tex.tx)
renderer.clear()
square_rect = sdl2.SDL_Rect(270, 190, 100, 100)
renderer.draw_rect([square_rect], sdl2.ext.Color(255, 0, 0))
renderer.present()
sdl2.SDL_SetRenderTarget(renderer.renderer, None)
# Main loop
running = True
while running:
for event in sdl2.ext.get_events():
if event.type == sdl2.SDL_QUIT:
running = False
break
renderer.clear()
renderer.copy(target_tex) # Ora usa l'oggetto Texture corretto
renderer.present()
sdl2.SDL_Delay(10)
sdl2.ext.quit()
if __name__ == "__main__":
run()
Loading…
Cancel
Save