Untitled
unknown
plain_text
a year ago
19 kB
8
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