Untitled
unknown
plain_text
2 months ago
19 kB
4
Indexable
import sys import tkinter as tk from tkinter import ttk import pygame import numpy as np from OpenGL.GL import * from OpenGL.GLU import * from pygame.locals import * import math class Component: def __init__(self, game_object): self.game_object = game_object game_object.components.append(self) def update(self): pass class GameObject: def __init__(self, object_name="GameObject"): self.name = object_name self.position = np.array([0.0, 0.0, 0.0]) self.rotation = np.array([0.0, 0.0, 0.0]) self.scale = np.array([1.0, 1.0, 1.0]) self.children = [] self.parent = None self.components = [] self.mesh_renderer = None self.gizmo = Gizmo(self) self.engine = None # Will be set when added to scene def add_child(self, child): child.parent = self self.children.append(child) def update(self): for component in self.components: component.update() for child in self.children: child.update() class Camera: def __init__(self): self.position = np.array([0.0, 2.0, 5.0], dtype=np.float32) self.front = np.array([0.0, 0.0, -1.0], dtype=np.float32) self.up = np.array([0.0, 1.0, 0.0], dtype=np.float32) self.right = np.array([1.0, 0.0, 0.0], dtype=np.float32) self.yaw = -90.0 self.pitch = 0.0 self.move_speed = 0.3 self.mouse_sensitivity = 0.1 self.last_mouse = None def update_camera_vectors(self): self.front[0] = math.cos(math.radians(self.yaw)) * math.cos(math.radians(self.pitch)) self.front[1] = math.sin(math.radians(self.pitch)) self.front[2] = math.sin(math.radians(self.yaw)) * math.cos(math.radians(self.pitch)) self.front = self.front / np.linalg.norm(self.front) self.right = np.cross(self.front, np.array([0.0, 1.0, 0.0])) self.right = self.right / np.linalg.norm(self.right) self.up = np.cross(self.right, self.front) def handle_mouse(self, mouse_pos, right_button_down): if right_button_down: # Only update camera if right mouse button is held down mouse_rel = pygame.mouse.get_rel() x_offset = mouse_rel[0] y_offset = -mouse_rel[1] x_offset *= self.mouse_sensitivity y_offset *= self.mouse_sensitivity self.yaw += x_offset self.pitch += y_offset self.pitch = np.clip(self.pitch, -89.0, 89.0) self.update_camera_vectors() def handle_keyboard(self, keys): if keys[K_w]: self.position += self.front * self.move_speed if keys[K_s]: self.position -= self.front * self.move_speed if keys[K_a]: self.position -= self.right * self.move_speed if keys[K_d]: self.position += self.right * self.move_speed if keys[K_SPACE]: self.position[1] += self.move_speed if keys[K_LSHIFT]: self.position[1] -= self.move_speed def apply_view(self): glLoadIdentity() gluPerspective(45, 800 / 600, 0.1, 1000.0) target = self.position + self.front gluLookAt( self.position[0], self.position[1], self.position[2], target[0], target[1], target[2], self.up[0], self.up[1], self.up[2] ) class PrimitiveFactory: @staticmethod def create_cube(): vertices = [ -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, ] indices = [ 0, 1, 2, 2, 3, 0, # Front 1, 5, 6, 6, 2, 1, # Right 5, 4, 7, 7, 6, 5, # Back 4, 0, 3, 3, 7, 4, # Left 3, 2, 6, 6, 7, 3, # Top 4, 5, 1, 1, 0, 4 # Bottom ] return vertices, indices @staticmethod def create_sphere(radius=1, segments=16): vertices = [] indices = [] for i in range(segments + 1): lat = np.pi * (-0.5 + float(i) / segments) for j in range(segments + 1): lon = 2 * np.pi * float(j) / segments x = np.cos(lat) * np.cos(lon) * radius y = np.sin(lat) * radius z = np.cos(lat) * np.sin(lon) * radius vertices.extend([x, y, z]) for i in range(segments): for j in range(segments): first = i * (segments + 1) + j second = first + segments + 1 indices.extend([first, second, first + 1, second, second + 1, first + 1]) return vertices, indices class Editor(tk.Tk): def __init__(self): super().__init__() self.title("Game Engine Editor") self.geometry("400x800") # Made taller to accommodate inspector # Hierarchy Panel self.hierarchy_frame = ttk.LabelFrame(self, text="Hierarchy") self.hierarchy_frame.pack(fill=tk.X, padx=5, pady=5) self.hierarchy_header = ttk.Frame(self.hierarchy_frame) self.hierarchy_header.pack(fill=tk.X, padx=5, pady=5) self.add_button = ttk.Button(self.hierarchy_header, text="Add", command=self.show_add_menu) self.add_button.pack(side=tk.LEFT) self.hierarchy_tree = ttk.Treeview(self.hierarchy_frame, height=15) self.hierarchy_tree.pack(fill=tk.X, padx=5, pady=5) self.hierarchy_tree.bind('<<TreeviewSelect>>', self.on_select) # Inspector Panel self.inspector_frame = ttk.LabelFrame(self, text="Inspector") self.inspector_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Transform controls self.transform_frame = ttk.LabelFrame(self.inspector_frame, text="Transform") self.transform_frame.pack(fill=tk.X, padx=5, pady=5) # Position controls self.position_vars = [tk.StringVar(value="0.0") for _ in range(3)] self.create_vector3_control("Position", self.position_vars) # Rotation controls self.rotation_vars = [tk.StringVar(value="0.0") for _ in range(3)] self.create_vector3_control("Rotation", self.rotation_vars) # Scale controls self.scale_vars = [tk.StringVar(value="1.0") for _ in range(3)] self.create_vector3_control("Scale", self.scale_vars) self.selected_object = None self.scene_root = GameObject("Scene") self.scene_objects = {} self.hierarchy_tree.insert('', 'end', 'root', text='Scene') self.running = True def create_vector3_control(self, label, variables): frame = ttk.Frame(self.transform_frame) frame.pack(fill=tk.X, padx=5, pady=2) ttk.Label(frame, text=label, width=8).pack(side=tk.LEFT) for i, var in enumerate(variables): subframe = ttk.Frame(frame) subframe.pack(side=tk.LEFT, padx=2) ttk.Label(subframe, text="XYZ"[i], width=2).pack(side=tk.LEFT) entry = ttk.Entry(subframe, textvariable=var, width=8) entry.pack(side=tk.LEFT) entry.bind('<Return>', lambda e, idx=i, vars=variables: self.on_transform_change(label.lower(), idx, vars)) def on_select(self, event): selected_items = self.hierarchy_tree.selection() if selected_items and selected_items[0] != 'root': self.selected_object = self.scene_objects[selected_items[0]] self.update_inspector() else: self.selected_object = None self.clear_inspector() def update_inspector(self): if self.selected_object: for i, var in enumerate(self.position_vars): var.set(f"{self.selected_object.position[i]:.2f}") for i, var in enumerate(self.rotation_vars): var.set(f"{self.selected_object.rotation[i]:.2f}") for i, var in enumerate(self.scale_vars): var.set(f"{self.selected_object.scale[i]:.2f}") def clear_inspector(self): for vars in [self.position_vars, self.rotation_vars, self.scale_vars]: for var in vars: var.set("0.0") def on_transform_change(self, transform_type, index, variables): if not self.selected_object: return try: value = float(variables[index].get()) if transform_type == "position": self.selected_object.position[index] = value elif transform_type == "rotation": self.selected_object.rotation[index] = value elif transform_type == "scale": self.selected_object.scale[index] = value except ValueError: self.update_inspector() # Reset to current values if invalid input def show_add_menu(self): menu = tk.Menu(self, tearoff=0) menu.add_command(label="Cube", command=lambda: self.add_primitive("Cube")) menu.add_command(label="Sphere", command=lambda: self.add_primitive("Sphere")) menu.tk_popup(self.add_button.winfo_rootx(), self.add_button.winfo_rooty() + self.add_button.winfo_height()) def add_primitive(self, primitive_type): game_object = GameObject(f"{primitive_type}_{len(self.scene_objects)}") game_object.engine = self.engine # Set reference to engine vertices, indices = (PrimitiveFactory.create_cube() if primitive_type == "Cube" else PrimitiveFactory.create_sphere()) game_object.mesh_renderer = MeshRenderer(game_object, vertices, indices) self.scene_root.add_child(game_object) item_id = self.hierarchy_tree.insert('root', 'end', text=game_object.name) self.scene_objects[item_id] = game_object def process_events(self): self.update() class MeshRenderer(Component): def __init__(self, game_object, vertices, indices): super().__init__(game_object) self.vertices = vertices self.indices = indices def render(self): glBegin(GL_TRIANGLES) for index in self.indices: vertex_idx = index * 3 glVertex3f(self.vertices[vertex_idx], self.vertices[vertex_idx + 1], self.vertices[vertex_idx + 2]) glEnd() def update(self): glPushMatrix() glTranslatef(*self.game_object.position) glRotatef(self.game_object.rotation[0], 1, 0, 0) glRotatef(self.game_object.rotation[1], 0, 1, 0) glRotatef(self.game_object.rotation[2], 0, 0, 1) glScalef(*self.game_object.scale) self.render() glPopMatrix() class Gizmo(Component): def __init__(self, game_object): super().__init__(game_object) self.selected_axis = None self.axis_colors = { 'x': (1.0, 0.0, 0.0), 'y': (0.0, 1.0, 0.0), 'z': (0.0, 0.0, 1.0) } self.axis_length = 1.0 # Base length of the gizmo axes self.dragging = False self.last_mouse_pos = None def render(self, camera_position): glDisable(GL_LIGHTING) glLineWidth(2.0) # Calculate distance from camera to object distance = np.linalg.norm(camera_position - self.game_object.position) scale_factor = distance * 0.1 # Scale gizmo size based on distance # Draw axes glBegin(GL_LINES) for axis, color in zip(['x', 'y', 'z'], [(1, 0, 0), (0, 1, 0), (0, 0, 1)]): glColor3f(*color) glVertex3f(0, 0, 0) if axis == 'x': glVertex3f(scale_factor, 0, 0) elif axis == 'y': glVertex3f(0, scale_factor, 0) else: glVertex3f(0, 0, scale_factor) glEnd() glLineWidth(1.0) glEnable(GL_LIGHTING) def update(self): if self.game_object == self.game_object.engine.editor.selected_object: glPushMatrix() glTranslatef(*self.game_object.position) glRotatef(self.game_object.rotation[0], 1, 0, 0) glRotatef(self.game_object.rotation[1], 0, 1, 0) glRotatef(self.game_object.rotation[2], 0, 0, 1) self.render(self.game_object.engine.camera.position) # Pass camera position glPopMatrix() def handle_mouse_click(self, mouse_pos): # Check if the mouse is over any of the gizmo axes if self.selected_axis is None: for axis in ['x', 'y', 'z']: if self.is_mouse_over_axis(axis, mouse_pos): self.selected_axis = axis self.dragging = True self.last_mouse_pos = mouse_pos break def is_mouse_over_axis(self, axis, mouse_pos): # Convert mouse position to normalized device coordinates width, height = pygame.display.get_surface().get_size() x = (mouse_pos[0] / width) * 2 - 1 y = 1 - (mouse_pos[1] / height) * 2 # Check against the axis position in world coordinates if axis == 'x': axis_start = np.array([0, 0, 0]) axis_end = np.array([self.axis_length, 0, 0]) elif axis == 'y': axis_start = np.array([0, 0, 0]) axis_end = np.array([0, self.axis_length, 0]) else: # 'z' axis_start = np.array([0, 0, 0]) axis_end = np.array([0, 0, self.axis_length]) # Check if the mouse is close to the axis line # This is a simplified distance check; you may want to implement a more precise method distance_to_axis = np.linalg.norm(np.cross(axis_end - axis_start, axis_start - np.array([x, y, 0]))) / np.linalg.norm(axis_end - axis_start) return distance_to_axis < 0.1 # Adjust threshold as needed def update_position(self, mouse_delta): if self.selected_axis and self.dragging: if self.selected_axis == 'x': self.game_object.position[0] += mouse_delta[0] * 0.1 # Adjust sensitivity as needed elif self.selected_axis == 'y': self.game_object.position[1] += mouse_delta[1] * 0.1 elif self.selected_axis == 'z': self.game_object.position[2] += mouse_delta[2] * 0.1 def stop_dragging(self): self.selected_axis = None self.dragging = False class Engine: def __init__(self): pygame.init() self.editor = Editor() self.editor.engine = self # Add reference to engine in editor self.display = (800, 600) self.screen = pygame.display.set_mode(self.display, DOUBLEBUF | OPENGL) pygame.display.set_caption("Game Engine Viewport") # Add mouse_captured state self.mouse_captured = True pygame.event.set_grab(True) pygame.mouse.set_visible(False) pygame.mouse.set_pos(self.display[0]//2, self.display[1]//2) self.mouse_rel_needs_reset = False glEnable(GL_DEPTH_TEST) # Update lighting configuration glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT0, GL_POSITION, [0, 1, 0, 0]) # Directional light from above glLightfv(GL_LIGHT0, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]) # Add some ambient light glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.5, 0.5, 0.5, 1.0]) # Reduce diffuse intensity # Enable color material to work better with basic colors glEnable(GL_COLOR_MATERIAL) glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) self.camera = Camera() def toggle_mouse_capture(self): self.mouse_captured = not self.mouse_captured pygame.event.set_grab(self.mouse_captured) pygame.mouse.set_visible(not self.mouse_captured) if self.mouse_captured: pygame.mouse.set_pos(self.display[0]//2, self.display[1]//2) self.mouse_rel_needs_reset = True def handle_input(self): if self.mouse_captured: # Only handle camera input when mouse is captured self.camera.handle_keyboard(pygame.key.get_pressed()) self.camera.handle_mouse(pygame.mouse.get_pos(), pygame.mouse.get_pressed()[2]) def render_grid(self): glDisable(GL_LIGHTING) # Disable lighting for grid glColor3f(0.2, 0.2, 0.2) # Set grid color to gray glBegin(GL_LINES) grid_size = 20 step = 1.0 for x in range(-grid_size, grid_size + 1): glVertex3f(x * step, 0, -grid_size * step) glVertex3f(x * step, 0, grid_size * step) glVertex3f(-grid_size * step, 0, x * step) glVertex3f(grid_size * step, 0, x * step) glEnd() glEnable(GL_LIGHTING) # Re-enable lighting for other objects def run(self): while self.editor.running: for event in pygame.event.get(): if event.type == pygame.QUIT: self.editor.running = False elif event.type == pygame.KEYDOWN: if event.key == K_ESCAPE: self.toggle_mouse_capture() elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 3: # Right mouse button self.toggle_mouse_capture() elif event.button == 1: # Left mouse button if self.editor.selected_object and self.editor.selected_object.gizmo: self.editor.selected_object.gizmo.handle_mouse_click(pygame.mouse.get_pos()) elif event.type == pygame.MOUSEBUTTONUP: if event.button == 3: # Right mouse button self.toggle_mouse_capture() elif event.button == 1: # Left mouse button if self.editor.selected_object and self.editor.selected_object.gizmo: self.editor.selected_object.gizmo.stop_dragging() right_button_down = pygame.mouse.get_pressed()[2] # Check if right mouse button is down if self.mouse_rel_needs_reset: pygame.mouse.get_rel() self.mouse_rel_needs_reset = False glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) self.handle_input() self.camera.handle_mouse(pygame.mouse.get_pos(), right_button_down) # Pass right button state self.camera.apply_view() self.render_grid() # Update all components for each game object for game_object in self.editor.scene_root.children: # First render the mesh if game_object.mesh_renderer: game_object.mesh_renderer.update() # Then render the gizmo if object is selected if game_object.gizmo: game_object.gizmo.update() pygame.display.flip() self.editor.process_events() pygame.time.wait(10) pygame.quit() self.editor.destroy() sys.exit() if __name__ == "__main__": engine = Engine() engine.run()
Editor is loading...
Leave a Comment