Advertisement
Guest User

Minecraft Pyglet Example

a guest
Nov 23rd, 2013
429
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 29.97 KB | None | 0 0
  1. import math
  2. import random
  3. import time
  4.  
  5. from collections import deque
  6. from pyglet import image
  7. from pyglet.gl import *
  8. from pyglet.graphics import TextureGroup
  9. from pyglet.window import key, mouse
  10.  
  11. TICKS_PER_SEC = 60
  12.  
  13. # Size of sectors used to ease block loading.
  14. SECTOR_SIZE = 16
  15.  
  16. WALKING_SPEED = 5
  17. FLYING_SPEED = 15
  18.  
  19. GRAVITY = 20.0
  20. MAX_JUMP_HEIGHT = 1.0 # About the height of a block.
  21. # To derive the formula for calculating jump speed, first solve
  22. #    v_t = v_0 + a * t
  23. # for the time at which you achieve maximum height, where a is the acceleration
  24. # due to gravity and v_t = 0. This gives:
  25. #    t = - v_0 / a
  26. # Use t and the desired MAX_JUMP_HEIGHT to solve for v_0 (jump speed) in
  27. #    s = s_0 + v_0 * t + (a * t^2) / 2
  28. JUMP_SPEED = math.sqrt(2 * GRAVITY * MAX_JUMP_HEIGHT)
  29. TERMINAL_VELOCITY = 50
  30.  
  31. PLAYER_HEIGHT = 2
  32.  
  33. def cube_vertices(x, y, z, n):
  34.     """ Return the vertices of the cube at position x, y, z with size 2*n.
  35.  
  36.    """
  37.     return [
  38.         x-n,y+n,z-n, x-n,y+n,z+n, x+n,y+n,z+n, x+n,y+n,z-n,  # top
  39.         x-n,y-n,z-n, x+n,y-n,z-n, x+n,y-n,z+n, x-n,y-n,z+n,  # bottom
  40.         x-n,y-n,z-n, x-n,y-n,z+n, x-n,y+n,z+n, x-n,y+n,z-n,  # left
  41.         x+n,y-n,z+n, x+n,y-n,z-n, x+n,y+n,z-n, x+n,y+n,z+n,  # right
  42.         x-n,y-n,z+n, x+n,y-n,z+n, x+n,y+n,z+n, x-n,y+n,z+n,  # front
  43.         x+n,y-n,z-n, x-n,y-n,z-n, x-n,y+n,z-n, x+n,y+n,z-n,  # back
  44.     ]
  45.  
  46.  
  47. def tex_coord(x, y, n=4):
  48.     """ Return the bounding vertices of the texture square.
  49.  
  50.    """
  51.     m = 1.0 / n
  52.     dx = x * m
  53.     dy = y * m
  54.     return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m
  55.  
  56.  
  57. def tex_coords(top, bottom, side):
  58.     """ Return a list of the texture squares for the top, bottom and side.
  59.  
  60.    """
  61.     top = tex_coord(*top)
  62.     bottom = tex_coord(*bottom)
  63.     side = tex_coord(*side)
  64.     result = []
  65.     result.extend(top)
  66.     result.extend(bottom)
  67.     result.extend(side * 4)
  68.     return result
  69.  
  70.  
  71. TEXTURE_PATH = 'texture.png'
  72.  
  73. GRASS = tex_coords((1, 0), (0, 1), (0, 0))
  74. SAND = tex_coords((1, 1), (1, 1), (1, 1))
  75. BRICK = tex_coords((2, 0), (2, 0), (2, 0))
  76. STONE = tex_coords((2, 1), (2, 1), (2, 1))
  77.  
  78. FACES = [
  79.     ( 0, 1, 0),
  80.     ( 0,-1, 0),
  81.     (-1, 0, 0),
  82.     ( 1, 0, 0),
  83.     ( 0, 0, 1),
  84.     ( 0, 0,-1),
  85. ]
  86.  
  87.  
  88. def normalize(position):
  89.     """ Accepts `position` of arbitrary precision and returns the block
  90.    containing that position.
  91.  
  92.    Parameters
  93.    ----------
  94.    position : tuple of len 3
  95.  
  96.    Returns
  97.    -------
  98.    block_position : tuple of ints of len 3
  99.  
  100.    """
  101.     x, y, z = position
  102.     x, y, z = (int(round(x)), int(round(y)), int(round(z)))
  103.     return (x, y, z)
  104.  
  105.  
  106. def sectorize(position):
  107.     """ Returns a tuple representing the sector for the given `position`.
  108.  
  109.    Parameters
  110.    ----------
  111.    position : tuple of len 3
  112.  
  113.    Returns
  114.    -------
  115.    sector : tuple of len 3
  116.  
  117.    """
  118.     x, y, z = normalize(position)
  119.     x, y, z = x / SECTOR_SIZE, y / SECTOR_SIZE, z / SECTOR_SIZE
  120.     return (x, 0, z)
  121.  
  122.  
  123. class Model(object):
  124.  
  125.     def __init__(self):
  126.  
  127.         # A Batch is a collection of vertex lists for batched rendering.
  128.         self.batch = pyglet.graphics.Batch()
  129.  
  130.         # A TextureGroup manages an OpenGL texture.
  131.         self.group = TextureGroup(image.load(TEXTURE_PATH).get_texture())
  132.  
  133.         # A mapping from position to the texture of the block at that position.
  134.         # This defines all the blocks that are currently in the world.
  135.         self.world = {}
  136.  
  137.         # Same mapping as `world` but only contains blocks that are shown.
  138.         self.shown = {}
  139.  
  140.         # Mapping from position to a pyglet `VertextList` for all shown blocks.
  141.         self._shown = {}
  142.  
  143.         # Mapping from sector to a list of positions inside that sector.
  144.         self.sectors = {}
  145.  
  146.         # Simple function queue implementation. The queue is populated with
  147.         # _show_block() and _hide_block() calls
  148.         self.queue = deque()
  149.  
  150.         self._initialize()
  151.  
  152.     def _initialize(self):
  153.         """ Initialize the world by placing all the blocks.
  154.  
  155.        """
  156.         n = 80  # 1/2 width and height of world
  157.         s = 1  # step size
  158.         y = 0  # initial y height
  159.         for x in range(-n, n + 1, s):
  160.             for z in range(-n, n + 1, s):
  161.                 # create a layer stone an grass everywhere.
  162.                 self.add_block((x, y - 2, z), GRASS, immediate=False)
  163.                 self.add_block((x, y - 3, z), STONE, immediate=False)
  164.                 if x in (-n, n) or z in (-n, n):
  165.                     # create outer walls.
  166.                     for dy in range(-2, 3):
  167.                         self.add_block((x, y + dy, z), STONE, immediate=False)
  168.  
  169.         # generate the hills randomly
  170.         o = n - 10
  171.         for _ in range(120):
  172.             a = random.randint(-o, o)  # x position of the hill
  173.             b = random.randint(-o, o)  # z position of the hill
  174.             c = -1  # base of the hill
  175.             h = random.randint(1, 6)  # height of the hill
  176.             s = random.randint(4, 8)  # 2 * s is the side length of the hill
  177.             d = 1  # how quickly to taper off the hills
  178.             t = random.choice([GRASS, SAND, BRICK])
  179.             for y in range(c, c + h):
  180.                 for x in range(a - s, a + s + 1):
  181.                     for z in range(b - s, b + s + 1):
  182.                         if (x - a) ** 2 + (z - b) ** 2 > (s + 1) ** 2:
  183.                             continue
  184.                         if (x - 0) ** 2 + (z - 0) ** 2 < 5 ** 2:
  185.                             continue
  186.                         self.add_block((x, y, z), t, immediate=False)
  187.                 s -= d  # decrement side lenth so hills taper off
  188.  
  189.     def hit_test(self, position, vector, max_distance=8):
  190.         """ Line of sight search from current position. If a block is
  191.        intersected it is returned, along with the block previously in the line
  192.        of sight. If no block is found, return None, None.
  193.  
  194.        Parameters
  195.        ----------
  196.        position : tuple of len 3
  197.            The (x, y, z) position to check visibility from.
  198.        vector : tuple of len 3
  199.            The line of sight vector.
  200.        max_distance : int
  201.            How many blocks away to search for a hit.
  202.  
  203.        """
  204.         m = 8
  205.         x, y, z = position
  206.         dx, dy, dz = vector
  207.         previous = None
  208.         for _ in range(max_distance * m):
  209.             key = normalize((x, y, z))
  210.             if key != previous and key in self.world:
  211.                 return key, previous
  212.             previous = key
  213.             x, y, z = x + dx / m, y + dy / m, z + dz / m
  214.         return None, None
  215.  
  216.     def exposed(self, position):
  217.         """ Returns False is given `position` is surrounded on all 6 sides by
  218.        blocks, True otherwise.
  219.  
  220.        """
  221.         x, y, z = position
  222.         for dx, dy, dz in FACES:
  223.             if (x + dx, y + dy, z + dz) not in self.world:
  224.                 return True
  225.         return False
  226.  
  227.     def add_block(self, position, texture, immediate=True):
  228.         """ Add a block with the given `texture` and `position` to the world.
  229.  
  230.        Parameters
  231.        ----------
  232.        position : tuple of len 3
  233.            The (x, y, z) position of the block to add.
  234.        texture : list of len 3
  235.            The coordinates of the texture squares. Use `tex_coords()` to
  236.            generate.
  237.        immediate : bool
  238.            Whether or not to draw the block immediately.
  239.  
  240.        """
  241.         if position in self.world:
  242.             self.remove_block(position, immediate)
  243.         self.world[position] = texture
  244.         self.sectors.setdefault(sectorize(position), []).append(position)
  245.         if immediate:
  246.             if self.exposed(position):
  247.                 self.show_block(position)
  248.             self.check_neighbors(position)
  249.  
  250.     def remove_block(self, position, immediate=True):
  251.         """ Remove the block at the given `position`.
  252.  
  253.        Parameters
  254.        ----------
  255.        position : tuple of len 3
  256.            The (x, y, z) position of the block to remove.
  257.        immediate : bool
  258.            Whether or not to immediately remove block from canvas.
  259.  
  260.        """
  261.         del self.world[position]
  262.         self.sectors[sectorize(position)].remove(position)
  263.         if immediate:
  264.             if position in self.shown:
  265.                 self.hide_block(position)
  266.             self.check_neighbors(position)
  267.  
  268.     def check_neighbors(self, position):
  269.         """ Check all blocks surrounding `position` and ensure their visual
  270.        state is current. This means hiding blocks that are not exposed and
  271.        ensuring that all exposed blocks are shown. Usually used after a block
  272.        is added or removed.
  273.  
  274.        """
  275.         x, y, z = position
  276.         for dx, dy, dz in FACES:
  277.             key = (x + dx, y + dy, z + dz)
  278.             if key not in self.world:
  279.                 continue
  280.             if self.exposed(key):
  281.                 if key not in self.shown:
  282.                     self.show_block(key)
  283.             else:
  284.                 if key in self.shown:
  285.                     self.hide_block(key)
  286.  
  287.     def show_block(self, position, immediate=True):
  288.         """ Show the block at the given `position`. This method assumes the
  289.        block has already been added with add_block()
  290.  
  291.        Parameters
  292.        ----------
  293.        position : tuple of len 3
  294.            The (x, y, z) position of the block to show.
  295.        immediate : bool
  296.            Whether or not to show the block immediately.
  297.  
  298.        """
  299.         texture = self.world[position]
  300.         self.shown[position] = texture
  301.         if immediate:
  302.             self._show_block(position, texture)
  303.         else:
  304.             self._enqueue(self._show_block, position, texture)
  305.  
  306.     def _show_block(self, position, texture):
  307.         """ Private implementation of the `show_block()` method.
  308.  
  309.        Parameters
  310.        ----------
  311.        position : tuple of len 3
  312.            The (x, y, z) position of the block to show.
  313.        texture : list of len 3
  314.            The coordinates of the texture squares. Use `tex_coords()` to
  315.            generate.
  316.  
  317.        """
  318.         x, y, z = position
  319.         vertex_data = cube_vertices(x, y, z, 0.5)
  320.         texture_data = list(texture)
  321.         # create vertex list
  322.         # FIXME Maybe `add_indexed()` should be used instead
  323.         self._shown[position] = self.batch.add(24, GL_QUADS, self.group,
  324.             ('v3f/static', vertex_data),
  325.             ('t2f/static', texture_data))
  326.  
  327.     def hide_block(self, position, immediate=True):
  328.         """ Hide the block at the given `position`. Hiding does not remove the
  329.        block from the world.
  330.  
  331.        Parameters
  332.        ----------
  333.        position : tuple of len 3
  334.            The (x, y, z) position of the block to hide.
  335.        immediate : bool
  336.            Whether or not to immediately remove the block from the canvas.
  337.  
  338.        """
  339.         self.shown.pop(position)
  340.         if immediate:
  341.             self._hide_block(position)
  342.         else:
  343.             self._enqueue(self._hide_block, position)
  344.  
  345.     def _hide_block(self, position):
  346.         """ Private implementation of the 'hide_block()` method.
  347.  
  348.        """
  349.         self._shown.pop(position).delete()
  350.  
  351.     def show_sector(self, sector):
  352.         """ Ensure all blocks in the given sector that should be shown are
  353.        drawn to the canvas.
  354.  
  355.        """
  356.         for position in self.sectors.get(sector, []):
  357.             if position not in self.shown and self.exposed(position):
  358.                 self.show_block(position, False)
  359.  
  360.     def hide_sector(self, sector):
  361.         """ Ensure all blocks in the given sector that should be hidden are
  362.        removed from the canvas.
  363.  
  364.        """
  365.         for position in self.sectors.get(sector, []):
  366.             if position in self.shown:
  367.                 self.hide_block(position, False)
  368.  
  369.     def change_sectors(self, before, after):
  370.         """ Move from sector `before` to sector `after`. A sector is a
  371.        contiguous x, y sub-region of world. Sectors are used to speed up
  372.        world rendering.
  373.  
  374.        """
  375.         before_set = set()
  376.         after_set = set()
  377.         pad = 4
  378.         for dx in range(-pad, pad + 1):
  379.             for dy in [0]:  # xrange(-pad, pad + 1):
  380.                 for dz in range(-pad, pad + 1):
  381.                     if dx ** 2 + dy ** 2 + dz ** 2 > (pad + 1) ** 2:
  382.                         continue
  383.                     if before:
  384.                         x, y, z = before
  385.                         before_set.add((x + dx, y + dy, z + dz))
  386.                     if after:
  387.                         x, y, z = after
  388.                         after_set.add((x + dx, y + dy, z + dz))
  389.         show = after_set - before_set
  390.         hide = before_set - after_set
  391.         for sector in show:
  392.             self.show_sector(sector)
  393.         for sector in hide:
  394.             self.hide_sector(sector)
  395.  
  396.     def _enqueue(self, func, *args):
  397.         """ Add `func` to the internal queue.
  398.  
  399.        """
  400.         self.queue.append((func, args))
  401.  
  402.     def _dequeue(self):
  403.         """ Pop the top function from the internal queue and call it.
  404.  
  405.        """
  406.         func, args = self.queue.popleft()
  407.         func(*args)
  408.  
  409.     def process_queue(self):
  410.         """ Process the entire queue while taking periodic breaks. This allows
  411.        the game loop to run smoothly. The queue contains calls to
  412.        _show_block() and _hide_block() so this method should be called if
  413.        add_block() or remove_block() was called with immediate=False
  414.  
  415.        """
  416.         start = time.clock()
  417.         while self.queue and time.clock() - start < 1.0 / TICKS_PER_SEC:
  418.             self._dequeue()
  419.  
  420.     def process_entire_queue(self):
  421.         """ Process the entire queue with no breaks.
  422.  
  423.        """
  424.         while self.queue:
  425.             self._dequeue()
  426.  
  427.  
  428. class Window(pyglet.window.Window):
  429.  
  430.     def __init__(self, *args, **kwargs):
  431.         super(Window, self).__init__(*args, **kwargs)
  432.  
  433.         # Whether or not the window exclusively captures the mouse.
  434.         self.exclusive = False
  435.  
  436.         # When flying gravity has no effect and speed is increased.
  437.         self.flying = False
  438.  
  439.         # Strafing is moving lateral to the direction you are facing,
  440.         # e.g. moving to the left or right while continuing to face forward.
  441.         #
  442.         # First element is -1 when moving forward, 1 when moving back, and 0
  443.         # otherwise. The second element is -1 when moving left, 1 when moving
  444.         # right, and 0 otherwise.
  445.         self.strafe = [0, 0]
  446.  
  447.         # Current (x, y, z) position in the world, specified with floats. Note
  448.         # that, perhaps unlike in math class, the y-axis is the vertical axis.
  449.         self.position = (0, 0, 0)
  450.  
  451.         # First element is rotation of the player in the x-z plane (ground
  452.         # plane) measured from the z-axis down. The second is the rotation
  453.         # angle from the ground plane up. Rotation is in degrees.
  454.         #
  455.         # The vertical plane rotation ranges from -90 (looking straight down) to
  456.         # 90 (looking straight up). The horizontal rotation range is unbounded.
  457.         self.rotation = (0, 0)
  458.  
  459.         # Which sector the player is currently in.
  460.         self.sector = None
  461.  
  462.         # The crosshairs at the center of the screen.
  463.         self.reticle = None
  464.  
  465.         # Velocity in the y (upward) direction.
  466.         self.dy = 0
  467.  
  468.         # A list of blocks the player can place. Hit num keys to cycle.
  469.         self.inventory = [BRICK, GRASS, SAND]
  470.  
  471.         # The current block the user can place. Hit num keys to cycle.
  472.         self.block = self.inventory[0]
  473.  
  474.         # Convenience list of num keys.
  475.         self.num_keys = [
  476.             key._1, key._2, key._3, key._4, key._5,
  477.             key._6, key._7, key._8, key._9, key._0]
  478.  
  479.         # Instance of the model that handles the world.
  480.         self.model = Model()
  481.  
  482.         # The label that is displayed in the top left of the canvas.
  483.         self.label = pyglet.text.Label('', font_name='Arial', font_size=18,
  484.             x=10, y=self.height - 10, anchor_x='left', anchor_y='top',
  485.             color=(0, 0, 0, 255))
  486.  
  487.         # This call schedules the `update()` method to be called
  488.         # TICKS_PER_SEC. This is the main game event loop.
  489.         pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SEC)
  490.  
  491.     def set_exclusive_mouse(self, exclusive):
  492.         """ If `exclusive` is True, the game will capture the mouse, if False
  493.        the game will ignore the mouse.
  494.  
  495.        """
  496.         super(Window, self).set_exclusive_mouse(exclusive)
  497.         self.exclusive = exclusive
  498.  
  499.     def get_sight_vector(self):
  500.         """ Returns the current line of sight vector indicating the direction
  501.        the player is looking.
  502.  
  503.        """
  504.         x, y = self.rotation
  505.         # y ranges from -90 to 90, or -pi/2 to pi/2, so m ranges from 0 to 1 and
  506.         # is 1 when looking ahead parallel to the ground and 0 when looking
  507.         # straight up or down.
  508.         m = math.cos(math.radians(y))
  509.         # dy ranges from -1 to 1 and is -1 when looking straight down and 1 when
  510.         # looking straight up.
  511.         dy = math.sin(math.radians(y))
  512.         dx = math.cos(math.radians(x - 90)) * m
  513.         dz = math.sin(math.radians(x - 90)) * m
  514.         return (dx, dy, dz)
  515.  
  516.     def get_motion_vector(self):
  517.         """ Returns the current motion vector indicating the velocity of the
  518.        player.
  519.  
  520.        Returns
  521.        -------
  522.        vector : tuple of len 3
  523.            Tuple containing the velocity in x, y, and z respectively.
  524.  
  525.        """
  526.         if any(self.strafe):
  527.             x, y = self.rotation
  528.             strafe = math.degrees(math.atan2(*self.strafe))
  529.             y_angle = math.radians(y)
  530.             x_angle = math.radians(x + strafe)
  531.             if self.flying:
  532.                 m = math.cos(y_angle)
  533.                 dy = math.sin(y_angle)
  534.                 if self.strafe[1]:
  535.                     # Moving left or right.
  536.                     dy = 0.0
  537.                     m = 1
  538.                 if self.strafe[0] > 0:
  539.                     # Moving backwards.
  540.                     dy *= -1
  541.                 # When you are flying up or down, you have less left and right
  542.                 # motion.
  543.                 dx = math.cos(x_angle) * m
  544.                 dz = math.sin(x_angle) * m
  545.             else:
  546.                 dy = 0.0
  547.                 dx = math.cos(x_angle)
  548.                 dz = math.sin(x_angle)
  549.         else:
  550.             dy = 0.0
  551.             dx = 0.0
  552.             dz = 0.0
  553.         return (dx, dy, dz)
  554.  
  555.     def update(self, dt):
  556.         """ This method is scheduled to be called repeatedly by the pyglet
  557.        clock.
  558.  
  559.        Parameters
  560.        ----------
  561.        dt : float
  562.            The change in time since the last call.
  563.  
  564.        """
  565.         self.model.process_queue()
  566.         sector = sectorize(self.position)
  567.         if sector != self.sector:
  568.             self.model.change_sectors(self.sector, sector)
  569.             if self.sector is None:
  570.                 self.model.process_entire_queue()
  571.             self.sector = sector
  572.         m = 8
  573.         dt = min(dt, 0.2)
  574.         for _ in range(m):
  575.             self._update(dt / m)
  576.  
  577.     def _update(self, dt):
  578.         """ Private implementation of the `update()` method. This is where most
  579.        of the motion logic lives, along with gravity and collision detection.
  580.  
  581.        Parameters
  582.        ----------
  583.        dt : float
  584.            The change in time since the last call.
  585.  
  586.        """
  587.         # walking
  588.         speed = FLYING_SPEED if self.flying else WALKING_SPEED
  589.         d = dt * speed # distance covered this tick.
  590.         dx, dy, dz = self.get_motion_vector()
  591.         # New position in space, before accounting for gravity.
  592.         dx, dy, dz = dx * d, dy * d, dz * d
  593.         # gravity
  594.         if not self.flying:
  595.             # Update your vertical speed: if you are falling, speed up until you
  596.             # hit terminal velocity; if you are jumping, slow down until you
  597.             # start falling.
  598.             self.dy -= dt * GRAVITY
  599.             self.dy = max(self.dy, -TERMINAL_VELOCITY)
  600.             dy += self.dy * dt
  601.         # collisions
  602.         x, y, z = self.position
  603.         x, y, z = self.collide((x + dx, y + dy, z + dz), PLAYER_HEIGHT)
  604.         self.position = (x, y, z)
  605.  
  606.     def collide(self, position, height):
  607.         """ Checks to see if the player at the given `position` and `height`
  608.        is colliding with any blocks in the world.
  609.  
  610.        Parameters
  611.        ----------
  612.        position : tuple of len 3
  613.            The (x, y, z) position to check for collisions at.
  614.        height : int or float
  615.            The height of the player.
  616.  
  617.        Returns
  618.        -------
  619.        position : tuple of len 3
  620.            The new position of the player taking into account collisions.
  621.  
  622.        """
  623.         # How much overlap with a dimension of a surrounding block you need to
  624.         # have to count as a collision. If 0, touching terrain at all counts as
  625.         # a collision. If .49, you sink into the ground, as if walking through
  626.         # tall grass. If >= .5, you'll fall through the ground.
  627.         pad = 0.25
  628.         p = list(position)
  629.         np = normalize(position)
  630.         for face in FACES:  # check all surrounding blocks
  631.             for i in range(3):  # check each dimension independently
  632.                 if not face[i]:
  633.                     continue
  634.                 # How much overlap you have with this dimension.
  635.                 d = (p[i] - np[i]) * face[i]
  636.                 if d < pad:
  637.                     continue
  638.                 for dy in range(height):  # check each height
  639.                     op = list(np)
  640.                     op[1] -= dy
  641.                     op[i] += face[i]
  642.                     if tuple(op) not in self.model.world:
  643.                         continue
  644.                     p[i] -= (d - pad) * face[i]
  645.                     if face == (0, -1, 0) or face == (0, 1, 0):
  646.                         # You are colliding with the ground or ceiling, so stop
  647.                         # falling / rising.
  648.                         self.dy = 0
  649.                     break
  650.         return tuple(p)
  651.  
  652.     def on_mouse_press(self, x, y, button, modifiers):
  653.         """ Called when a mouse button is pressed. See pyglet docs for button
  654.        amd modifier mappings.
  655.  
  656.        Parameters
  657.        ----------
  658.        x, y : int
  659.            The coordinates of the mouse click. Always center of the screen if
  660.            the mouse is captured.
  661.        button : int
  662.            Number representing mouse button that was clicked. 1 = left button,
  663.            4 = right button.
  664.        modifiers : int
  665.            Number representing any modifying keys that were pressed when the
  666.            mouse button was clicked.
  667.  
  668.        """
  669.         if self.exclusive:
  670.             vector = self.get_sight_vector()
  671.             block, previous = self.model.hit_test(self.position, vector)
  672.             if (button == mouse.RIGHT) or \
  673.                     ((button == mouse.LEFT) and (modifiers & key.MOD_CTRL)):
  674.                 # ON OSX, control + left click = right click.
  675.                 if previous:
  676.                     self.model.add_block(previous, self.block)
  677.             elif button == pyglet.window.mouse.LEFT and block:
  678.                 texture = self.model.world[block]
  679.                 if texture != STONE:
  680.                     self.model.remove_block(block)
  681.         else:
  682.             self.set_exclusive_mouse(True)
  683.  
  684.     def on_mouse_motion(self, x, y, dx, dy):
  685.         """ Called when the player moves the mouse.
  686.  
  687.        Parameters
  688.        ----------
  689.        x, y : int
  690.            The coordinates of the mouse click. Always center of the screen if
  691.            the mouse is captured.
  692.        dx, dy : float
  693.            The movement of the mouse.
  694.  
  695.        """
  696.         if self.exclusive:
  697.             m = 0.15
  698.             x, y = self.rotation
  699.             x, y = x + dx * m, y + dy * m
  700.             y = max(-90, min(90, y))
  701.             self.rotation = (x, y)
  702.  
  703.     def on_key_press(self, symbol, modifiers):
  704.         """ Called when the player presses a key. See pyglet docs for key
  705.        mappings.
  706.  
  707.        Parameters
  708.        ----------
  709.        symbol : int
  710.            Number representing the key that was pressed.
  711.        modifiers : int
  712.            Number representing any modifying keys that were pressed.
  713.  
  714.        """
  715.         if symbol == key.W:
  716.             self.strafe[0] -= 1
  717.         elif symbol == key.S:
  718.             self.strafe[0] += 1
  719.         elif symbol == key.A:
  720.             self.strafe[1] -= 1
  721.         elif symbol == key.D:
  722.             self.strafe[1] += 1
  723.         elif symbol == key.SPACE:
  724.             if self.dy == 0:
  725.                 self.dy = JUMP_SPEED
  726.         elif symbol == key.ESCAPE:
  727.             self.set_exclusive_mouse(False)
  728.         elif symbol == key.TAB:
  729.             self.flying = not self.flying
  730.         elif symbol in self.num_keys:
  731.             index = (symbol - self.num_keys[0]) % len(self.inventory)
  732.             self.block = self.inventory[index]
  733.  
  734.     def on_key_release(self, symbol, modifiers):
  735.         """ Called when the player releases a key. See pyglet docs for key
  736.        mappings.
  737.  
  738.        Parameters
  739.        ----------
  740.        symbol : int
  741.            Number representing the key that was pressed.
  742.        modifiers : int
  743.            Number representing any modifying keys that were pressed.
  744.  
  745.        """
  746.         if symbol == key.W:
  747.             self.strafe[0] += 1
  748.         elif symbol == key.S:
  749.             self.strafe[0] -= 1
  750.         elif symbol == key.A:
  751.             self.strafe[1] += 1
  752.         elif symbol == key.D:
  753.             self.strafe[1] -= 1
  754.  
  755.     def on_resize(self, width, height):
  756.         """ Called when the window is resized to a new `width` and `height`.
  757.  
  758.        """
  759.         # label
  760.         self.label.y = height - 10
  761.         # reticle
  762.         if self.reticle:
  763.             self.reticle.delete()
  764.         x, y = self.width / 2, self.height / 2
  765.         n = 10
  766.         self.reticle = pyglet.graphics.vertex_list(4,
  767.             ('v2i', (x - n, y, x + n, y, x, y - n, x, y + n))
  768.         )
  769.  
  770.     def set_2d(self):
  771.         """ Configure OpenGL to draw in 2d.
  772.  
  773.        """
  774.         width, height = self.get_size()
  775.         glDisable(GL_DEPTH_TEST)
  776.         glViewport(0, 0, width, height)
  777.         glMatrixMode(GL_PROJECTION)
  778.         glLoadIdentity()
  779.         glOrtho(0, width, 0, height, -1, 1)
  780.         glMatrixMode(GL_MODELVIEW)
  781.         glLoadIdentity()
  782.  
  783.     def set_3d(self):
  784.         """ Configure OpenGL to draw in 3d.
  785.  
  786.        """
  787.         width, height = self.get_size()
  788.         glEnable(GL_DEPTH_TEST)
  789.         glViewport(0, 0, width, height)
  790.         glMatrixMode(GL_PROJECTION)
  791.         glLoadIdentity()
  792.         gluPerspective(65.0, width / float(height), 0.1, 60.0)
  793.         glMatrixMode(GL_MODELVIEW)
  794.         glLoadIdentity()
  795.         x, y = self.rotation
  796.         glRotatef(x, 0, 1, 0)
  797.         glRotatef(-y, math.cos(math.radians(x)), 0, math.sin(math.radians(x)))
  798.         x, y, z = self.position
  799.         glTranslatef(-x, -y, -z)
  800.  
  801.     def on_draw(self):
  802.         """ Called by pyglet to draw the canvas.
  803.  
  804.        """
  805.         self.clear()
  806.         self.set_3d()
  807.         glColor3d(1, 1, 1)
  808.         self.model.batch.draw()
  809.         self.draw_focused_block()
  810.         self.set_2d()
  811.         self.draw_label()
  812.         self.draw_reticle()
  813.  
  814.     def draw_focused_block(self):
  815.         """ Draw black edges around the block that is currently under the
  816.        crosshairs.
  817.  
  818.        """
  819.         vector = self.get_sight_vector()
  820.         block = self.model.hit_test(self.position, vector)[0]
  821.         if block:
  822.             x, y, z = block
  823.             vertex_data = cube_vertices(x, y, z, 0.51)
  824.             glColor3d(0, 0, 0)
  825.             glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
  826.             pyglet.graphics.draw(24, GL_QUADS, ('v3f/static', vertex_data))
  827.             glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
  828.  
  829.     def draw_label(self):
  830.         """ Draw the label in the top left of the screen.
  831.  
  832.        """
  833.         x, y, z = self.position
  834.         self.label.text = '%02d (%.2f, %.2f, %.2f) %d / %d' % (
  835.             pyglet.clock.get_fps(), x, y, z,
  836.             len(self.model._shown), len(self.model.world))
  837.         self.label.draw()
  838.  
  839.     def draw_reticle(self):
  840.         """ Draw the crosshairs in the center of the screen.
  841.  
  842.        """
  843.         glColor3d(0, 0, 0)
  844.         self.reticle.draw(GL_LINES)
  845.  
  846.  
  847. def setup_fog():
  848.     """ Configure the OpenGL fog properties.
  849.  
  850.    """
  851.     # Enable fog. Fog "blends a fog color with each rasterized pixel fragment's
  852.     # post-texturing color."
  853.     glEnable(GL_FOG)
  854.     # Set the fog color.
  855.     glFogfv(GL_FOG_COLOR, (GLfloat * 4)(0.5, 0.69, 1.0, 1))
  856.     # Say we have no preference between rendering speed and quality.
  857.     glHint(GL_FOG_HINT, GL_DONT_CARE)
  858.     # Specify the equation used to compute the blending factor.
  859.     glFogi(GL_FOG_MODE, GL_LINEAR)
  860.     # How close and far away fog starts and ends. The closer the start and end,
  861.     # the denser the fog in the fog range.
  862.     glFogf(GL_FOG_START, 20.0)
  863.     glFogf(GL_FOG_END, 60.0)
  864.  
  865.  
  866. def setup():
  867.     """ Basic OpenGL configuration.
  868.  
  869.    """
  870.     # Set the color of "clear", i.e. the sky, in rgba.
  871.     glClearColor(0.5, 0.69, 1.0, 1)
  872.     # Enable culling (not rendering) of back-facing facets -- facets that aren't
  873.     # visible to you.
  874.     glEnable(GL_CULL_FACE)
  875.     # Set the texture minification/magnification function to GL_NEAREST (nearest
  876.     # in Manhattan distance) to the specified texture coordinates. GL_NEAREST
  877.     # "is generally faster than GL_LINEAR, but it can produce textured images
  878.     # with sharper edges because the transition between texture elements is not
  879.     # as smooth."
  880.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
  881.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
  882.     setup_fog()
  883.  
  884.  
  885. def main():
  886.     window = Window(width=800, height=600, caption='Pyglet', resizable=True)
  887.     # Hide the mouse cursor and prevent the mouse from leaving the window.
  888.     window.set_exclusive_mouse(True)
  889.     setup()
  890.     pyglet.app.run()
  891.  
  892. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement