added raycaster project
This commit is contained in:
390
raycaster.py
Normal file
390
raycaster.py
Normal file
@@ -0,0 +1,390 @@
|
||||
import array
|
||||
import pygame
|
||||
import math
|
||||
from tkinter import *
|
||||
|
||||
# tutorial from https://lodev.org/cgtutor/raycasting.html
|
||||
|
||||
# constants
|
||||
mapWidth = 24
|
||||
mapHeight = 24
|
||||
screenWidth = 900 #640
|
||||
screenHeight = 600 #480
|
||||
|
||||
# world map
|
||||
worldMap = [
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 0, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
]
|
||||
|
||||
# initialize pygame
|
||||
pygame.init()
|
||||
|
||||
# initialize font
|
||||
pygame.font.init()
|
||||
|
||||
# create the screen
|
||||
screen = pygame.display.set_mode((screenWidth, screenHeight))
|
||||
pygame.display.set_caption("Python Raycaster")
|
||||
|
||||
# create game clock
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
def switch(number: int):
|
||||
"""acts like a switch statement. switches the ints of the map with a color value to display.
|
||||
|
||||
Args:
|
||||
|
||||
number: the number to switch on
|
||||
"""
|
||||
switcher = {
|
||||
1: (255, 0, 0), # red
|
||||
2: (0, 255, 0), # green
|
||||
3: (0, 0, 255), # blue
|
||||
4: (255, 255, 255) # white
|
||||
}
|
||||
return switcher.get(number, pygame.Color("#ffff00")) # default is yellow
|
||||
|
||||
def stop():
|
||||
pygame.quit()
|
||||
quit()
|
||||
|
||||
|
||||
|
||||
def display_text(text: str, position: tuple, color: tuple):
|
||||
"""displays text to the screen at the given position.
|
||||
|
||||
Args:
|
||||
|
||||
text: the text to display.
|
||||
position: the position to display the text.
|
||||
color: the color of the text.
|
||||
|
||||
"""
|
||||
fpsFont = pygame.font.SysFont('Consolas',15)
|
||||
screen.blit(fpsFont.render(text, False, color), position)
|
||||
|
||||
def display_text_centered(text: str, position: tuple, color: tuple, size: int, font: str):
|
||||
"""displays text to the screen with the given parameters
|
||||
|
||||
Args:
|
||||
|
||||
text: the text to display
|
||||
position: the position to display the text.
|
||||
color: the color of the text.
|
||||
size: the font size
|
||||
font: the system font to use
|
||||
"""
|
||||
font_obj = pygame.font.SysFont(font, size)
|
||||
rendered = font_obj.render(text, False, color)
|
||||
rect = rendered.get_rect().width
|
||||
screen.blit(rendered, (position[0] - rect // 2,position[1]))
|
||||
return rendered
|
||||
|
||||
def get_text_object(text: str, color: tuple, fontSize: int, font: str):
|
||||
font_obj = pygame.font.SysFont(font, fontSize)
|
||||
return font_obj.render(text, False, color)
|
||||
|
||||
def game_intro():
|
||||
intro = True
|
||||
while intro:
|
||||
screen.fill((0, 0, 0))
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
print("exiting...")
|
||||
intro = False
|
||||
stop()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_SPACE:
|
||||
|
||||
intro = False
|
||||
|
||||
display_text_centered(text="Python raycaster", position=(screenWidth // 2, 100), color=(200, 200, 200), size=30, font='Consolas')
|
||||
display_text_centered(text="Press 'SPACE' to start", position=(screenWidth // 2, 200), color=(200, 40, 40), size=20, font='Consolas')
|
||||
display_text_centered(text="WASD to move, left and right arrow keys to look", position=(screenWidth//2, 250), color=(50, 100, 100), size=16, font='Consolas')
|
||||
|
||||
copy_right_text = get_text_object(text="made by Sem van der Hoeven", color=(100, 100, 100), fontSize=12, font='Consolas')
|
||||
rect = copy_right_text.get_rect()
|
||||
screen.blit(copy_right_text, (0, screenHeight - rect.height))
|
||||
|
||||
|
||||
pygame.display.update()
|
||||
clock.tick(30)
|
||||
|
||||
def game_loop(clock, screen):
|
||||
"""main game loop that runs the game.
|
||||
|
||||
Args:
|
||||
|
||||
clock: The pygame clock object.
|
||||
screen: the pygame screen object.
|
||||
"""
|
||||
|
||||
posX = 22.0 # x start position
|
||||
posY = 12.0 # y start position
|
||||
dirX = -1.0 # initial x of direction vector
|
||||
dirY = 0.0 # initial y of direction vector
|
||||
|
||||
planeX = 0.0 # 2d raycaster version of camera plane x
|
||||
planeY = 0.66 # 2d raycaster version of camera plane y
|
||||
|
||||
running = True
|
||||
rotateLeft = False
|
||||
rotateRight = False
|
||||
moveForward = False
|
||||
moveBackward = False
|
||||
moveLeft = False
|
||||
moveRight = False
|
||||
debugMode = False
|
||||
|
||||
# main game loop
|
||||
print("starting game...")
|
||||
while running:
|
||||
|
||||
# update the next frame at 30 fps
|
||||
ms = clock.tick(60) / 1000.0
|
||||
|
||||
moveSpeed = ms * 3.0
|
||||
rotSpeed = ms * 3.0
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
print("exiting...")
|
||||
del screen
|
||||
del clock
|
||||
running = False
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == ord('a'):
|
||||
moveLeft = True
|
||||
if event.key == ord('d'):
|
||||
moveRight = True
|
||||
if event.key == ord('w'):
|
||||
moveForward = True
|
||||
if event.key == ord('s'):
|
||||
moveBackward = True
|
||||
if event.key == pygame.K_LEFT:
|
||||
rotateLeft = True
|
||||
if event.key == pygame.K_RIGHT:
|
||||
rotateRight = True
|
||||
if event.key == pygame.K_TAB:
|
||||
debugMode = not debugMode
|
||||
print("setting debug mode to" ,debugMode)
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == ord('a'):
|
||||
moveLeft = False
|
||||
if event.key == ord('d'):
|
||||
moveRight = False
|
||||
if event.key == ord('w'):
|
||||
moveForward = False
|
||||
if event.key == ord('s'):
|
||||
moveBackward = False
|
||||
if event.key == pygame.K_LEFT:
|
||||
rotateLeft = False
|
||||
if event.key == pygame.K_RIGHT:
|
||||
rotateRight = False
|
||||
if running == False:
|
||||
continue # if the user has pressed the quit key, stop the loop
|
||||
|
||||
# loop that goes over every x (vertical line)
|
||||
screen.fill((0, 0, 0))
|
||||
for x in range(screenWidth):
|
||||
|
||||
# calculate camera x, the x coordinate on the camera plane that the current
|
||||
# x-coordinate represents.
|
||||
# This way, the right side of the screen will get coordinate 1, center will get coordinate 0,
|
||||
# and left will get coordinate -1
|
||||
cameraX = 2 * x / float(screenWidth) - 1
|
||||
rayDirX = dirX + planeX * cameraX
|
||||
rayDirY = dirY + planeY * cameraX
|
||||
|
||||
# which box of the map we're in
|
||||
mapX = int(posX)
|
||||
mapY = int(posY)
|
||||
|
||||
# length of the ray from current position to next x or y side
|
||||
sideDistX = 0.0 # (double)
|
||||
sideDistY = 0.0 # (double)
|
||||
|
||||
# length of ray from one x or y-side to next x or y-side
|
||||
|
||||
# rayDirX and rayDirY can be 0, so then division by 0 would occur if
|
||||
# we did only abs(1/rayDirX), so we need to check if it is 0
|
||||
deltaDistX = 0 if rayDirY == 0 else (
|
||||
1 if rayDirX == 0 else abs(1/rayDirX))
|
||||
deltaDistY = 0 if rayDirX == 0 else (
|
||||
1 if rayDirY == 0 else abs(1/rayDirY))
|
||||
|
||||
perpWallDist = 0.0 # used to calculate the length of the ray (double)
|
||||
|
||||
# what direction to step in x or y-direction (either +1 or -1 for positive or negative direction)
|
||||
# if the ray direction has a negative x-component, stepX will be -1. If it has a positive x-component,
|
||||
# it will be +1. If the x-component is 0 the value of stepX won't matter because it won't be used.
|
||||
# same for stepY
|
||||
stepX = 0 # (int)
|
||||
stepY = 0 # (int)
|
||||
|
||||
hit = 0 # was there a wall hit?
|
||||
side = 0 # (int) was a vertical or horizontal wall hit?
|
||||
|
||||
# calculate step and initial sideDist
|
||||
# is the x-component of the ray is negative, sideDistX is the distance to the first vertical line to the left.
|
||||
if rayDirX < 0:
|
||||
# if the x-component of the ray is positive, sideDistX is the distance to the first vertical line to the richt.
|
||||
# same for the y-component, but then to the first horizontal line to the top or bottom
|
||||
stepX = -1
|
||||
sideDistX = (posX - mapX) * deltaDistX
|
||||
else:
|
||||
stepX = 1
|
||||
sideDistX = (mapX + 1.0 - posX) * deltaDistX
|
||||
|
||||
if rayDirY < 0:
|
||||
stepY = -1
|
||||
sideDistY = (posY - mapY) * deltaDistY
|
||||
else:
|
||||
stepY = 1
|
||||
sideDistY = (mapY + 1.0 - posY) * deltaDistY
|
||||
|
||||
# actual DDA algorithm
|
||||
|
||||
while hit == 0: # while the ray has not hit a wall
|
||||
# jump to the next map square, OR in x direction OR in y direction
|
||||
if (sideDistX < sideDistY): # the ray is going morea horizontal than vertical
|
||||
sideDistX += deltaDistX
|
||||
mapX += stepX
|
||||
side = 0
|
||||
else:
|
||||
sideDistY += deltaDistY
|
||||
mapY += stepY
|
||||
side = 1
|
||||
# check if the ray has hit a wall
|
||||
# if it is not 0, so the square does not contain a walkable square, so a wall
|
||||
if worldMap[mapX][mapY] > 0:
|
||||
hit = 1
|
||||
|
||||
# calculate the distance projected on camera direction (Euclidean distance will give fisheye effect!)
|
||||
if side == 0: # vertical wall hit
|
||||
perpWallDist = (mapX - posX + (1 - stepX) / 2) / rayDirX
|
||||
else: # horizontal wall hit
|
||||
perpWallDist = (mapY - posY + (1 - stepY) / 2) / rayDirY
|
||||
|
||||
# calculate the height of the line to draw on the screen
|
||||
if (perpWallDist != 0):
|
||||
lineHeight = int(screenHeight / perpWallDist)
|
||||
|
||||
# calculate the lowest and highest pixel to fill in current vertical stripe
|
||||
drawStart = -lineHeight / 2 + screenHeight / 2
|
||||
if drawStart < 0:
|
||||
drawStart = 0
|
||||
drawEnd = lineHeight / 2 + screenHeight / 2
|
||||
if drawEnd >= screenHeight:
|
||||
drawEnd = screenHeight - 1
|
||||
|
||||
color = switch(worldMap[mapX][mapY])
|
||||
|
||||
if side == 1:
|
||||
color = (color[0] / 2, color[1] / 2, color[2] / 2)
|
||||
|
||||
pygame.draw.line(screen, color, (x, int(drawStart)),
|
||||
(x, int(drawEnd)), 1)
|
||||
|
||||
if moveForward:
|
||||
movePosX = int(posX + dirX * moveSpeed) # x position to move to next
|
||||
movePosY = int(posY + dirY * moveSpeed) # y position to move to next
|
||||
|
||||
if worldMap[movePosX][movePosY] == 0:
|
||||
posX += dirX * moveSpeed
|
||||
posY += dirY * moveSpeed
|
||||
|
||||
if moveBackward:
|
||||
movePosX = int(posX - dirX * moveSpeed)
|
||||
movePosY = int(posY - dirY * moveSpeed)
|
||||
|
||||
if worldMap[movePosX][movePosY] == 0:
|
||||
posX -= dirX * moveSpeed
|
||||
posY -= dirY * moveSpeed
|
||||
|
||||
if moveLeft:
|
||||
oldDirX = dirX
|
||||
oldDirY = dirY
|
||||
dirX = dirX * math.cos(math.pi/2) - dirY * math.sin(math.pi/2)
|
||||
dirY = oldDirX * math.sin(math.pi/2) + dirY * math.cos(math.pi/2)
|
||||
|
||||
movePosX = int(posX + dirX * moveSpeed) # x position to move to next
|
||||
movePosY = int(posY + dirY * moveSpeed) # y position to move to next
|
||||
if worldMap[movePosX][movePosY] == 0:
|
||||
posX += dirX * moveSpeed
|
||||
posY += dirY * moveSpeed
|
||||
dirX = oldDirX
|
||||
dirY = oldDirY
|
||||
|
||||
if moveRight:
|
||||
oldDirX = dirX
|
||||
oldDirY = dirY
|
||||
dirX = dirX * math.cos(-math.pi/2) - dirY * math.sin(-math.pi/2) # rotate 90 degrees to the right
|
||||
dirY = oldDirX * math.sin(-math.pi/2) + dirY * math.cos(-math.pi/2) # so change the direction to 90 degrees
|
||||
|
||||
# apply the move, so move to the right
|
||||
movePosX = int(posX + dirX * moveSpeed) # x position to move to next
|
||||
movePosY = int(posY + dirY * moveSpeed) # y position to move to next
|
||||
if worldMap[movePosX][movePosY] == 0:
|
||||
posX += dirX * moveSpeed
|
||||
posY += dirY * moveSpeed
|
||||
# reset the direction vector because we still want to look forward
|
||||
dirX = oldDirX
|
||||
dirY = oldDirY
|
||||
|
||||
if rotateRight:
|
||||
oldDirX = dirX
|
||||
dirX = dirX * math.cos(-rotSpeed) - dirY * math.sin(-rotSpeed)
|
||||
dirY = oldDirX * math.sin(-rotSpeed) + dirY * math.cos(-rotSpeed)
|
||||
oldPlaneX = planeX
|
||||
planeX = planeX * math.cos(-rotSpeed) - planeY * math.sin(-rotSpeed)
|
||||
planeY = oldPlaneX * math.sin(-rotSpeed) + planeY * math.cos(-rotSpeed)
|
||||
|
||||
if rotateLeft:
|
||||
oldDirX = dirX
|
||||
dirX = dirX * math.cos(rotSpeed) - dirY * math.sin(rotSpeed)
|
||||
dirY = oldDirX * math.sin(rotSpeed) + dirY * math.cos(rotSpeed)
|
||||
oldPlaneX = planeX
|
||||
planeX = planeX * math.cos(rotSpeed) - planeY * math.sin(rotSpeed)
|
||||
planeY = oldPlaneX * math.sin(rotSpeed) + planeY * math.cos(rotSpeed)
|
||||
|
||||
|
||||
textToRender = "FPS: " + str(int(1 // ms))
|
||||
if debugMode: textToRender = textToRender + " posX: " + str(int(
|
||||
posX)) + " posY: " + str(int(
|
||||
posY)) + " dirX: " + str(dirX) + " dirY: " + str(dirY)
|
||||
|
||||
display_text(textToRender, (0, 0), (0, 255, 255))
|
||||
copy_right_text = get_text_object(text="press TAB for debug info", color=(100, 100, 100), fontSize=12, font='Consolas')
|
||||
rect = copy_right_text.get_rect()
|
||||
screen.blit(copy_right_text, (0, screenHeight - rect.height))
|
||||
|
||||
# uodate the display
|
||||
pygame.display.update()
|
||||
|
||||
game_intro()
|
||||
game_loop(clock,screen)
|
||||
stop()
|
||||
Reference in New Issue
Block a user