Add initial project structure with Pygame integration and camera controls
This commit is contained in:
		
						commit
						43882f4fef
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | uv.lock | ||||||
|  | .venv/ | ||||||
							
								
								
									
										258
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,258 @@ | |||||||
|  | import pygame | ||||||
|  | import time | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | # Initialize Pygame | ||||||
|  | pygame.init() | ||||||
|  | 
 | ||||||
|  | # Constants | ||||||
|  | SCREEN_WIDTH = 1920/2 | ||||||
|  | SCREEN_HEIGHT = 1080/2 | ||||||
|  | BLACK = (0, 0, 0) | ||||||
|  | DARK_GRAY = (64, 64, 64) | ||||||
|  | GRAY = (128, 128, 128) | ||||||
|  | WHITE = (255, 255, 255) | ||||||
|  | 
 | ||||||
|  | # Grid settings | ||||||
|  | GRID_WIDTH = 20  # Number of cells horizontally | ||||||
|  | GRID_HEIGHT = 15  # Number of cells vertically | ||||||
|  | CELL_SIZE = 20  # Size of each cell in pixels | ||||||
|  | 
 | ||||||
|  | DEFAULT_TPS = 20 # Amount of ticks per second for the simulation | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Camera: | ||||||
|  |     def __init__(self): | ||||||
|  |         self.x = 0 | ||||||
|  |         self.y = 0 | ||||||
|  |         self.target_x = 0 | ||||||
|  |         self.target_y = 0 | ||||||
|  |         self.speed = 700  # Pixels per second | ||||||
|  |         self.zoom = 1.0 | ||||||
|  |         self.target_zoom = 1.0 | ||||||
|  |         self.smoothing = 0.15  # Higher = more responsive, lower = more smooth | ||||||
|  |         self.zoom_smoothing = 0.10 | ||||||
|  |         self.is_panning = False | ||||||
|  |         self.last_mouse_pos = None | ||||||
|  | 
 | ||||||
|  |     def update(self, keys, deltatime): | ||||||
|  |         # Update target position based on input | ||||||
|  |         if keys[pygame.K_w]: | ||||||
|  |             self.target_y -= self.speed * deltatime / self.zoom | ||||||
|  |         if keys[pygame.K_s]: | ||||||
|  |             self.target_y += self.speed * deltatime / self.zoom | ||||||
|  |         if keys[pygame.K_a]: | ||||||
|  |             self.target_x -= self.speed * deltatime / self.zoom | ||||||
|  |         if keys[pygame.K_d]: | ||||||
|  |             self.target_x += self.speed * deltatime / self.zoom | ||||||
|  |         if keys[pygame.K_r]: | ||||||
|  |             self.target_x = 0 | ||||||
|  |             self.target_y = 0 | ||||||
|  | 
 | ||||||
|  |         # Smooth camera movement with drift | ||||||
|  |         smoothing_factor = 1 - pow(1 - self.smoothing, deltatime * 60)  # Adjust smoothing based on deltatime | ||||||
|  |         self.x += (self.target_x - self.x) * smoothing_factor | ||||||
|  |         self.y += (self.target_y - self.y) * smoothing_factor | ||||||
|  | 
 | ||||||
|  |         # Smooth zoom | ||||||
|  |         zoom_smoothing_factor = 1 - pow(1 - self.zoom_smoothing, deltatime * 60) | ||||||
|  |         self.zoom += (self.target_zoom - self.zoom) * zoom_smoothing_factor | ||||||
|  | 
 | ||||||
|  |     def handle_zoom(self, zoom_delta): | ||||||
|  |         # Zoom in/out with mouse wheel | ||||||
|  |         zoom_factor = 1.1 | ||||||
|  |         if zoom_delta > 0:  # Zoom in | ||||||
|  |             self.target_zoom *= zoom_factor | ||||||
|  |         elif zoom_delta < 0:  # Zoom out | ||||||
|  |             self.target_zoom /= zoom_factor | ||||||
|  | 
 | ||||||
|  |         # Clamp zoom levels | ||||||
|  |         self.target_zoom = max(0.1, min(5.0, self.target_zoom)) | ||||||
|  | 
 | ||||||
|  |     def start_panning(self, mouse_pos): | ||||||
|  |         self.is_panning = True | ||||||
|  |         self.last_mouse_pos = mouse_pos | ||||||
|  | 
 | ||||||
|  |     def stop_panning(self): | ||||||
|  |         self.is_panning = False | ||||||
|  |         self.last_mouse_pos = None | ||||||
|  | 
 | ||||||
|  |     def pan(self, mouse_pos): | ||||||
|  |         if self.is_panning and self.last_mouse_pos: | ||||||
|  |             dx = mouse_pos[0] - self.last_mouse_pos[0] | ||||||
|  |             dy = mouse_pos[1] - self.last_mouse_pos[1] | ||||||
|  |             self.x -= dx / self.zoom | ||||||
|  |             self.y -= dy / self.zoom | ||||||
|  |             self.target_x = self.x  # Sync target position with actual position | ||||||
|  |             self.target_y = self.y | ||||||
|  |             self.last_mouse_pos = mouse_pos | ||||||
|  | 
 | ||||||
|  |     def get_real_coordinates(self, screen_x, screen_y): | ||||||
|  |         # Convert screen coordinates to world coordinates | ||||||
|  |         world_x = (screen_x - SCREEN_WIDTH // 2 + self.x * self.zoom) / self.zoom | ||||||
|  |         world_y = (screen_y - SCREEN_HEIGHT // 2 + self.y * self.zoom) / self.zoom | ||||||
|  |         # Adjust for grid centering | ||||||
|  |         world_x += GRID_WIDTH * CELL_SIZE / 2 | ||||||
|  |         world_y += GRID_HEIGHT * CELL_SIZE / 2 | ||||||
|  |         # Convert to grid coordinates | ||||||
|  |         world_x = int(world_x // CELL_SIZE) | ||||||
|  |         world_y = int(world_y // CELL_SIZE) | ||||||
|  | 
 | ||||||
|  |         return world_x, world_y | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def draw_grid(screen, camera, showing_grid=True): | ||||||
|  |     # Fill screen with black | ||||||
|  |     screen.fill(BLACK) | ||||||
|  | 
 | ||||||
|  |     # Calculate effective cell size with zoom | ||||||
|  |     effective_cell_size = CELL_SIZE * camera.zoom | ||||||
|  | 
 | ||||||
|  |     # Calculate grid boundaries in world coordinates (centered at 0,0) | ||||||
|  |     grid_world_width = GRID_WIDTH * effective_cell_size | ||||||
|  |     grid_world_height = GRID_HEIGHT * effective_cell_size | ||||||
|  | 
 | ||||||
|  |     # Calculate grid position relative to camera (with grid centered at 0,0) | ||||||
|  |     grid_center_x = SCREEN_WIDTH // 2 - camera.x * camera.zoom | ||||||
|  |     grid_center_y = SCREEN_HEIGHT // 2 - camera.y * camera.zoom | ||||||
|  | 
 | ||||||
|  |     grid_left = grid_center_x - grid_world_width // 2 | ||||||
|  |     grid_top = grid_center_y - grid_world_height // 2 | ||||||
|  |     grid_right = grid_left + grid_world_width | ||||||
|  |     grid_bottom = grid_top + grid_world_height | ||||||
|  | 
 | ||||||
|  |     # Check if grid should be shown | ||||||
|  |     if not showing_grid: | ||||||
|  |         return  # Exit early if grid is not visible | ||||||
|  | 
 | ||||||
|  |     # Check if grid is visible on screen | ||||||
|  |     if (grid_right < 0 or grid_left > SCREEN_WIDTH or | ||||||
|  |             grid_bottom < 0 or grid_top > SCREEN_HEIGHT): | ||||||
|  |         return  # Grid is completely off-screen | ||||||
|  | 
 | ||||||
|  |     # Fill the grid area with dark gray background | ||||||
|  |     grid_rect = pygame.Rect(max(0, grid_left), max(0, grid_top), | ||||||
|  |                             min(SCREEN_WIDTH, grid_right) - max(0, grid_left), | ||||||
|  |                             min(SCREEN_HEIGHT, grid_bottom) - max(0, grid_top)) | ||||||
|  | 
 | ||||||
|  |     # Only draw if the rectangle has positive dimensions | ||||||
|  |     if grid_rect.width > 0 and grid_rect.height > 0: | ||||||
|  |         pygame.draw.rect(screen, DARK_GRAY, grid_rect) | ||||||
|  | 
 | ||||||
|  |     # Draw vertical grid lines (only if zoom is high enough to see them clearly) | ||||||
|  |     if effective_cell_size > 4: | ||||||
|  |         for i in range(GRID_WIDTH + 1): | ||||||
|  |             line_x = grid_left + i * effective_cell_size | ||||||
|  |             if 0 <= line_x <= SCREEN_WIDTH: | ||||||
|  |                 start_y = max(0, grid_top) | ||||||
|  |                 end_y = min(SCREEN_HEIGHT, grid_bottom) | ||||||
|  |                 if start_y < end_y: | ||||||
|  |                     pygame.draw.line(screen, GRAY, (line_x, start_y), (line_x, end_y)) | ||||||
|  | 
 | ||||||
|  |         # Draw horizontal grid lines | ||||||
|  |         for i in range(GRID_HEIGHT + 1): | ||||||
|  |             line_y = grid_top + i * effective_cell_size | ||||||
|  |             if 0 <= line_y <= SCREEN_HEIGHT: | ||||||
|  |                 start_x = max(0, grid_left) | ||||||
|  |                 end_x = min(SCREEN_WIDTH, grid_right) | ||||||
|  |                 if start_x < end_x: | ||||||
|  |                     pygame.draw.line(screen, GRAY, (start_x, line_y), (end_x, line_y)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1) | ||||||
|  |     pygame.display.set_caption("Dynamic Abstraction System Testing") | ||||||
|  |     clock = pygame.time.Clock() | ||||||
|  |     camera = Camera() | ||||||
|  | 
 | ||||||
|  |     is_showing_grid = True  # Flag to control grid visibility | ||||||
|  | 
 | ||||||
|  |     font = pygame.font.Font('freesansbold.ttf', 16) | ||||||
|  | 
 | ||||||
|  |     tick_interval = 1.0 / DEFAULT_TPS  # Time per tick | ||||||
|  |     last_tick_time = time.perf_counter()  # Tracks the last tick time | ||||||
|  |     last_tps_time = time.perf_counter()  # Tracks the last TPS calculation time | ||||||
|  |     tick_counter = 0  # Counts ticks executed | ||||||
|  |     actual_tps = 0  # Stores the calculated TPS | ||||||
|  | 
 | ||||||
|  |     print("Controls:") | ||||||
|  |     print("WASD - Move camera") | ||||||
|  |     print("Mouse wheel - Zoom in/out") | ||||||
|  |     print("Middle mouse button - Pan camera") | ||||||
|  |     print("R - Reset camera to origin") | ||||||
|  |     print("ESC or close window - Exit") | ||||||
|  | 
 | ||||||
|  |     running = True | ||||||
|  |     while running: | ||||||
|  |         deltatime = clock.get_time() / 1000.0  # Convert milliseconds to seconds | ||||||
|  | 
 | ||||||
|  |         # Handle events | ||||||
|  |         for event in pygame.event.get(): | ||||||
|  |             if event.type == pygame.QUIT: | ||||||
|  |                 running = False | ||||||
|  |             elif event.type == pygame.KEYDOWN: | ||||||
|  |                 if event.key == pygame.K_ESCAPE: | ||||||
|  |                     running = False | ||||||
|  |                 if event.key == pygame.K_g: | ||||||
|  |                     is_showing_grid = not is_showing_grid | ||||||
|  |             elif event.type == pygame.MOUSEWHEEL: | ||||||
|  |                 camera.handle_zoom(event.y) | ||||||
|  |             elif event.type == pygame.MOUSEBUTTONDOWN: | ||||||
|  |                 if event.button == 2:  # Middle mouse button | ||||||
|  |                     camera.start_panning(event.pos) | ||||||
|  |             elif event.type == pygame.MOUSEBUTTONUP: | ||||||
|  |                 if event.button == 2:  # Middle mouse button | ||||||
|  |                     camera.stop_panning() | ||||||
|  |             elif event.type == pygame.MOUSEMOTION: | ||||||
|  |                 camera.pan(event.pos) | ||||||
|  | 
 | ||||||
|  |         # Get pressed keys for smooth movement | ||||||
|  |         keys = pygame.key.get_pressed() | ||||||
|  |         camera.update(keys, deltatime) | ||||||
|  | 
 | ||||||
|  |         # Tick logic (runs every tick interval) | ||||||
|  |         current_time = time.perf_counter() | ||||||
|  |         while current_time - last_tick_time >= tick_interval: | ||||||
|  |             last_tick_time += tick_interval | ||||||
|  |             tick_counter += 1 | ||||||
|  |             # Add your tick-specific logic here | ||||||
|  |             print("Tick logic executed") | ||||||
|  | 
 | ||||||
|  |         # Calculate TPS every second | ||||||
|  |         if current_time - last_tps_time >= 1.0: | ||||||
|  |             actual_tps = tick_counter | ||||||
|  |             tick_counter = 0 | ||||||
|  |             last_tps_time += 1.0 | ||||||
|  | 
 | ||||||
|  |         # Draw everything | ||||||
|  |         draw_grid(screen, camera, is_showing_grid) | ||||||
|  | 
 | ||||||
|  |         # Render mouse position as text in top left of screen | ||||||
|  |         mouse_x, mouse_y = camera.get_real_coordinates(*pygame.mouse.get_pos()) | ||||||
|  |         mouse_text = font.render(f"Mouse: ({mouse_x}, {mouse_y})", True, WHITE) | ||||||
|  |         text_rect = mouse_text.get_rect() | ||||||
|  |         text_rect.topleft = (10, 10) | ||||||
|  |         screen.blit(mouse_text, text_rect) | ||||||
|  | 
 | ||||||
|  |         # Render FPS in top right | ||||||
|  |         fps_text = font.render(f"FPS: {int(clock.get_fps())}", True, WHITE) | ||||||
|  |         fps_rect = fps_text.get_rect() | ||||||
|  |         fps_rect.topright = (SCREEN_WIDTH - 10, 10) | ||||||
|  |         screen.blit(fps_text, fps_rect) | ||||||
|  | 
 | ||||||
|  |         # Render TPS in bottom right | ||||||
|  |         tps_text = font.render(f"TPS: {actual_tps}", True, WHITE) | ||||||
|  |         tps_rect = tps_text.get_rect() | ||||||
|  |         tps_rect.bottomright = (SCREEN_WIDTH - 10, SCREEN_HEIGHT - 10) | ||||||
|  |         screen.blit(tps_text, tps_rect) | ||||||
|  | 
 | ||||||
|  |         # Update display | ||||||
|  |         pygame.display.flip() | ||||||
|  |         clock.tick(180) | ||||||
|  | 
 | ||||||
|  |     pygame.quit() | ||||||
|  |     sys.exit() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
							
								
								
									
										8
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | [project] | ||||||
|  | name = "dynamicsystemabstraction" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "Add your description here" | ||||||
|  | requires-python = ">=3.11" | ||||||
|  | dependencies = [ | ||||||
|  |     "pygame>=2.6.1", | ||||||
|  | ] | ||||||
							
								
								
									
										2
									
								
								world/world.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								world/world.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | class World: | ||||||
|  |     pass | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sam
						Sam