当前位置:   article > 正文

用Python实现简易版《Minecraft》_python编程制作mc

python编程制作mc

导语

        《我的世界》(Minecraft)别称《麦块》《我的作品》《当个创世神》,是一款沙盒建造游戏,该游戏基于Java平台,最初由马库斯·诺奇·佩尔松单独开发,后由Mojang制作发行。网易则是中国大陆地区的独家运营商于2014年9月4日在PS4XboxOne发布,于2017年5月12日在Switch发布,于2017年8月8日在PC发布。

        玩家可以独自一人或与朋友们一起,探索随机生成的世界。在Minecraft的创造模式中,玩家将拥有无限的资源;而在生存模式中,玩家需要尽可能收集利用资源,打造武器和盔甲,去抵御危险的怪物。

        而今天我们要做的简易版《Minecraft》,则负责了地形生成,人物移动,破坏、放置方块等内容的开发。

        废话不多说,开始吧!

开始创作

前期准备

        我的Python 3.10.9,pyglet 1.5.27。

        pyglet是python的一个第三方库,下载方法也很简单,在Windows的cmd下输入:

pip install pyglet

        即可。

方块图像

        拿走不谢~

Minecraft方块图

        将图像重命名为 texture.png

正式开始创作之旅

引入一些库

        首先,我们需要用到刚刚下的pyglet库中的一些程序,以及关于系统,数学运算,时间,随机数等的自带库。

        引用如下:

  1. from __future__ import division
  2. import sys
  3. import math
  4. import random
  5. import time
  6. from collections import deque
  7. from pyglet import image
  8. from pyglet.gl import *
  9. from pyglet.graphics import TextureGroup
  10. from pyglet.window import key, mouse

会用到的函数与变量(常量)

        设计一个游戏,自然会涉及到的一些变量或常量就是人物的坐标、走路的大小、人物的大小、移动速度、重力、帧数等。在这一模块中,还会引用到一些方块的设定、一些常用的函数等。

        代码如下,写了注释,自己研究吧。

  1. # 每秒帧数
  2. TICKS_PER_SEC = 120
  3. # 你可以走的大小
  4. SECTOR_SIZE =2000
  5. # 行走速度与飞行速度
  6. WALKING_SPEED = 5.5
  7. FLYING_SPEED = 15
  8. # 重力与跳跃高度
  9. GRAVITY = 20
  10. MAX_JUMP_HEIGHT =1
  11. # About the height of a block.
  12. # To derive the formula for calculating jump speed, first solve
  13. # v_t = v_0 + a * t
  14. # for the time at which you achieve maximum height, where a is the acceleration
  15. # due to gravity and v_t = 0. This gives:
  16. # t = - v_0 / a
  17. # Use t and the desired MAX_JUMP_HEIGHT to solve for v_0 (jump speed) in
  18. # s = s_0 + v_0 * t + (a * t^2) / 2
  19. JUMP_SPEED = math.sqrt(2 * GRAVITY * MAX_JUMP_HEIGHT)
  20. TERMINAL_VELOCITY = 50
  21. PLAYER_HEIGHT = 2
  22. if sys.version_info[0] >= 3:
  23. xrange = range
  24. def cube_vertices(x, y, z, n):
  25. """ Return the vertices of the cube at position x, y, z with size 2*n.
  26. """
  27. return [
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. ]
  35. def tex_coord(x, y, n=4):
  36. """
  37. Return the bounding vertices of the texture square.
  38. """
  39. m = 1.0 / n
  40. dx = x * m
  41. dy = y * m
  42. return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m
  43. def tex_coords(top, bottom, side):
  44. """
  45. Return a list of the texture squares for the top, bottom and side.
  46. """
  47. top = tex_coord(*top)
  48. bottom = tex_coord(*bottom)
  49. side = tex_coord(*side)
  50. result = []
  51. result.extend(top)
  52. result.extend(bottom)
  53. result.extend(side * 4)
  54. return result
  55. TEXTURE_PATH = 'Minecraft(1)/texture.png'
  56. GRASS = tex_coords((1, 0), (0, 1), (0, 0))
  57. SAND = tex_coords((1, 1), (1, 1), (1, 1))
  58. BRICK = tex_coords((2, 0), (2, 0), (2, 0))
  59. STONE = tex_coords((2, 1), (2, 1), (2, 1))
  60. PURPLE = tex_coords((3, 1), (3, 1), (3, 1))
  61. RED = tex_coords((3, 0), (3, 0), (3, 0))
  62. GREEN = tex_coords((3, 2), (3, 2), (3, 2))
  63. BLUE = tex_coords((1, 2), (1, 2), (1, 2))
  64. BLACK = tex_coords((2, 2), (2, 2), (2, 2))
  65. ORANGE = tex_coords((3, 2), (3, 2), (3, 2))
  66. FACES = [
  67. ( 0, 1, 0),
  68. ( 0,-1, 0),
  69. (-1, 0, 0),
  70. ( 1, 0, 0),
  71. ( 0, 0, 1),
  72. ( 0, 0,-1),
  73. ]
  74. def normalize(position):
  75. """ Accepts `position` of arbitrary precision and returns the block
  76. containing that position.
  77. Parameters
  78. ----------
  79. position : tuple of len 3
  80. Returns
  81. -------
  82. block_position : tuple of ints of len 3
  83. """
  84. x, y, z = position
  85. x, y, z = (int(round(x)), int(round(y)), int(round(z)))
  86. return (x, y, z)
  87. def sectorize(position):
  88. """ Returns a tuple representing the sector for the given `position`.
  89. Parameters
  90. ----------
  91. position : tuple of len 3
  92. Returns
  93. -------
  94. sector : tuple of len 3
  95. """
  96. x, y, z = normalize(position)
  97. x, y, z = x // SECTOR_SIZE, y // SECTOR_SIZE, z // SECTOR_SIZE
  98. return (x, 0, z)

生成世界的类

        生成一个世界,首先要做到的是用很多个数组、字典、列表、集合等来存储一些必要的信息,比如每个方块的位置等。

  1. class Model(object):
  2. def __init__(self):
  3. # A Batch is a collection of vertex lists for batched rendering.
  4. self.batch = pyglet.graphics.Batch()
  5. # A TextureGroup manages an OpenGL texture.
  6. self.group = TextureGroup(image.load(TEXTURE_PATH).get_texture())
  7. # A mapping from position to the texture of the block at that position.
  8. # This defines all the blocks that are currently in the world.
  9. self.world = {}
  10. # Same mapping as `world` but only contains blocks that are shown.
  11. self.shown = {}
  12. # Mapping from position to a pyglet `VertextList` for all shown blocks.
  13. self._shown = {}
  14. # Mapping from sector to a list of positions inside that sector.
  15. self.sectors = {}
  16. # Simple function queue implementation. The queue is populated with
  17. # _show_block() and _hide_block() calls
  18. self.queue = deque()
  19. self._initialize()
  20. def _initialize(self):
  21. """ Initialize the world by placing all the blocks.
  22. """
  23. n = 200 # 1/2 width and height of world
  24. s = 1 # step size
  25. y =0 # initial y height
  26. for x in xrange(-n, n + 1, s):
  27. for z in xrange(-n, n + 1, s):
  28. # create a layer stone an grass everywhere.
  29. self.add_block((x, y - 2, z), GRASS, immediate=False)
  30. self.add_block((x, y - 3, z), STONE, immediate=False)
  31. self.add_block((x, y - 4, z), STONE, immediate=False)
  32. if x in (-n, n) or z in (-n, n):
  33. # create outer walls.
  34. for dy in xrange(-2, 3):
  35. self.add_block((x, y + dy, z), STONE, immediate=False)
  36. # generate the hills randomly
  37. o = n -15
  38. for _ in xrange(120):
  39. a = random.randint(-o, o) # x position of the hill
  40. b = random.randint(-o, o) # z position of the hill
  41. c = -1 # base of the hill
  42. h = random.randint(2,6) # height of the hill
  43. s = random.randint(4,12) # 2 * s is the side length of the hill
  44. d = 1 # how quickly to taper off the hills
  45. t = random.choice([GRASS, SAND,STONE])
  46. for y in xrange(c, c + h):
  47. for x in xrange(a - s, a + s + 1):
  48. for z in xrange(b - s, b + s + 1):
  49. if (x - a) ** 2 + (z - b) ** 2 > (s + 1) ** 2:
  50. continue
  51. if (x - 0) ** 2 + (z - 0) ** 2 < 5 ** 2:
  52. continue
  53. self.add_block((x, y, z), t, immediate=False)
  54. s -= d # decrement side lenth so hills taper off
  55. def hit_test(self, position, vector, max_distance=8):
  56. """ Line of sight search from current position. If a block is
  57. intersected it is returned, along with the block previously in the line
  58. of sight. If no block is found, return None, None.
  59. Parameters
  60. ----------
  61. position : tuple of len 3
  62. The (x, y, z) position to check visibility from.
  63. vector : tuple of len 3
  64. The line of sight vector.
  65. max_distance : int
  66. How many blocks away to search for a hit.
  67. """
  68. m = 8
  69. x, y, z = position
  70. dx, dy, dz = vector
  71. previous = None
  72. for _ in xrange(max_distance * m):
  73. key = normalize((x, y, z))
  74. if key != previous and key in self.world:
  75. return key, previous
  76. previous = key
  77. x, y, z = x + dx / m, y + dy / m, z + dz / m
  78. return None, None
  79. def exposed(self, position):
  80. """ Returns False is given `position` is surrounded on all 6 sides by
  81. blocks, True otherwise.
  82. """
  83. x, y, z = position
  84. for dx, dy, dz in FACES:
  85. if (x + dx, y + dy, z + dz) not in self.world:
  86. return True
  87. return False
  88. def add_block(self, position, texture, immediate=True):
  89. """ Add a block with the given `texture` and `position` to the world.
  90. Parameters
  91. ----------
  92. position : tuple of len 3
  93. The (x, y, z) position of the block to add.
  94. texture : list of len 3
  95. The coordinates of the texture squares. Use `tex_coords()` to
  96. generate.
  97. immediate : bool
  98. Whether or not to draw the block immediately.
  99. """
  100. if position in self.world:
  101. self.remove_block(position, immediate)
  102. self.world[position] = texture
  103. self.sectors.setdefault(sectorize(position), []).append(position)
  104. if immediate:
  105. if self.exposed(position):
  106. self.show_block(position)
  107. self.check_neighbors(position)
  108. def remove_block(self, position, immediate=True):
  109. """ Remove the block at the given `position`.
  110. Parameters
  111. ----------
  112. position : tuple of len 3
  113. The (x, y, z) position of the block to remove.
  114. immediate : bool
  115. Whether or not to immediately remove block from canvas.
  116. """
  117. del self.world[position]
  118. self.sectors[sectorize(position)].remove(position)
  119. if immediate:
  120. if position in self.shown:
  121. self.hide_block(position)
  122. self.check_neighbors(position)
  123. def check_neighbors(self, position):
  124. """ Check all blocks surrounding `position` and ensure their visual
  125. state is current. This means hiding blocks that are not exposed and
  126. ensuring that all exposed blocks are shown. Usually used after a block
  127. is added or removed.
  128. """
  129. x, y, z = position
  130. for dx, dy, dz in FACES:
  131. key = (x + dx, y + dy, z + dz)
  132. if key not in self.world:
  133. continue
  134. if self.exposed(key):
  135. if key not in self.shown:
  136. self.show_block(key)
  137. else:
  138. if key in self.shown:
  139. self.hide_block(key)
  140. def show_block(self, position, immediate=True):
  141. """ Show the block at the given `position`. This method assumes the
  142. block has already been added with add_block()
  143. Parameters
  144. ----------
  145. position : tuple of len 3
  146. The (x, y, z) position of the block to show.
  147. immediate : bool
  148. Whether or not to show the block immediately.
  149. """
  150. texture = self.world[position]
  151. self.shown[position] = texture
  152. if immediate:
  153. self._show_block(position, texture)
  154. else:
  155. self._enqueue(self._show_block, position, texture)
  156. def _show_block(self, position, texture):
  157. """ Private implementation of the `show_block()` method.
  158. Parameters
  159. ----------
  160. position : tuple of len 3
  161. The (x, y, z) position of the block to show.
  162. texture : list of len 3
  163. The coordinates of the texture squares. Use `tex_coords()` to
  164. generate.
  165. """
  166. x, y, z = position
  167. vertex_data = cube_vertices(x, y, z, 0.5)
  168. texture_data = list(texture)
  169. # create vertex list
  170. # FIXME Maybe `add_indexed()` should be used instead
  171. self._shown[position] = self.batch.add(24, GL_QUADS, self.group,
  172. ('v3f/static', vertex_data),
  173. ('t2f/static', texture_data))
  174. def hide_block(self, position, immediate=True):
  175. """ Hide the block at the given `position`. Hiding does not remove the
  176. block from the world.
  177. Parameters
  178. ----------
  179. position : tuple of len 3
  180. The (x, y, z) position of the block to hide.
  181. immediate : bool
  182. Whether or not to immediately remove the block from the canvas.
  183. """
  184. self.shown.pop(position)
  185. if immediate:
  186. self._hide_block(position)
  187. else:
  188. self._enqueue(self._hide_block, position)
  189. def _hide_block(self, position):
  190. """ Private implementation of the 'hide_block()` method.
  191. """
  192. self._shown.pop(position).delete()
  193. def show_sector(self, sector):
  194. """ Ensure all blocks in the given sector that should be shown are
  195. drawn to the canvas.
  196. """
  197. for position in self.sectors.get(sector, []):
  198. if position not in self.shown and self.exposed(position):
  199. self.show_block(position, False)
  200. def hide_sector(self, sector):
  201. """ Ensure all blocks in the given sector that should be hidden are
  202. removed from the canvas.
  203. """
  204. for position in self.sectors.get(sector, []):
  205. if position in self.shown:
  206. self.hide_block(position, False)
  207. def change_sectors(self, before, after):
  208. """ Move from sector `before` to sector `after`. A sector is a
  209. contiguous x, y sub-region of world. Sectors are used to speed up
  210. world rendering.
  211. """
  212. before_set = set()
  213. after_set = set()
  214. pad = 4
  215. for dx in xrange(-pad, pad + 1):
  216. for dy in [0]: # xrange(-pad, pad + 1):
  217. for dz in xrange(-pad, pad + 1):
  218. if dx ** 2 + dy ** 2 + dz ** 2 > (pad + 1) ** 2:
  219. continue
  220. if before:
  221. x, y, z = before
  222. before_set.add((x + dx, y + dy, z + dz))
  223. if after:
  224. x, y, z = after
  225. after_set.add((x + dx, y + dy, z + dz))
  226. show = after_set - before_set
  227. hide = before_set - after_set
  228. for sector in show:
  229. self.show_sector(sector)
  230. for sector in hide:
  231. self.hide_sector(sector)
  232. def _enqueue(self, func, *args):
  233. """ Add `func` to the internal queue.
  234. """
  235. self.queue.append((func, args))
  236. def _dequeue(self):
  237. """ Pop the top function from the internal queue and call it.
  238. """
  239. func, args = self.queue.popleft()
  240. func(*args)
  241. def process_queue(self):
  242. """ Process the entire queue while taking periodic breaks. This allows
  243. the game loop to run smoothly. The queue contains calls to
  244. _show_block() and _hide_block() so this method should be called if
  245. add_block() or remove_block() was called with immediate=False
  246. """
  247. start = time.time
  248. while self.queue and time.time < 1 / TICKS_PER_SEC:
  249. self._dequeue()
  250. def process_entire_queue(self):
  251. """ Process the entire queue with no breaks.
  252. """
  253. while self.queue:
  254. self._dequeue()
'
运行

窗口类

        已经接近尾声啦!下面定义一个窗口:

  1. class Window(pyglet.window.Window):
  2. def __init__(self, *args, **kwargs):
  3. super(Window, self).__init__(*args, **kwargs)
  4. # Whether or not the window exclusively captures the mouse.
  5. self.exclusive = False
  6. # When flying gravity has no effect and speed is increased.
  7. self.flying = False
  8. # Strafing is moving lateral to the direction you are facing,
  9. # e.g. moving to the left or right while continuing to face forward.
  10. #
  11. # First element is -1 when moving forward, 1 when moving back, and 0
  12. # otherwise. The second element is -1 when moving left, 1 when moving
  13. # right, and 0 otherwise.
  14. self.strafe = [0, 0]
  15. # Current (x, y, z) position in the world, specified with floats. Note
  16. # that, perhaps unlike in math class, the y-axis is the vertical axis.
  17. self.position = (0, 0, 0)
  18. # First element is rotation of the player in the x-z plane (ground
  19. # plane) measured from the z-axis down. The second is the rotation
  20. # angle from the ground plane up. Rotation is in degrees.
  21. #
  22. # The vertical plane rotation ranges from -90 (looking straight down) to
  23. # 90 (looking straight up). The horizontal rotation range is unbounded.
  24. self.rotation = (0, 0)
  25. # Which sector the player is currently in.
  26. self.sector = None
  27. # The crosshairs at the center of the screen.
  28. self.reticle = None
  29. # Velocity in the y (upward) direction.
  30. self.dy = 0
  31. # A list of blocks the player can place. Hit num keys to cycle.
  32. self.inventory = [BRICK, GRASS, SAND,STONE,RED,PURPLE,GREEN,BLUE,BLACK,ORANGE]
  33. # The current block the user can place. Hit num keys to cycle.
  34. self.block = self.inventory[0]
  35. # Convenience list of num keys.
  36. self.num_keys = [
  37. key._1, key._2, key._3, key._4, key._5,
  38. key._6, key._7, key._8, key._9, key._0,key.E,key.R]
  39. # Instance of the model that handles the world.
  40. self.model = Model()
  41. # The label that is displayed in the top left of the canvas.
  42. self.label = pyglet.text.Label('', font_name='Arial', font_size=18,
  43. x=10, y=self.height - 10, anchor_x='left', anchor_y='top',
  44. color=(0, 0, 0, 255))
  45. # This call schedules the `update()` method to be called
  46. # TICKS_PER_SEC. This is the main game event loop.
  47. pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SEC)
  48. def set_exclusive_mouse(self, exclusive):
  49. """ If `exclusive` is True, the game will capture the mouse, if False
  50. the game will ignore the mouse.
  51. """
  52. super(Window, self).set_exclusive_mouse(exclusive)
  53. self.exclusive = exclusive
  54. def get_sight_vector(self):
  55. """ Returns the current line of sight vector indicating the direction
  56. the player is looking.
  57. """
  58. x, y = self.rotation
  59. # y ranges from -90 to 90, or -pi/2 to pi/2, so m ranges from 0 to 1 and
  60. # is 1 when looking ahead parallel to the ground and 0 when looking
  61. # straight up or down.
  62. m = math.cos(math.radians(y))
  63. # dy ranges from -1 to 1 and is -1 when looking straight down and 1 when
  64. # looking straight up.
  65. dy = math.sin(math.radians(y))
  66. dx = math.cos(math.radians(x - 90)) * m
  67. dz = math.sin(math.radians(x - 90)) * m
  68. return (dx, dy, dz)
  69. def get_motion_vector(self):
  70. """ Returns the current motion vector indicating the velocity of the
  71. player.
  72. Returns
  73. -------
  74. vector : tuple of len 3
  75. Tuple containing the velocity in x, y, and z respectively.
  76. """
  77. if any(self.strafe):
  78. x, y = self.rotation
  79. strafe = math.degrees(math.atan2(*self.strafe))
  80. y_angle = math.radians(y)
  81. x_angle = math.radians(x + strafe)
  82. if self.flying:
  83. m = math.cos(y_angle)
  84. dy = math.sin(y_angle)
  85. if self.strafe[1]:
  86. # Moving left or right.
  87. dy = 0.0
  88. m = 1
  89. if self.strafe[0] > 0:
  90. # Moving backwards.
  91. dy *= -1
  92. # When you are flying up or down, you have less left and right
  93. # motion.
  94. dx = math.cos(x_angle) * m
  95. dz = math.sin(x_angle) * m
  96. else:
  97. dy = 0.0
  98. dx = math.cos(x_angle)
  99. dz = math.sin(x_angle)
  100. else:
  101. dy = 0.0
  102. dx = 0.0
  103. dz = 0.0
  104. return (dx, dy, dz)
  105. def update(self, dt):
  106. """ This method is scheduled to be called repeatedly by the pyglet
  107. clock.
  108. Parameters
  109. ----------
  110. dt : float
  111. The change in time since the last call.
  112. """
  113. self.model.process_queue()
  114. sector = sectorize(self.position)
  115. if sector != self.sector:
  116. self.model.change_sectors(self.sector, sector)
  117. if self.sector is None:
  118. self.model.process_entire_queue()
  119. self.sector = sector
  120. m = 8
  121. dt = min(dt, 0.2)
  122. for _ in xrange(m):
  123. self._update(dt / m)
  124. def _update(self, dt):
  125. """ Private implementation of the `update()` method. This is where most
  126. of the motion logic lives, along with gravity and collision detection.
  127. Parameters
  128. ----------
  129. dt : float
  130. The change in time since the last call.
  131. """
  132. # walking
  133. speed = FLYING_SPEED if self.flying else WALKING_SPEED
  134. d = dt * speed # distance covered this tick.
  135. dx, dy, dz = self.get_motion_vector()
  136. # New position in space, before accounting for gravity.
  137. dx, dy, dz = dx * d, dy * d, dz * d
  138. # gravity
  139. if not self.flying:
  140. # Update your vertical speed: if you are falling, speed up until you
  141. # hit terminal velocity; if you are jumping, slow down until you
  142. # start falling.
  143. self.dy -= dt * GRAVITY
  144. self.dy = max(self.dy, -TERMINAL_VELOCITY)
  145. dy += self.dy * dt
  146. # collisions
  147. x, y, z = self.position
  148. x, y, z = self.collide((x + dx, y + dy, z + dz), PLAYER_HEIGHT)
  149. self.position = (x, y, z)
  150. def collide(self, position, height):
  151. """ Checks to see if the player at the given `position` and `height`
  152. is colliding with any blocks in the world.
  153. Parameters
  154. ----------
  155. position : tuple of len 3
  156. The (x, y, z) position to check for collisions at.
  157. height : int or float
  158. The height of the player.
  159. Returns
  160. -------
  161. position : tuple of len 3
  162. The new position of the player taking into account collisions.
  163. """
  164. # How much overlap with a dimension of a surrounding block you need to
  165. # have to count as a collision. If 0, touching terrain at all counts as
  166. # a collision. If .49, you sink into the ground, as if walking through
  167. # tall grass. If >= .5, you'll fall through the ground.
  168. pad = 0
  169. p = list(position)
  170. np = normalize(position)
  171. for face in FACES: # check all surrounding blocks
  172. for i in xrange(3): # check each dimension independently
  173. if not face[i]:
  174. continue
  175. # How much overlap you have with this dimension.
  176. d = (p[i] - np[i]) * face[i]
  177. if d < pad:
  178. continue
  179. for dy in xrange(height): # check each height
  180. op = list(np)
  181. op[1] -= dy
  182. op[i] += face[i]
  183. if tuple(op) not in self.model.world:
  184. continue
  185. p[i] -= (d - pad) * face[i]
  186. if face == (0, -1, 0) or face == (0, 1, 0):
  187. # You are colliding with the ground or ceiling, so stop
  188. # falling / rising.
  189. self.dy = 0
  190. break
  191. return tuple(p)
  192. def on_mouse_press(self, x, y, button, modifiers):
  193. """ Called when a mouse button is pressed. See pyglet docs for button
  194. amd modifier mappings.
  195. Parameters
  196. ----------
  197. x, y : int
  198. The coordinates of the mouse click. Always center of the screen if
  199. the mouse is captured.
  200. button : int
  201. Number representing mouse button that was clicked. 1 = left button,
  202. 4 = right button.
  203. modifiers : int
  204. Number representing any modifying keys that were pressed when the
  205. mouse button was clicked.
  206. """
  207. if self.exclusive:
  208. vector = self.get_sight_vector()
  209. block, previous = self.model.hit_test(self.position, vector)
  210. if (button == mouse.RIGHT) or \
  211. ((button == mouse.LEFT) and (modifiers & key.MOD_CTRL)):
  212. # ON OSX, control + left click = right click.
  213. if previous:
  214. self.model.add_block(previous, self.block)
  215. elif button == pyglet.window.mouse.LEFT and block:
  216. texture = self.model.world[block]
  217. self.model.remove_block(block)
  218. else:
  219. self.set_exclusive_mouse(True)
  220. def on_mouse_motion(self, x, y, dx, dy):
  221. """ Called when the player moves the mouse.
  222. Parameters
  223. ----------
  224. x, y : int
  225. The coordinates of the mouse click. Always center of the screen if
  226. the mouse is captured.
  227. dx, dy : float
  228. The movement of the mouse.
  229. """
  230. if self.exclusive:
  231. m = 0.15
  232. x, y = self.rotation
  233. x, y = x + dx * m, y + dy * m
  234. y = max(-90, min(90, y))
  235. self.rotation = (x, y)
  236. def on_key_press(self, symbol, modifiers):
  237. """ Called when the player presses a key. See pyglet docs for key
  238. mappings.
  239. Parameters
  240. ----------
  241. symbol : int
  242. Number representing the key that was pressed.
  243. modifiers : int
  244. Number representing any modifying keys that were pressed.
  245. """
  246. if symbol == key.W:
  247. self.strafe[0] -= 1
  248. elif symbol == key.S:
  249. self.strafe[0] += 1
  250. elif symbol == key.A:
  251. self.strafe[1] -= 1
  252. elif symbol == key.D:
  253. self.strafe[1] += 1
  254. elif symbol == key.SPACE:
  255. if self.dy == 0:
  256. self.dy = JUMP_SPEED
  257. elif symbol == key.ESCAPE:
  258. self.set_exclusive_mouse(False)
  259. elif symbol == key.TAB:
  260. self.flying = not self.flying
  261. elif symbol in self.num_keys:
  262. index = (symbol - self.num_keys[0]) % len(self.inventory)
  263. self.block = self.inventory[index]
  264. def on_key_release(self, symbol, modifiers):
  265. """ Called when the player releases a key. See pyglet docs for key
  266. mappings.
  267. Parameters
  268. ----------
  269. symbol : int
  270. Number representing the key that was pressed.
  271. modifiers : int
  272. Number representing any modifying keys that were pressed.
  273. """
  274. if symbol == key.W:
  275. self.strafe[0] += 1
  276. elif symbol == key.S:
  277. self.strafe[0] -= 1
  278. elif symbol == key.A:
  279. self.strafe[1] += 1
  280. elif symbol == key.D:
  281. self.strafe[1] -= 1
  282. def on_resize(self, width, height):
  283. """ Called when the window is resized to a new `width` and `height`.
  284. """
  285. # label
  286. self.label.y = height - 10
  287. # reticle
  288. if self.reticle:
  289. self.reticle.delete()
  290. x, y = self.width // 2, self.height // 2
  291. n = 10
  292. self.reticle = pyglet.graphics.vertex_list(4,
  293. ('v2i', (x - n, y, x + n, y, x, y - n, x, y + n))
  294. )
  295. def set_2d(self):
  296. """ Configure OpenGL to draw in 2d.
  297. """
  298. width, height = self.get_size()
  299. glDisable(GL_DEPTH_TEST)
  300. viewport = self.get_viewport_size()
  301. glViewport(0, 0, max(1, viewport[0]), max(1, viewport[1]))
  302. glMatrixMode(GL_PROJECTION)
  303. glLoadIdentity()
  304. glOrtho(0, max(1, width), 0, max(1, height), -1, 1)
  305. glMatrixMode(GL_MODELVIEW)
  306. glLoadIdentity()
  307. def set_3d(self):
  308. """ Configure OpenGL to draw in 3d.
  309. """
  310. width, height = self.get_size()
  311. glEnable(GL_DEPTH_TEST)
  312. viewport = self.get_viewport_size()
  313. glViewport(0, 0, max(1, viewport[0]), max(1, viewport[1]))
  314. glMatrixMode(GL_PROJECTION)
  315. glLoadIdentity()
  316. gluPerspective(65.0, width / float(height), 0.1, 60.0)
  317. glMatrixMode(GL_MODELVIEW)
  318. glLoadIdentity()
  319. x, y = self.rotation
  320. glRotatef(x, 0, 1, 0)
  321. glRotatef(-y, math.cos(math.radians(x)), 0, math.sin(math.radians(x)))
  322. x, y, z = self.position
  323. glTranslatef(-x, -y, -z)
  324. def on_draw(self):
  325. """ Called by pyglet to draw the canvas.
  326. """
  327. self.clear()
  328. self.set_3d()
  329. glColor3d(1, 1, 1)
  330. self.model.batch.draw()
  331. self.draw_focused_block()
  332. self.set_2d()
  333. self.draw_label()
  334. self.draw_reticle()
  335. def draw_focused_block(self):
  336. """ Draw black edges around the block that is currently under the
  337. crosshairs.
  338. """
  339. vector = self.get_sight_vector()
  340. block = self.model.hit_test(self.position, vector)[0]
  341. if block:
  342. x, y, z = block
  343. vertex_data = cube_vertices(x, y, z, 0.51)
  344. glColor3d(0, 0, 0)
  345. glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
  346. pyglet.graphics.draw(24, GL_QUADS, ('v3f/static', vertex_data))
  347. glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
  348. def draw_label(self):
  349. """ Draw the label in the top left of the screen.
  350. """
  351. x, y, z = self.position
  352. self.label.text = '%02d (%.2f, %.2f, %.2f) %d / %d' % (
  353. pyglet.clock.get_fps(), x, y, z,
  354. len(self.model._shown), len(self.model.world))
  355. self.label.draw()
  356. def draw_reticle(self):
  357. """ Draw the crosshairs in the center of the screen.
  358. """
  359. glColor3d(0, 0, 0)
  360. self.reticle.draw(GL_LINES)

最后,一些生成的函数

  1. def setup_fog():
  2. """ Configure the OpenGL fog properties.
  3. """
  4. # Enable fog. Fog "blends a fog color with each rasterized pixel fragment's
  5. # post-texturing color."
  6. glEnable(GL_FOG)
  7. # Set the fog color.
  8. glFogfv(GL_FOG_COLOR, (GLfloat * 4)(0.5, 0.69, 1.0, 0))
  9. # Say we have no preference between rendering speed and quality.
  10. glHint(GL_FOG_HINT, GL_DONT_CARE)
  11. # Specify the equation used to compute the blending factor.
  12. glFogi(GL_FOG_MODE, GL_LINEAR)
  13. # How close and far away fog starts and ends. The closer the start and end,
  14. # the denser the fog in the fog range.
  15. glFogf(GL_FOG_START, 20.0)
  16. glFogf(GL_FOG_END, 60.0)
  17. def setup():
  18. """ Basic OpenGL configuration.
  19. """
  20. # Set the color of "clear", i.e. the sky, in rgba.
  21. glClearColor(0.5, 0.69, 1.0, 1)
  22. # Enable culling (not rendering) of back-facing facets -- facets that aren't
  23. # visible to you.
  24. glEnable(GL_CULL_FACE)
  25. # Set the texture minification/magnification function to GL_NEAREST (nearest
  26. # in Manhattan distance) to the specified texture coordinates. GL_NEAREST
  27. # "is generally faster than GL_LINEAR, but it can produce textured images
  28. # with sharper edges because the transition between texture elements is not
  29. # as smooth."
  30. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
  31. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
  32. setup_fog()
'
运行

最最后,主函数

  1. def main():
  2. window = Window(width=800, height=450, caption='Pyglet', resizable=True)
  3. # Hide the mouse cursor and prevent the mouse from leaving the window.
  4. window.set_exclusive_mouse(True)
  5. setup()
  6. pyglet.app.run()
  7. if __name__ == '__main__':
  8. main()

最最最后,我很了解你,全部的源码

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

最最最最后,效果

最最最最最后的结语

        作者:Unbeatable-NGU from NortH

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/797001
推荐阅读
相关标签
  

闽ICP备14008679号