Advertisement
Peaser

Minecraft in Python

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