我正在尝试使用 tkinter 创建一个类似末日的应用程序,但我的墙壁没有正确的颜色。这是我的代码:
执行该工作的代码的一部分:
color = "#D3D3D3" # Default wall color is light gray
if hit:
for wx, wy, width, height, wall_color in self.map_data:
if wx <= target_x <= wx + width and wy <= target_y <= wy + height:
color = wall_color
break
代码的功能:
def draw_3d_view(self):
"""Draw the 3D view on the main canvas."""
self.main_canvas.delete("all")
fov = math.pi / 3 # Field of view (60 degrees)
num_rays = int(self.main_canvas['width']) # Number of rays should match canvas width
max_depth = 800
half_fov = fov / 2
angle_step = fov / num_rays
screen_width = int(self.main_canvas['width'])
screen_height = int(self.main_canvas['height'])
ray_angle = self.player.angle - half_fov
for ray in range(num_rays):
depth = 0
hit = False
# Cast ray
for depth in range(1, max_depth):
target_x = self.player.x + depth * math.cos(ray_angle)
target_y = self.player.y + depth * math.sin(ray_angle)
if self.check_collision(target_x, target_y):
hit = True
break
if not hit:
depth = max_depth # If no hit, set depth to max depth for drawing faraway wall
# Calculate wall height based on depth, with a scaling factor to make walls appear taller
scaling_factor = 10 # Adjust this factor to make walls taller or shorter
wall_height = (screen_height / (depth * math.cos(ray_angle - self.player.angle) + 0.0001)) * scaling_factor
start_y = screen_height / 2 - wall_height / 2
end_y = screen_height / 2 + wall_height / 2
# Determine wall color
color = "#D3D3D3" # Default wall color is light gray
if hit:
for wx, wy, width, height, wall_color in self.map_data:
if wx <= target_x <= wx + width and wy <= target_y <= wy + height:
color = wall_color
break
# Draw the vertical line for this ray
self.main_canvas.create_line(ray, start_y, ray, end_y, fill=color)
# Move to the next ray angle
ray_angle += angle_step
# Ensure the view is updated
self.update_idletasks()
完整代码:
import tkinter as tk
import gui
import math
class Player:
def __init__(self, x=0, y=0, size=10, angle=0):
self.x = x
self.y = y
self.speed = 10
self.size = size
self.color = "#000000"
self.angle = angle # Direction the player is facing
def update_angle(self):
if self.angle > 360:
self.angle = 0
if self.angle < 0:
self.angle = 360
class Doom(tk.Tk):
def __init__(self):
super().__init__()
self.title("Doom Main Window")
self.geometry("1280x720")
self.attributes("-fullscreen", True)
self.bind("<F11>", self.toggle_fullscreen) # Press F11 to toggle fullscreen
# Create the main window canvas
self.main_canvas = gui.create_canvas(self)
self.main_drawer = gui.TkDrawUtils(self.main_canvas)
# Create the map view window
self.map_window = tk.Toplevel(self)
self.map_window.title("Doom Map View")
self.map_window.geometry("640x365")
self.map_canvas = gui.create_canvas(self.map_window)
self.map_drawer = gui.TkDrawUtils(self.map_canvas)
# Initialize player position
self.player = Player(x=50, y=50) # Set initial player position
self.player_speed = self.player.speed
# Example map data
self.map_data = [
# (x, y, width, height, color)
# Rooms
(10, 10, 200, 120, "#FF0000"), # Red Room
(240, 10, 200, 120, "#00FF00"), # Green Room
(460, 10, 140, 120, "#FFC0CB"), # Pink Room
(10, 160, 120, 200, "#0000FF"), # Blue Room
(160, 160, 120, 200, "#FFFF00"), # Yellow Room
(300, 160, 140, 200, "#FF00FF"), # Magenta Room
(460, 160, 140, 200, "#00FFFF"), # Cyan Room
# Corridors
(120, 60, 120, 20, "#FF0000"), # Red Corridor
(360, 60, 20, 100, "#00FF00"), # Green Corridor
(440, 80, 20, 20, "#FFC0CB"), # Pink Corridor
(120, 180, 40, 20, "#0000FF"), # Blue Corridor
(280, 280, 20, 20, "#FFFF00"), # Yellow Corridor
(440, 200, 20, 20, "#FF00FF"), # Magenta Corridor
(540, 130, 20, 40, "#00FFFF") # Cyan Corridor
]
# Draw the map and player
self.draw_map()
self.refresh()
self.update_view()
# Bind keys to movement functions
self.bind_keys()
def bind_keys(self):
self.bind("<w>", self.move_up)
self.bind("<s>", self.move_down)
self.bind("<a>", self.move_left)
self.bind("<d>", self.move_right)
self.map_window.bind("<w>", self.move_up)
self.map_window.bind("<s>", self.move_down)
self.map_window.bind("<a>", self.move_left)
self.map_window.bind("<d>", self.move_right)
self.bind("<Left>", self.angle_left)
self.bind("<Right>", self.angle_right)
self.map_window.bind("<Left>", self.angle_left)
self.map_window.bind("<Right>", self.angle_right)
def angle_left(self, event=None):
self.player.angle -= 1
self.player.update_angle() # Ensure angle is within valid range
self.refresh()
def angle_right(self, event=None):
self.player.angle += 1
self.player.update_angle() # Ensure angle is within valid range
self.refresh()
def move_up(self, event=None):
"""Move the player forward in the direction they are facing."""
# Calculate new position based on the player's angle
new_x = self.player.x + self.player_speed * math.cos(math.radians(self.player.angle))
new_y = self.player.y + self.player_speed * math.sin(math.radians(self.player.angle))
if not self.check_collision(new_x, new_y):
self.player.x = new_x
self.player.y = new_y
self.update_view()
def move_down(self, event=None):
"""Move the player backward from the direction they are facing."""
# Calculate new position based on the player's angle
new_x = self.player.x - self.player_speed * math.cos(math.radians(self.player.angle))
new_y = self.player.y - self.player_speed * math.sin(math.radians(self.player.angle))
if not self.check_collision(new_x, new_y):
self.player.x = new_x
self.player.y = new_y
self.update_view()
def move_left(self, event=None):
"""Move the player left relative to their facing direction."""
# Calculate new position perpendicular to the player's angle (left direction)
angle_rad = math.radians(self.player.angle)
new_x = self.player.x + self.player_speed * math.sin(angle_rad)
new_y = self.player.y - self.player_speed * math.cos(angle_rad)
if not self.check_collision(new_x, new_y):
self.player.x = new_x
self.player.y = new_y
self.update_view()
def move_right(self, event=None):
"""Move the player right relative to their facing direction."""
# Calculate new position perpendicular to the player's angle (right direction)
angle_rad = math.radians(self.player.angle)
new_x = self.player.x - self.player_speed * math.sin(angle_rad)
new_y = self.player.y + self.player_speed * math.cos(angle_rad)
if not self.check_collision(new_x, new_y):
self.player.x = new_x
self.player.y = new_y
self.update_view()
def update_view(self):
"""Update both the 2D map and the 3D view."""
self.draw_map()
self.draw_3d_view()
self.update()
self.update_idletasks()
def check_collision(self, x, y):
"""Check if the player collides with any walls or corridors."""
player_size = self.player.size
player_rect = (x, y, x + player_size, y + player_size) # Define player rectangle
for (wx, wy, width, height, color) in self.map_data:
wall_rect = (wx, wy, wx + width, wy + height) # Define wall rectangle
# Check if rectangles overlap
if (player_rect[0] < wall_rect[2] and player_rect[2] > wall_rect[0] and
player_rect[1] < wall_rect[3] and player_rect[3] > wall_rect[1]):
return False
return True
def draw_map(self):
"""Draw walls and player on the map canvas based on the map data."""
# Clear previous drawings
self.map_canvas.delete("all")
# Draw walls
for (x, y, width, height, color) in self.map_data:
self.map_drawer.draw_filled_rectangle(x, y, width, height, color)
# Draw player
player_size = self.player.size
player_color = self.player.color
self.map_drawer.draw_filled_rectangle(self.player.x, self.player.y, player_size, player_size, player_color)
def draw_3d_view(self):
"""Draw the 3D view on the main canvas."""
self.main_canvas.delete("all")
fov = math.pi / 3 # Field of view (60 degrees)
num_rays = int(self.main_canvas['width']) # Number of rays should match canvas width
max_depth = 800
half_fov = fov / 2
angle_step = fov / num_rays
screen_width = int(self.main_canvas['width'])
screen_height = int(self.main_canvas['height'])
ray_angle = self.player.angle - half_fov
for ray in range(num_rays):
depth = 0
hit = False
# Cast ray
for depth in range(1, max_depth):
target_x = self.player.x + depth * math.cos(ray_angle)
target_y = self.player.y + depth * math.sin(ray_angle)
if self.check_collision(target_x, target_y):
hit = True
break
if not hit:
depth = max_depth # If no hit, set depth to max depth for drawing faraway wall
# Calculate wall height based on depth, with a scaling factor to make walls appear taller
scaling_factor = 10 # Adjust this factor to make walls taller or shorter
wall_height = (screen_height / (depth * math.cos(ray_angle - self.player.angle) + 0.0001)) * scaling_factor
start_y = screen_height / 2 - wall_height / 2
end_y = screen_height / 2 + wall_height / 2
# Determine wall color
color = "#D3D3D3" # Default wall color is light gray
if hit:
for wx, wy, width, height, wall_color in self.map_data:
if wx <= target_x <= wx + width and wy <= target_y <= wy + height:
color = wall_color
break
# Draw the vertical line for this ray
self.main_canvas.create_line(ray, start_y, ray, end_y, fill=color)
# Move to the next ray angle
ray_angle += angle_step
# Ensure the view is updated
self.update_idletasks()
def toggle_fullscreen(self, event=None):
"""Toggle fullscreen mode."""
current_state = self.attributes("-fullscreen")
self.attributes("-fullscreen", not current_state)
if not current_state:
self.update_idletasks()
def refresh(self):
self.player.update_angle()
self.update_idletasks()
self.update()
self.update_view()
if __name__ == '__main__':
app = Doom()
app.mainloop()
gui.py:
import tkinter as tk
from PIL import Image, ImageTk
class TkDrawUtils:
def __init__(self, canvas):
self.canvas = canvas
def draw_line(self, x1, y1, x2, y2, color='black'):
"""Draw a line on the canvas from (x1, y1) to (x2, y2) with the specified color."""
self.canvas.create_line(x1, y1, x2, y2, fill=color)
def draw_rectangle(self, x, y, width, height, color='black'):
"""Draw a rectangle on the canvas with the specified color."""
self.canvas.create_rectangle(x, y, x + width, y + height, outline=color, fill=color)
def draw_filled_rectangle(self, x, y, width, height, color='black'):
"""Draw a filled rectangle on the canvas with the specified color."""
self.canvas.create_rectangle(x, y, x + width, y + height, fill=color, outline='')
def draw_image(self, x, y, image_path):
"""Draw an image at the specified location on the canvas."""
image = Image.open(image_path).convert("RGBA")
self.image_tk = ImageTk.PhotoImage(image)
self.canvas.create_image(x, y, image=self.image_tk, anchor='nw')
def draw_polygon(self, points, fill_color=None, outline_color='black'):
"""
Draw a polygon connecting the given points.
:param points: List of (x, y) tuples representing the vertices of the polygon.
:param fill_color: Color to fill the polygon. If None, the polygon is not filled.
:param outline_color: Color of the polygon's outline.
"""
self.canvas.create_polygon(points, fill=fill_color, outline=outline_color)
def draw_shapes_from_map(self, map_obj, cell_size):
"""
Draw shapes based on the provided map object.
The map object should be a list of lists where each cell describes a shape:
- 'R': Rectangle
- 'F': Filled Rectangle
- 'L': Line (followed by coordinates and color)
- 'I': Image (followed by image path)
- 'P': Polygon (followed by points and optional fill color)
"""
for row_index, row in enumerate(map_obj):
for col_index, cell in enumerate(row):
x = col_index * cell_size
y = row_index * cell_size
if cell.startswith('R'):
self.draw_rectangle(x, y, cell_size, cell_size, 'blue')
elif cell.startswith('F'):
self.draw_filled_rectangle(x, y, cell_size, cell_size, 'green')
elif cell.startswith('L'):
_, x2, y2, color = cell.split()
x2 = float(x2)
y2 = float(y2)
self.draw_line(x, y, x2, y2, color)
elif cell.startswith('I'):
_, image_path = cell.split()
self.draw_image(x, y, image_path)
elif cell.startswith('P'):
parts = cell.split()
points = [(float(parts[i]), float(parts[i + 1])) for i in range(1, len(parts) - 1, 2)]
fill_color = parts[-1] if len(parts) % 2 == 0 else None
self.draw_polygon(points, fill_color)
def create_canvas(parent):
"""Create a fullscreen Tkinter canvas for the parent window."""
# Get the screen width and height
screen_width = parent.winfo_screenwidth()
screen_height = parent.winfo_screenheight()
canvas = tk.Canvas(parent, width=screen_width, height=screen_height, bg='white')
canvas.pack(fill=tk.BOTH, expand=True)
# Make the canvas resize with the window
canvas.bind("<Configure>", lambda e: canvas.config(width=parent.winfo_width(), height=parent.winfo_height()))
return canvas
我尝试过 chatGPT 但那个机器人只是愚蠢的。我尝试了你所在房间的颜色,但看起来不正确。
问题在于正在检查碰撞 在 更新光线颜色。这意味着如果光线与墙壁碰撞,实际上是在检查光线 下一帧 位置的颜色,而不是它当前击中的墙壁。
以下是解决此问题的步骤:
-
在
draw_3d_view
函数中,将碰撞检查和颜色确定代码移到循环的绘制部分 。这样,将在同一位置检查碰撞并确定颜色。 -
删除不必要的
hit
变量 ,因为我们现在可以在循环中直接使用碰撞信息。
这是更新后的
draw_3d_view
函数:
def draw_3d_view(self):
"""在主画布上绘制 3D 视图。"""
self.main_canvas.delete("all")
fov = math.pi / 3 # 视野(60 度)
num_rays = int(self.main_canvas['width']) # 光线数量应与画布宽度匹配
max_depth = 800
half_fov = fov / 2
angle_step = fov / num_rays
screen_width = int(self.main_canvas['width'])
screen_height = int(self.main_canvas['height'])
ray_angle = self.player.angle - half_fov
for ray in range(num_rays):
depth = 0
# 投射光线
for depth in range(1, max_depth):
target_x = self.player.x + depth * math.cos(ray_angle)
target_y = self.player.y + depth * math.sin(ray_angle)
if self.check_collision(target_x, target_y):
# 计算基于深度的墙壁高度,使用缩放因子使墙壁看起来更高
scaling_factor = 10 # 调整此因子以使墙壁更高或更矮
wall_height = (screen_height / (depth * math.cos(ray_angle - self.player.angle) + 0.0001)) * scaling_factor
start_y = screen_height / 2 - wall_height / 2
end_y = screen_height / 2 + wall_height / 2
# 确定墙壁颜色
color = "#D3D3D3" # 默认墙壁颜色为浅灰色
for wx, wy, width, height, wall_color in self.map_data:
if wx <= target_x <= wx + width and wy <= target_y <= wy + height:
color = wall_color
break
# 为此光线绘制垂直线
self.main_canvas.create_line(ray, start_y, ray, end_y, fill=color)
break
# 如果没有命中,则将深度设置为最大深度以绘制远处的墙壁
if depth == max_depth - 1:
scaling_factor = 10
wall_height = (screen_height / (depth * math.cos(ray_angle - self.player.angle) + 0.0001)) * scaling_factor
start_y = screen_height / 2 - wall_height / 2
end_y = screen_height / 2 + wall_height / 2
self.main_canvas.create_line(ray, start_y, ray, end_y, fill=color)
# 移动到下一个光线角度
ray_angle += angle_step
# 确保视图已更新
self.update_idletasks()
通过此更改,的光线投射引擎现在应该可以正确地为墙壁着色。
标签:python,tkinter From: 78808895