from __future__ import division |
import sys |
import math |
import random |
import time |
from collections import deque |
from pyglet import image |
from pyglet.gl import * |
from pyglet.graphics import TextureGroup |
from pyglet.window import key, mouse |
from enum import Enum |
TICKS_PER_SEC = 60 |
SIZEG = 10 |
SECTOR_SIZE = 16 |
WALKING_SPEED = 4 |
FLYING_SPEED = 15 |
GRAVITY = 20.0 |
MAX_JUMP_HEIGHT = 1.125 |
JUMP_SPEED = math.sqrt( 2 * GRAVITY * MAX_JUMP_HEIGHT) |
TERMINAL_VELOCITY = 50 |
PLAYER_HEIGHT = 2 |
if sys.version_info[ 0 ] > = 3 : |
xrange = range |
class MAP_ENTRY_TYPE(Enum): |
MAP_EMPTY = 0 , |
MAP_BLOCK = 1 , |
class WALL_DIRECTION(Enum): |
WALL_LEFT = 0 , |
WALL_UP = 1 , |
WALL_RIGHT = 2 , |
WALL_DOWN = 3 , |
class Map (): |
def __init__( self , width, height): |
self .width = width |
self .height = height |
self . map = [[ 0 for x in range ( self .width)] for y in range ( self .height)] |
|
def resetMap( self , value): |
for y in range ( self .height): |
for x in range ( self .width): |
self .setMap(x, y, value) |
|
def setMap( self , x, y, value): |
if value = = MAP_ENTRY_TYPE.MAP_EMPTY: |
self . map [y][x] = 0 |
elif value = = MAP_ENTRY_TYPE.MAP_BLOCK: |
self . map [y][x] = 1 |
|
def isVisited( self , x, y): |
return self . map [y][x] ! = 1 |
def checkAdjacentPos( map , x, y, width, height, checklist): |
directions = [] |
if x > 0 : |
if not map .isVisited( 2 * (x - 1 ) + 1 , 2 * y + 1 ): |
directions.append(WALL_DIRECTION.WALL_LEFT) |
|
if y > 0 : |
if not map .isVisited( 2 * x + 1 , 2 * (y - 1 ) + 1 ): |
directions.append(WALL_DIRECTION.WALL_UP) |
if x < width - 1 : |
if not map .isVisited( 2 * (x + 1 ) + 1 , 2 * y + 1 ): |
directions.append(WALL_DIRECTION.WALL_RIGHT) |
|
if y < height - 1 : |
if not map .isVisited( 2 * x + 1 , 2 * (y + 1 ) + 1 ): |
directions.append(WALL_DIRECTION.WALL_DOWN) |
|
if len (directions): |
direction = random.choice(directions) |
if direction = = WALL_DIRECTION.WALL_LEFT: |
map .setMap( 2 * (x - 1 ) + 1 , 2 * y + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
map .setMap( 2 * x, 2 * y + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
checklist.append((x - 1 , y)) |
elif direction = = WALL_DIRECTION.WALL_UP: |
map .setMap( 2 * x + 1 , 2 * (y - 1 ) + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
map .setMap( 2 * x + 1 , 2 * y, MAP_ENTRY_TYPE.MAP_EMPTY) |
checklist.append((x, y - 1 )) |
elif direction = = WALL_DIRECTION.WALL_RIGHT: |
map .setMap( 2 * (x + 1 ) + 1 , 2 * y + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
map .setMap( 2 * x + 2 , 2 * y + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
checklist.append((x + 1 , y)) |
elif direction = = WALL_DIRECTION.WALL_DOWN: |
map .setMap( 2 * x + 1 , 2 * (y + 1 ) + 1 , MAP_ENTRY_TYPE.MAP_EMPTY) |
map .setMap( 2 * x + 1 , 2 * y + 2 , MAP_ENTRY_TYPE.MAP_EMPTY) |
checklist.append((x, y + 1 )) |
return True |
else : |
return False |
def randomPrim( map , width, height): |
checklist = [] |
checklist.append((random.randint( 0 , width - 1 ), random.randint( 0 , height - 1 ))) |
while len (checklist): |
entry = random.choice(checklist) |
if not checkAdjacentPos( map , entry[ 0 ], entry[ 1 ], width, height, checklist): |
checklist.remove(entry) |
def doRandomPrim( map ): |
map .resetMap(MAP_ENTRY_TYPE.MAP_BLOCK) |
randomPrim( map , ( map .width - 1 ) / / 2 , ( map .height - 1 ) / / 2 ) |
def cube_vertices(x, y, z, n): |
return [ |
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 |
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 |
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 |
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 |
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 |
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 |
] |
def tex_coord(x, y, n = 4 ): |
m = 1.0 / n |
dx = x * m |
dy = y * m |
return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m |
def tex_coords(top, bottom, side): |
top = tex_coord( * top) |
bottom = tex_coord( * bottom) |
side = tex_coord( * side) |
result = [] |
result.extend(top) |
result.extend(bottom) |
result.extend(side * 4 ) |
return result |
TEXTURE_PATH = 'texture.png' |
GRASS = tex_coords(( 0 , 0 ), ( 0 , 0 ), ( 0 , 0 )) |
STONE = tex_coords(( 2 , 1 ), ( 2 , 1 ), ( 2 , 1 )) |
GREEN = tex_coords(( 0 , 1 ), ( 0 , 1 ), ( 0 , 1 )) |
FACES = [ |
( 0 , 1 , 0 ), |
( 0 , - 1 , 0 ), |
( - 1 , 0 , 0 ), |
( 1 , 0 , 0 ), |
( 0 , 0 , 1 ), |
( 0 , 0 , - 1 ), |
] |
def normalize(position): |
x, y, z = position |
x, y, z = ( int ( round (x)), int ( round (y)), int ( round (z))) |
return (x, y, z) |
def sectorize(position): |
x, y, z = normalize(position) |
x, y, z = x / / SECTOR_SIZE, y / / SECTOR_SIZE, z / / SECTOR_SIZE |
return (x, 0 , z) |
class Model( object ): |
def __init__( self ): |
self .batch = pyglet.graphics.Batch() |
self .group = TextureGroup(image.load(TEXTURE_PATH).get_texture()) |
self .world = {} |
self .shown = {} |
self ._shown = {} |
self .sectors = {} |
self .queue = deque() |
self ._initialize() |
def _initialize( self ): |
n = SIZEG |
s = 1 |
y = 0 |
for x in xrange ( - n, n + 1 , s): |
for z in xrange ( - n, n + 1 , s): |
self .add_block((x, y - 2 , z), GRASS, immediate = False ) |
map = Map (n * 2 + 1 , n * 2 + 1 ) |
doRandomPrim( map ) |
for row in xrange ( len ( map . map )): |
for entry in xrange ( len ( map . map [row])): |
if map . map [row][entry] = = 1 : |
self .add_block(( - n + row, - 1 , - n + entry), STONE, immediate = False ) |
self .add_block(( - n + row, 0 , - n + entry), STONE, immediate = False ) |
self .add_block(( - n + row, 1 , - n + entry), STONE, immediate = False ) |
def hit_test( self , position, vector, max_distance = 8 ): |
m = 8 |
x, y, z = position |
dx, dy, dz = vector |
previous = None |
for _ in xrange (max_distance * m): |
key = normalize((x, y, z)) |
if key ! = previous and key in self .world: |
return key, previous |
previous = key |
x, y, z = x + dx / m, y + dy / m, z + dz / m |
return None , None |
def exposed( self , position): |
x, y, z = position |
for dx, dy, dz in FACES: |
if (x + dx, y + dy, z + dz) not in self .world: |
return True |
return False |
def add_block( self , position, texture, immediate = True ): |
if position in self .world: |
self .remove_block(position, immediate) |
self .world[position] = texture |
self .sectors.setdefault(sectorize(position), []).append(position) |
if immediate: |
if self .exposed(position): |
self .show_block(position) |
self .check_neighbors(position) |
def remove_block( self , position, immediate = True ): |
del self .world[position] |
self .sectors[sectorize(position)].remove(position) |
if immediate: |
if position in self .shown: |
self .hide_block(position) |
self .check_neighbors(position) |
def check_neighbors( self , position): |
x, y, z = position |
for dx, dy, dz in FACES: |
key = (x + dx, y + dy, z + dz) |
if key not in self .world: |
continue |
if self .exposed(key): |
if key not in self .shown: |
self .show_block(key) |
else : |
if key in self .shown: |
self .hide_block(key) |
def show_block( self , position, immediate = True ): |
texture = self .world[position] |
self .shown[position] = texture |
if immediate: |
self ._show_block(position, texture) |
else : |
self ._enqueue( self ._show_block, position, texture) |
def _show_block( self , position, texture): |
x, y, z = position |
vertex_data = cube_vertices(x, y, z, 0.5 ) |
texture_data = list (texture) |
self ._shown[position] = self .batch.add( 24 , GL_QUADS, self .group, |
( 'v3f/static' , vertex_data), |
( 't2f/static' , texture_data)) |
def hide_block( self , position, immediate = True ): |
self .shown.pop(position) |
if immediate: |
self ._hide_block(position) |
else : |
self ._enqueue( self ._hide_block, position) |
def _hide_block( self , position): |
self ._shown.pop(position).delete() |
def show_sector( self , sector): |
for position in self .sectors.get(sector, []): |
if position not in self .shown and self .exposed(position): |
self .show_block(position, False ) |
def hide_sector( self , sector): |
for position in self .sectors.get(sector, []): |
if position in self .shown: |
self .hide_block(position, False ) |
def change_sectors( self , before, after): |
before_set = set () |
after_set = set () |
pad = 4 |
for dx in xrange ( - pad, pad + 1 ): |
for dy in [ 0 ]: |
for dz in xrange ( - pad, pad + 1 ): |
if dx * * 2 + dy * * 2 + dz * * 2 > (pad + 1 ) * * 2 : |
continue |
if before: |
x, y, z = before |
before_set.add((x + dx, y + dy, z + dz)) |
if after: |
x, y, z = after |
after_set.add((x + dx, y + dy, z + dz)) |
show = after_set - before_set |
hide = before_set - after_set |
for sector in show: |
self .show_sector(sector) |
for sector in hide: |
self .hide_sector(sector) |
def _enqueue( self , func, * args): |
self .queue.append((func, args)) |
def _dequeue( self ): |
func, args = self .queue.popleft() |
func( * args) |
def process_queue( self ): |
start = time.perf_counter() |
while self .queue and time.perf_counter() - start < 1.0 / TICKS_PER_SEC: |
self ._dequeue() |
def process_entire_queue( self ): |
while self .queue: |
self ._dequeue() |
class Window(pyglet.window.Window): |
def __init__( self , * args, * * kwargs): |
super (Window, self ).__init__( * args, * * kwargs) |
self .exclusive = False |
self .flying = False |
self .strafe = [ 0 , 0 ] |
self .position = ( 0 , 0 , 0 ) |
self .rotation = ( 0 , 0 ) |
self .sector = None |
self .reticle = None |
self .dy = 0 |
self .num_keys = [ |
key._1, key._2, key._3, key._4, key._5, |
key._6, key._7, key._8, key._9, key._0] |
self .model = Model() |
self .enderx = random.randint( - SIZEG, SIZEG) |
self .enderz = random.randint( - SIZEG, SIZEG) |
if ( 0 , 0 , 0 ) in self .model.world: |
self .model.remove_block(( 0 , - 1 , 0 ), immediate = False ) |
self .model.remove_block(( 0 , 0 , 0 ), immediate = False ) |
self .model.remove_block(( 0 , 1 , 0 ), immediate = False ) |
if ( self .enderx, 0 , self .enderz) in self .model.world: |
self .model.remove_block(( self .enderx, - 1 , self .enderz), immediate = False ) |
self .model.remove_block(( self .enderx, 0 , self .enderz), immediate = False ) |
self .model.remove_block(( self .enderx, 1 , self .enderz), immediate = False ) |
self .model.add_block(( self .enderx, - 1 , self .enderz), GREEN, immediate = False ) |
self .label = pyglet.text.Label(' ', font_name=' Arial', font_size = 18 , |
x = 10 , y = self .height - 10 , anchor_x = 'left' , anchor_y = 'top' , |
color = ( 0 , 0 , 0 , 255 )) |
pyglet.clock.schedule_interval( self .update, 1.0 / TICKS_PER_SEC) |
def set_exclusive_mouse( self , exclusive): |
super (Window, self ).set_exclusive_mouse(exclusive) |
self .exclusive = exclusive |
def get_sight_vector( self ): |
x, y = self .rotation |
m = math.cos(math.radians(y)) |
dy = math.sin(math.radians(y)) |
dx = math.cos(math.radians(x - 90 )) * m |
dz = math.sin(math.radians(x - 90 )) * m |
return (dx, dy, dz) |
def get_motion_vector( self ): |
if any ( self .strafe): |
x, y = self .rotation |
strafe = math.degrees(math.atan2( * self .strafe)) |
y_angle = math.radians(y) |
x_angle = math.radians(x + strafe) |
if self .flying: |
m = math.cos(y_angle) |
dy = math.sin(y_angle) |
if self .strafe[ 1 ]: |
dy = 0.0 |
m = 1 |
if self .strafe[ 0 ] > 0 : |
dy * = - 1 |
dx = math.cos(x_angle) * m |
dz = math.sin(x_angle) * m |
else : |
dy = 0.0 |
dx = math.cos(x_angle) |
dz = math.sin(x_angle) |
else : |
dy = 0.0 |
dx = 0.0 |
dz = 0.0 |
return (dx, dy, dz) |
def update( self , dt): |
self .model.process_queue() |
sector = sectorize( self .position) |
if sector ! = self .sector: |
self .model.change_sectors( self .sector, sector) |
if self .sector is None : |
self .model.process_entire_queue() |
self .sector = sector |
m = 8 |
dt = min (dt, 0.2 ) |
for _ in xrange (m): |
self ._update(dt / m) |
def _update( self , dt): |
speed = FLYING_SPEED if self .flying else WALKING_SPEED |
d = dt * speed |
dx, dy, dz = self .get_motion_vector() |
dx, dy, dz = dx * d, dy * d, dz * d |
if not self .flying: |
self .dy - = dt * GRAVITY |
self .dy = max ( self .dy, - TERMINAL_VELOCITY) |
dy + = self .dy * dt |
x, y, z = self .position |
x, y, z = self .collide((x + dx, y + dy, z + dz), PLAYER_HEIGHT) |
self .position = (x, y, z) |
def collide( self , position, height): |
pad = 0.25 |
p = list (position) |
np = normalize(position) |
for face in FACES: # check all surrounding blocks |
for i in xrange ( 3 ): # check each dimension independently |
if not face[i]: |
continue |
d = (p[i] - np[i]) * face[i] |
if d < pad: |
continue |
for dy in xrange (height): # check each height |
op = list (np) |
op[ 1 ] - = dy |
op[i] + = face[i] |
if tuple (op) not in self .model.world: |
continue |
p[i] - = (d - pad) * face[i] |
if face = = ( 0 , - 1 , 0 ) or face = = ( 0 , 1 , 0 ): |
self .dy = 0 |
break |
return tuple (p) |
def on_mouse_press( self , x, y, button, modifiers): |
if self .exclusive: |
vector = self .get_sight_vector() |
block, previous = self .model.hit_test( self .position, vector) |
if button = = pyglet.window.mouse.LEFT and block: |
texture = self .model.world[block] |
if texture = = GREEN: |
self .label.text = "你赢了!" |
self .flying = True |
else : |
self .set_exclusive_mouse( True ) |
def on_mouse_motion( self , x, y, dx, dy): |
if self .exclusive: |
m = 0.15 |
x, y = self .rotation |
x, y = x + dx * m, y + dy * m |
y = max ( - 90 , min ( 90 , y)) |
self .rotation = (x, y) |
def on_key_press( self , symbol, modifiers): |
if symbol = = key.W: |
self .strafe[ 0 ] - = 1 |
elif symbol = = key.S: |
self .strafe[ 0 ] + = 1 |
elif symbol = = key.A: |
self .strafe[ 1 ] - = 1 |
elif symbol = = key.D: |
self .strafe[ 1 ] + = 1 |
elif symbol = = key.SPACE: |
if self .dy = = 0 : |
self .dy = JUMP_SPEED |
elif symbol = = key.ESCAPE: |
self .set_exclusive_mouse( False ) |
def on_key_release( self , symbol, modifiers): |
if symbol = = key.W: |
self .strafe[ 0 ] + = 1 |
elif symbol = = key.S: |
self .strafe[ 0 ] - = 1 |
elif symbol = = key.A: |
self .strafe[ 1 ] + = 1 |
elif symbol = = key.D: |
self .strafe[ 1 ] - = 1 |
def on_resize( self , width, height): |
self .label.y = height - 10 |
if self .reticle: |
self .reticle.delete() |
x, y = self .width / / 2 , self .height / / 2 |
n = 10 |
self .reticle = pyglet.graphics.vertex_list( 4 , |
( 'v2i' , (x - n, y, x + n, y, x, y - n, x, y + n)) |
) |
def set_2d( self ): |
width, height = self .get_size() |
glDisable(GL_DEPTH_TEST) |
viewport = self .get_viewport_size() |
glViewport( 0 , 0 , max ( 1 , viewport[ 0 ]), max ( 1 , viewport[ 1 ])) |
glMatrixMode(GL_PROJECTION) |
glLoadIdentity() |
glOrtho( 0 , max ( 1 , width), 0 , max ( 1 , height), - 1 , 1 ) |
glMatrixMode(GL_MODELVIEW) |
glLoadIdentity() |
def set_3d( self ): |
width, height = self .get_size() |
glEnable(GL_DEPTH_TEST) |
viewport = self .get_viewport_size() |
glViewport( 0 , 0 , max ( 1 , viewport[ 0 ]), max ( 1 , viewport[ 1 ])) |
glMatrixMode(GL_PROJECTION) |
glLoadIdentity() |
gluPerspective( 65.0 , width / float (height), 0.1 , 60.0 ) |
glMatrixMode(GL_MODELVIEW) |
glLoadIdentity() |
x, y = self .rotation |
glRotatef(x, 0 , 1 , 0 ) |
glRotatef( - y, math.cos(math.radians(x)), 0 , math.sin(math.radians(x))) |
x, y, z = self .position |
glTranslatef( - x, - y, - z) |
def on_draw( self ): |
self .clear() |
self .set_3d() |
glColor3d( 1 , 1 , 1 ) |
self .model.batch.draw() |
self .draw_focused_block() |
self .set_2d() |
self .draw_label() |
self .draw_reticle() |
def draw_focused_block( self ): |
vector = self .get_sight_vector() |
block = self .model.hit_test( self .position, vector)[ 0 ] |
if block: |
x, y, z = block |
vertex_data = cube_vertices(x, y, z, 0.51 ) |
glColor3d( 0 , 0 , 0 ) |
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) |
pyglet.graphics.draw( 24 , GL_QUADS, ( 'v3f/static' , vertex_data)) |
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) |
def draw_label( self ): |
x, y, z = self .position |
if self .label.text ! = "你赢了!" : |
self .label.text = '当前坐标:(%.1f, %.1f), 目标:(%.1f, %.1f)' % (x, z, self .enderx, self .enderz) |
self .label.draw() |
def draw_reticle( self ): |
glColor3d( 0 , 0 , 0 ) |
self .reticle.draw(GL_LINES) |
def setup_fog(): |
glEnable(GL_FOG) |
glFogfv(GL_FOG_COLOR, (GLfloat * 4 )( 1.0 , 1.0 , 0.8 , 1 )) |
glHint(GL_FOG_HINT, GL_DONT_CARE) |
glFogi(GL_FOG_MODE, GL_LINEAR) |
glFogf(GL_FOG_START, 30.0 ) |
glFogf(GL_FOG_END, 60.0 ) |
def setup(): |
glClearColor( 1.0 , 1.0 , 0.8 , 1 ) |
glEnable(GL_CULL_FACE) |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) |
setup_fog() |
def main(): |
window = Window(width = 800 , height = 600 , caption = 'Zifan-Python-Maze' , resizable = True ) |
window.set_exclusive_mouse( True ) |
setup() |
pyglet.app.run() |
if __name__ = = '__main__' : |
main() |