示例#1
0
class Game:
    """
    Represents state of the game. Initializes pygame and all necessary variables and
    constants on object creation.
    """
    def __init__(self, level, windowSize, totalRays, fovDegrees, targetFps):
        self.level = level
        self.windowSize = windowSize
        self.fovDegrees = fovDegrees
        self.targetFps = targetFps

        self.moveSpeed = NORMALIZED_MOVE * (60 / targetFps)
        self.turnSpeed = NORMALIZED_TURN * (60 / targetFps) * (totalRays / 600)
        self.cataclysmSpeed = NORMALIZED_CATACLYSM * 0.00001 * (60 / targetFps)

        self.raycasting = None
        self.screen = None
        self.font = None
        self.minimap = None
        self.fovRays = None
        self.pixelsPerRay = None
        self.distanceToProjection = None
        self.player = None
        self.winScreen = None

        # Initialize raycasting logic
        self.raycasting = Raycasting(totalRays, BLOCK_SIZE, self.level)

        # Initialize pygame
        pygame.init()
        pygame.display.set_caption("Raycasting labyrint")
        self.screen = pygame.display.set_mode(self.windowSize)

        # Prepare font rendering
        pygame.font.init()
        self.font = pygame.font.SysFont("Sans Serif", 30)

        # Prepare minimap
        minimapSize = self.level.get_size() * BLOCK_SIZE // MINIMAP_SIZE_DIV
        self.minimap = pygame.Surface((minimapSize.x, minimapSize.y))

        # Compute fov related stuff
        self.fovRays = self.raycasting.degrees_to_ray_number(self.fovDegrees)
        self.pixelsPerRay = windowSize[0] // (totalRays //
                                              (360 // self.fovDegrees))

        # Compute distance between player and the projection plane (also fov stuff)
        self.distanceToProjection = int(
            (PROJECTION_WIDTH) / (tan(radians(self.fovDegrees / 2))))

        # Prepare fisheye correction
        self.fisheyeCoefficients = self.raycasting.fisheye_coefficients(
            self.fovDegrees, self.fovRays)

        # Create player object
        startPos = self.level.get_start_block() * BLOCK_SIZE
        startPos[0] = startPos.x + (BLOCK_SIZE // 2)  # Center vertically
        startPos[1] = startPos.y + (BLOCK_SIZE // 2)  # Center horizontally
        self.player = Player(startPos, 0, self.raycasting, self.fovRays)

        # Create win screen
        self.winScreen = pygame.Surface(windowSize, flags=pygame.SRCALPHA)
        self.winScreen.fill(
            (CEIL_COLOR[0], CEIL_COLOR[1], CEIL_COLOR[2], WIN_SCREEN_OPACITY))
        youWon = self.font.render("You won!", False, TEXT_COLOR)
        pressQ = self.font.render("Press 'Q' to quit.", False, TEXT_COLOR)
        self.winScreen.blit(youWon, (8, 8))
        self.winScreen.blit(pressQ, (8, 40))

    def run(self):
        """
        Main game loop.
        """

        clock = pygame.time.Clock()
        keepGoing = True

        playerHasMoved = False
        timerOn = False
        timer = 0.0

        win = False  # Set to True when player reaches the flag
        drawMinimap = False

        cataclysm = 0.0  # Win screen animation time
        cataclysmedRays = [0] * self.raycasting.get_total_rays()

        while keepGoing:
            #
            # Events and user input
            #

            # Handle events
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    keepGoing = False
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:  # If 'q' was pressed down
                        # Quit game
                        keepGoing = False
                    if event.key == pygame.K_m:  # If 'm' was pressed down
                        # Toggle minimap
                        drawMinimap = not drawMinimap

            # Player and camera movement
            pressedKeys = pygame.key.get_pressed()

            self.moveSpeed *= 1.0 - cataclysm

            if pressedKeys[pygame.K_w]:
                self.player.move_forward(self.moveSpeed)

                if not playerHasMoved:
                    playerHasMoved = True
                    timerOn = True
            if pressedKeys[pygame.K_s]:
                self.player.move_backward(self.moveSpeed)

                if not playerHasMoved:
                    playerHasMoved = True
                    timerOn = True
            if pressedKeys[pygame.K_a]:
                self.player.move_left(self.moveSpeed)

                if not playerHasMoved:
                    playerHasMoved = True
                    timerOn = True
            if pressedKeys[pygame.K_d]:
                self.player.move_right(self.moveSpeed)

                if not playerHasMoved:
                    playerHasMoved = True
                    timerOn = True

            if pressedKeys[pygame.K_j]:
                self.player.turn(-self.turnSpeed)
            if pressedKeys[pygame.K_l]:
                self.player.turn(self.turnSpeed)

            #
            # Win stuff
            #

            # Check if flag was reached
            if (not win) and self.player.get_pos(
            ) // BLOCK_SIZE == self.level.get_flag_block():
                win = True
                timerOn = False

                self.moveSpeed /= 2.0
                self.turnSpeed //= 2

            # Animate cataclysm
            i = 0
            while i < len(cataclysmedRays):
                if cataclysmedRays[i] < 3:
                    if random() < cataclysm:
                        cataclysmedRays[i] = 1
                    if random() < cataclysm:
                        cataclysmedRays[i] = 2
                    if random() < cataclysm:
                        cataclysmedRays[i] = 3
                i += 1

            if win:
                if cataclysm < 1.0:
                    cataclysm += self.cataclysmSpeed
                else:
                    cataclysm = 1.0

            #
            # Rendering
            #

            # Draw floor and ceiling
            self.screen.fill(FLOOR_COLOR)
            ceilRect = pygame.Rect(0, 0, self.windowSize[0],
                                   self.windowSize[1] // 2)
            pygame.draw.rect(self.screen, CEIL_COLOR, ceilRect)

            # Render walls
            distances, intersections, flagDistances = self.raycasting.cast_rays(  # Cast rays
                self.player.get_left_ray(),
                self.player.get_right_ray(),
                self.player.get_pos(),
                messUpRays=cataclysmedRays)
            for i, distance in enumerate(distances):
                if not distance is None:
                    #
                    # Draw a column coresponding to each cast ray
                    #

                    currPixel = i * self.pixelsPerRay

                    # Compute height of the column
                    if -1.0 < distance < 1.0:
                        height = self.windowSize[1]
                    else:
                        height = self.windowSize[
                            1] / distance * self.distanceToProjection

                    # Apply fisheye correction
                    height *= self.fisheyeCoefficients[i]

                    # Compute color of the column
                    colorCoeficient = distance / RENDER_DISTANCE
                    if colorCoeficient > 1.0:
                        colorCoeficient = 1.0
                    red = WALL_COLOR[0] * (1.0 - colorCoeficient) + \
                          CEIL_COLOR[0] * colorCoeficient
                    green = WALL_COLOR[1] * (1.0 - colorCoeficient) + \
                            CEIL_COLOR[1] * colorCoeficient
                    blue = WALL_COLOR[2] * (1.0 - colorCoeficient) + \
                           CEIL_COLOR[2] * colorCoeficient
                    color = (red, green, blue)

                    # Draw the column
                    column = pygame.Rect(
                        currPixel, self.windowSize[1] // 2 - (height // 2),
                        self.pixelsPerRay, height)
                    pygame.draw.rect(self.screen, color, column)

            # Render flag
            for i, distance in enumerate(flagDistances):
                if not distance is None:
                    currPixel = i * self.pixelsPerRay

                    # Compute height of the column
                    if -0.1 < distance < 0.1:
                        height = self.windowSize[1]
                    else:
                        height = self.windowSize[
                            1] / distance * self.distanceToProjection / FLAG_HEIGHT_DIV

                    # Apply fisheye correction
                    height *= self.fisheyeCoefficients[i]

                    # Compute color of the column
                    colorCoeficient = distance / RENDER_DISTANCE
                    if colorCoeficient > 1.0:
                        colorCoeficient = 1.0
                    red = FLAG_COLOR[0] * (1.0 - colorCoeficient) + \
                          CEIL_COLOR[0] * colorCoeficient
                    green = FLAG_COLOR[1] * (1.0 - colorCoeficient) + \
                            CEIL_COLOR[1] * colorCoeficient
                    blue = FLAG_COLOR[2] * (1.0 - colorCoeficient) + \
                           CEIL_COLOR[2] * colorCoeficient
                    color = (red, green, blue)

                    # Draw the column
                    column = pygame.Rect(
                        currPixel, self.windowSize[1] // 2 - (height // 2),
                        self.pixelsPerRay, height)
                    pygame.draw.rect(self.screen, color, column)

            #
            # Draw UI
            #

            # Minimap
            if drawMinimap:
                # Draw walls on minimap
                self.minimap.fill(FLOOR_COLOR)
                for x, wall_column in enumerate(self.level.get_walls()):
                    for y, wall in enumerate(wall_column):
                        if wall:
                            rectSize = BLOCK_SIZE // MINIMAP_SIZE_DIV
                            rectX = x * rectSize
                            rectY = y * rectSize
                            rect = pygame.Rect(rectX, rectY, rectSize,
                                               rectSize)
                            pygame.draw.rect(self.minimap, WALL_COLOR, rect)

                # Draw flag on minimap
                x, y = self.level.get_flag_block()
                rectSize = BLOCK_SIZE // MINIMAP_SIZE_DIV
                rectX = x * rectSize
                rectY = y * rectSize
                rect = pygame.Rect(rectX, rectY, rectSize, rectSize)
                pygame.draw.rect(self.minimap, FLAG_COLOR, rect)

                # Draw player on minimap
                rectX = self.player.get_pos().x // MINIMAP_SIZE_DIV
                rectY = self.player.get_pos().y // MINIMAP_SIZE_DIV
                rectX -= MINIMAP_PLAYER_SIZE // 2
                rectY -= MINIMAP_PLAYER_SIZE // 2
                rect = pygame.Rect(rectX, rectY, MINIMAP_PLAYER_SIZE,
                                   MINIMAP_PLAYER_SIZE)
                pygame.draw.rect(self.minimap, MINIMAP_COLOR, rect)

                # Draw intersections (of rays that have been cast) on minimap
                for intersection in intersections:
                    if not intersection is None:
                        rectX = intersection.x // MINIMAP_SIZE_DIV
                        rectY = intersection.y // MINIMAP_SIZE_DIV
                        rect = pygame.Rect(rectX, rectY, 1, 1)
                        pygame.draw.rect(self.minimap, MINIMAP_COLOR, rect)

                # Blit minimap onto window
                self.screen.blit(self.minimap, (0, 0))

            # Fps
            fpsCount = ceil(clock.get_fps())
            fpsText = "fps: %d" % (fpsCount)
            color = TEXT_COLOR if fpsCount > (self.targetFps -
                                              10) else MINIMAP_COLOR
            fpsSurface = self.font.render(fpsText, False, color)
            self.screen.blit(fpsSurface,
                             (self.windowSize[0] - (7 * 10) - 8, 8))

            # Win screen
            if win:
                self.screen.blit(self.winScreen, (0, 0))

            # Timer
            timeText = "time: %.2f s" % (timer / 1000)
            timeSurface = self.font.render(timeText, False, TEXT_COLOR)
            self.screen.blit(timeSurface,
                             (self.windowSize[0] -
                              (12 * 10) - 8, self.windowSize[1] - 16 - 8))

            #
            # Finish drawing
            #

            pygame.display.flip()

            #
            # Time
            #

            if timerOn:
                timer += clock.get_time()

            clock.tick(self.targetFps)