Python: How do I generate minecraft style terrain with pyglet? - python

I'm currently trying to make a Minecraft clone using pyglet and python for fun, and I found a nice tutorial which I used to create a player class with movement, and a 3D block that generates in the scene.
Now I want to create some sort of a terrain, I read about terrain generating and I stumbled upon a function called "noise function" which seems to fit nicely with one I'm trying to do. sadly I don't really know how to implement it. :(
At first I tried to generate a flat terrain by creating a function in the Model class which contains the code that creates a cube, and then I create a loop that generates all the numbers between 1 and 20 for example, and use them as values to the cube function. but it didn't work so i had to remove it :(
I do think that it might be too soon to implement a terrain with the noise function seeing the stage I'm currently in. so creating an endless flat terrain that do work will be good as well :D
If you want to check the program, You'll need to press 'E' when you run the code to enable a mouse lock, which will let you to move the mouse and the player in the scene.
Here is the code I have:
from pyglet.gl import *
from pyglet.window import key
import math
import random
from random import *
class Model:
def get_tex(self, file):
tex = pyglet.image.load(file).texture
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
return pyglet.graphics.TextureGroup(tex)
def __init__(self):
self.top = self.get_tex('grass_top.png')
self.side = self.get_tex('grass_side.png')
self.bottom = self.get_tex('dirt.png')
self.batch = pyglet.graphics.Batch()
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1,))
x, y, z = randint(0, 5), randint(0, 5), -1
X, Y, Z = x+1, y+1, z+1
self.batch.add(4, GL_QUADS, self.side, ('v3f', (x, y, z, x, y, Z, x, Y, Z, x, Y, z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (X, y, Z, X, y, z, X, Y, z, X, Y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.bottom, ('v3f', (x, y, z, X, y, z, X, y, Z, x, y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, self.top, ('v3f', (x, Y, Z, X, Y, Z, X, Y, z, x, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (X, y, z, x, y, z, x, Y, z, X, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, self.side, ('v3f', (x, y, Z, X, y, Z, X, Y, Z, x, Y, Z, )), tex_coords)
def draw(self):
self.batch.draw()
class Player:
def __init__(self, pos=(0, 0, 0), rot=(0, 0)):
self.pos = list(pos)
self.rot = list(rot)
def mouse_motion(self, dx, dy):
dx /= 8
dy /= 8
self.rot[0] += dy
self.rot[1] -= dx
if self.rot[0] > 90:
self.rot[0] = 90
elif self.rot[0] < -90:
self.rot[0] = -90
def update(self, dt, keys):
s = dt*10
rotation_y = -self.rot[1]/180*math.pi
dx, dz = s*math.sin(rotation_y), s*math.cos(rotation_y)
if keys[key.W]:
self.pos[0] += dx
self.pos[2] -= dz
if keys[key.S]:
self.pos[0] -= dx
self.pos[2] += dz
if keys[key.A]:
self.pos[0] -= dz
self.pos[2] -= dx
if keys[key.D]:
self.pos[0] += dz
self.pos[2] += dx
if keys[key.SPACE]:
self.pos[1] += s
if keys[key.LSHIFT]:
self.pos[1] -= s
class Window(pyglet.window.Window):
def push(self, pos, rot):
glPushMatrix()
glRotatef(-rot[0], 1, 0, 0)
glRotatef(-rot[1], 0, 1, 0)
glTranslatef(-pos[0], -pos[1], -pos[2],)
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluOrtho2D(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width / self.height, 0.05, 1000)
self.Model()
def setLock(self, state): self.lock = state; self.set_exclusive_mouse(state)
lock = False; mouse_lock = property(lambda self: self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300, 200)
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
self.model = Model()
self.player = Player((0.5, 1.5, 1.5), (-30, 0))
def on_mouse_motion(self, x, y, dx, dy):
if self.mouse_lock:
self.player.mouse_motion(dx, dy)
def on_key_press(self, KEY, MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.E:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.push(self.player.pos, self.player.rot)
self.model.draw()
glPopMatrix()
if __name__ == '__main__':
window = Window(width=854, height=480, caption='Minecraft', resizable=True)
glClearColor(0.5, 0.7, 1, 1)
glEnable(GL_DEPTH_TEST)
# glEnable(GL_CULL_FACE)
pyglet.app.run()
My project use's some images to create the dirt texture. so here is a link to a website which has the images and the main program which you can see above you:
http://www.mediafire.com/file/7iolhmh1hqj9516/Basic+Pyglet+Cube.rar
I already tried to generate more then one block using a list and recalling a function

You could create a function to show a block at the given position and with the texture texture:
def show_block(self, position, texture):
top = texture[0]
side = texture[1]
bottom = texture[2]
x, y, z = position
X, Y, Z = x+1,y+1, z+1
tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1,))
self.batch.add(4, GL_QUADS, side, ('v3f', (x, y, z, x, y, Z, x, Y, Z, x, Y, z,)), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (X, y, Z, X, y, z, X, Y, z, X, Y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, bottom, ('v3f', (x, y, z, X, y, z, X, y, Z, x, y, Z,)), tex_coords)
self.batch.add(4, GL_QUADS, top, ('v3f', (x, Y, Z, X, Y, Z, X, Y, z, x, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (X, y, z, x, y, z, x, Y, z, X, Y, z, )), tex_coords)
self.batch.add(4, GL_QUADS, side, ('v3f', (x, y, Z, X, y, Z, X, Y, Z, x, Y, Z, )), tex_coords)
Then you could use perlin to generate terrain:
class Perlin:
def __call__(self,x,y): return (self.noise(x*self.f,y*self.f)+1)/2
def __init__(self,seed=None):
self.f = 15/512; self.m = 65535; p = list(range(self.m))
if seed: random.seed(seed)
random.shuffle(p); self.p = p+p
def fade(self,t): return t*t*t*(t*(t*6-15)+10)
def lerp(self,t,a,b): return a+t*(b-a)
def grad(self,hash,x,y,z):
h = hash&15; u = y if h&8 else x
v = (x if h==12 or h==14 else z) if h&12 else y
return (u if h&1 else -u)+(v if h&2 else -v)
def noise(self,x,y,z=0):
p,fade,lerp,grad = self.p,self.fade,self.lerp,self.grad
xf,yf,zf = math.floor(x),math.floor(y),math.floor(z)
X,Y,Z = xf%self.m,yf%self.m,zf%self.m
x-=xf; y-=yf; z-=zf
u,v,w = fade(x),fade(y),fade(z)
A = p[X ]+Y; AA = p[A]+Z; AB = p[A+1]+Z
B = p[X+1]+Y; BA = p[B]+Z; BB = p[B+1]+Z
return lerp(w,lerp(v,lerp(u,grad(p[AA],x,y,z),grad(p[BA],x-1,y,z)),lerp(u,grad(p[AB],x,y-1,z),grad(p[BB],x-1,y-1,z))),
lerp(v,lerp(u,grad(p[AA+1],x,y,z-1),grad(p[BA+1],x-1,y,z-1)),lerp(u,grad(p[AB+1],x,y-1,z-1),grad(p[BB+1],x-1,y-1,z-1))))
After all these changes, your code should look somewhat like this:
from pyglet.gl import *
from pyglet.window import key,mouse
from collections import deque
import sys, os, time, math, random
class Perlin:
def __call__(self,x,y): return int(sum(self.noise(x*s,y*s)*h for s,h in self.perlins)*self.avg)
def __init__(self):
self.m = 65536; p = list(range(self.m)); random.shuffle(p); self.p = p+p
p = self.perlins = tuple((1/i,i) for i in (16,20,22,31,32,64,512) for j in range(2))
self.avg = 8*len(p)/sum(f+i for f,i in p)
def fade(self,t): return t*t*t*(t*(t*6-15)+10)
def lerp(self,t,a,b): return a+t*(b-a)
def grad(self,hash,x,y,z):
h = hash&15; u = y if h&8 else x
v = (x if h==12 or h==14 else z) if h&12 else y
return (u if h&1 else -u)+(v if h&2 else -v)
def noise(self,x,y,z=0):
p,fade,lerp,grad = self.p,self.fade,self.lerp,self.grad
xf,yf,zf = math.floor(x),math.floor(y),math.floor(z)
X,Y,Z = xf%self.m,yf%self.m,zf%self.m
x-=xf; y-=yf; z-=zf
u,v,w = fade(x),fade(y),fade(z)
A = p[X ]+Y; AA = p[A]+Z; AB = p[A+1]+Z
B = p[X+1]+Y; BA = p[B]+Z; BB = p[B+1]+Z
return lerp(w,lerp(v,lerp(u,grad(p[AA],x,y,z),grad(p[BA],x-1,y,z)),lerp(u,grad(p[AB],x,y-1,z),grad(p[BB],x-1,y-1,z))),
lerp(v,lerp(u,grad(p[AA+1],x,y,z-1),grad(p[BA+1],x-1,y,z-1)),lerp(u,grad(p[AB+1],x,y-1,z-1),grad(p[BB+1],x-1,y-1,z-1))))
class Model:
alpha_textures = 'leaves_oak','tall_grass'
def load_textures(self):
t = self.texture = {}; self.texture_dir = {}; dirs = ['textures']
while dirs:
dir = dirs.pop(0); textures = os.listdir(dir)
for file in textures:
if os.path.isdir(dir+'/'+file): dirs+=[dir+'/'+file]
else:
n = file.split('.')[0]; self.texture_dir[n] = dir; image = pyglet.image.load(dir+'/'+file)
transparent = n in self.alpha_textures
texture = image.texture if transparent else image.get_mipmapped_texture()
self.texture[n] = pyglet.graphics.TextureGroup(texture)
if not transparent: glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST)
self.block = {}; self.ids = []; done = []
items = sorted(self.texture_dir.items(),key=lambda i:i[0])
for name,dir in items:
n = name.split(' ')[0]
if n in done: continue
done+=[n]
if dir.startswith('textures/blocks'):
self.ids+=[n]
if dir=='textures/blocks': self.block[n] = t[n],t[n],t[n],t[n],t[n],t[n]
elif dir=='textures/blocks/tbs': self.block[n] = t[n+' s'],t[n+' s'],t[n+' b'],t[n+' t'],t[n+' s'],t[n+' s']
elif dir=='textures/blocks/ts': self.block[n] = t[n+' s'],t[n+' s'],t[n+' t'],t[n+' t'],t[n+' s'],t[n+' s']
self.ids+=['water']
flow,still = t['water_flow'],t['water_still']
self.block['water'] = flow,flow,still,still,flow,flow
def draw(self):
glEnable(GL_ALPHA_TEST); self.opaque.draw(); glDisable(GL_ALPHA_TEST)
glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); self.transparent.draw()
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); self.transparent.draw()
def update(self,dt):
self.cubes.water.update(dt)
def __init__(self):
self.opaque = pyglet.graphics.Batch()
self.transparent = pyglet.graphics.Batch()
self.load_textures()
self.cubes = CubeHandler(self)
perlin = Perlin()
for x in range(64):
for z in range(64):
y = perlin(x,z)
self.cubes.add((x,-y,-z),'grass')
for i in range(1,3): self.cubes.add((x,-i-y,-z),'dirt')
for cube in self.cubes.cubes.values(): self.cubes.update_cube(cube)
class Water:
def __init__(self,transparent):
self.transparent = transparent
self.time = {'still':TimeLoop(32),'flow':TimeLoop(32)}
self.coords = {'still':[],'flow':[]}; self.still_faces = {}; self.flow_faces = {}
for i in range(32-1,-1,-1):
y0 = i/16; y1 = (i+1)/16; self.coords['still'] += [[0,y0, 1,y0, 1,y1, 0,y1]]
y0 = i/32; y1 = (i+1)/32; self.coords['flow'] += [[0,y0, 1,y0, 1,y1, 0,y1]]
a,b = self.time['still'],self.time['flow']; self.t = b,b,a,a,b,b
a,b = self.coords['still'],self.coords['flow']; self.c = b,b,a,a,b,b
def update(self,dt):
if self.time['still'].update(dt*0.5):
for face,i in self.still_faces.items(): face.tex_coords = self.c[i][self.t[i].int]
if self.time['flow'].update(dt):
for face,i in self.flow_faces.items(): face.tex_coords = self.c[i][self.t[i].int]
def show(self,v,t,i):
face = self.transparent.add(4,GL_QUADS,t,('v3f',v),('t2f',self.c[i][0]))
faces = self.still_faces if i==2 or i==3 else self.flow_faces
faces[face] = i; return face
class CubeHandler:
def __init__(self,model):
self.model = model
self.opaque,self.transparent = model.opaque,model.transparent
self.block,self.alpha_textures = model.block,model.alpha_textures
self.water = Water(self.transparent)
self.cubes = {}
def hit_test(self,p,vec,dist=256):
if normalize(p) in self.cubes: return None,None
m = 8; x,y,z = p; dx,dy,dz = vec
dx/=m; dy/=m; dz/=m; prev = None
for i in range(dist*m):
key = normalize((x,y,z))
if key in self.cubes: return key,prev
prev = key
x,y,z = x+dx,y+dy,z+dz
return None,None
def show(self,v,t,i): return self.opaque.add(4,GL_QUADS,t,('v3f',v),('t2f',(0,0, 1,0, 1,1, 0,1)))
def update_cube(self,cube):
if not any(cube.shown.values()): return
show = self.water.show if cube.name=='water' else self.show
v = cube_vertices(cube.p)
f = 'left','right','bottom','top','back','front'
for i in (0,1,2,3,4,5):
if cube.shown[f[i]] and not cube.faces[f[i]]: cube.faces[f[i]] = show(v[i],cube.t[i],i)
def set_adj(self,cube,adj,state):
x,y,z = cube.p; X,Y,Z = adj; d = X-x,Y-y,Z-z; f = 'left','right','bottom','top','back','front'
for i in (0,1,2):
if d[i]:
j = i+i; a,b = [f[j+1],f[j]][::d[i]]; cube.shown[a] = state
if not state and cube.faces[a]: cube.faces[a].delete(); face = cube.faces[a]; cube.faces[a] = None; self.remove_water(face)
def add(self,p,t,now=False):
if p in self.cubes: return
cube = self.cubes[p] = Cube(t,p,self.block[t],'alpha' if t in self.alpha_textures else 'blend' if t=='water' else 'solid')
for adj in adjacent(*cube.p):
if adj not in self.cubes: self.set_adj(cube,adj,True)
else:
a,b = cube.type,self.cubes[adj].type
if a==b and (a=='solid' or b=='blend'): self.set_adj(self.cubes[adj],cube.p,False)
elif a!='blend' and b!='solid': self.set_adj(self.cubes[adj],cube.p,False); self.set_adj(cube,adj,True)
if now: self.update_cube(cube)
def remove_water(self,face):
if face in self.water.still_faces: del self.water.still_faces[face]
elif face in self.water.flow_faces: del self.water.flow_faces[face]
def remove(self,p):
if p not in self.cubes: return
cube = self.cubes.pop(p)
for side,face in cube.faces.items():
if face: face.delete()
self.remove_water(face)
for adj in adjacent(*cube.p):
if adj in self.cubes:
self.set_adj(self.cubes[adj],cube.p,True)
self.update_cube(self.cubes[adj])
class Cube:
def __init__(self,name,p,t,typ):
self.name,self.p,self.t,self.type = name,p,t,typ
self.shown = {'left':False,'right':False,'bottom':False,'top':False,'back':False,'front':False}
self.faces = {'left':None,'right':None,'bottom':None,'top':None,'back':None,'front':None}
class TimeLoop:
def __init__(self,duration): self.unit = 0; self.int = 0; self.duration = duration; self.prev = 0
def update(self,dt):
self.unit+=dt; self.unit-=int(self.unit); self.int = int(self.unit*self.duration)
if self.prev!=self.int: self.prev = self.int; return True
def cube_vertices(pos,n=0.5):
x,y,z = pos; v = tuple((x+X,y+Y,z+Z) for X in (-n,n) for Y in (-n,n) for Z in (-n,n))
return tuple(tuple(k for j in i for k in v[j]) for i in ((0,1,3,2),(5,4,6,7),(0,4,5,1),(3,7,6,2),(4,0,2,6),(1,5,7,3)))
def flatten(lst): return sum(map(list,lst),[])
def normalize(pos): x,y,z = pos; return round(x),round(y),round(z)
def adjacent(x,y,z):
for p in ((x-1,y,z),(x+1,y,z),(x,y-1,z),(x,y+1,z),(x,y,z-1),(x,y,z+1)): yield p
class Player:
WALKING_SPEED = 5
FLYING_SPEED = 15
GRAVITY = 20
JUMP_SPEED = (2*GRAVITY)**.5
TERMINAL_VELOCITY = 50
def push(self): glPushMatrix(); glRotatef(-self.rot[0],1,0,0); glRotatef(self.rot[1],0,1,0); glTranslatef(-self.pos[0],-self.pos[1],-self.pos[2])
def __init__(self,cubes,pos=(0,0,0),rot=(0,0)):
self.cubes = cubes
self.pos,self.rot = list(pos),list(rot)
self.flying = True
self.noclip = True
self.dy = 0
def mouse_motion(self,dx,dy):
dx/=8; dy/=8; self.rot[0]+=dy; self.rot[1]+=dx
if self.rot[0]>90: self.rot[0] = 90
elif self.rot[0]<-90: self.rot[0] = -90
def jump(self):
if not self.dy: self.dy = self.JUMP_SPEED
def get_sight_vector(self):
rotX,rotY = self.rot[0]/180*math.pi,self.rot[1]/180*math.pi
dx,dz = math.sin(rotY),-math.cos(rotY)
dy,m = math.sin(rotX),math.cos(rotX)
return dx*m,dy,dz*m
def update(self,dt,keys):
DX,DY,DZ = 0,0,0; s = dt*self.FLYING_SPEED if self.flying else dt*self.WALKING_SPEED
rotY = self.rot[1]/180*math.pi
dx,dz = s*math.sin(rotY),s*math.cos(rotY)
if self.flying:
if keys[key.LSHIFT]: DY-=s
if keys[key.SPACE]: DY+=s
elif keys[key.SPACE]: self.jump()
if keys[key.W]: DX+=dx; DZ-=dz
if keys[key.S]: DX-=dx; DZ+=dz
if keys[key.A]: DX-=dz; DZ-=dx
if keys[key.D]: DX+=dz; DZ+=dx
if dt<0.2:
dt/=10; DX/=10; DY/=10; DZ/=10
for i in range(10): self.move(dt,DX,DY,DZ)
def move(self,dt,dx,dy,dz):
if not self.flying:
self.dy -= dt*self.GRAVITY
self.dy = max(self.dy,-self.TERMINAL_VELOCITY)
dy += self.dy*dt
x,y,z = self.pos
self.pos = self.collide((x+dx,y+dy,z+dz))
def collide(self,pos):
if self.noclip and self.flying: return pos
pad = 0.25; p = list(pos); np = normalize(pos)
for face in ((-1,0,0),(1,0,0),(0,-1,0),(0,1,0),(0,0,-1),(0,0,1)):
for i in (0,1,2):
if not face[i]: continue
d = (p[i]-np[i])*face[i]
if d<pad: continue
for dy in (0,1):
op = list(np); op[1]-=dy; op[i]+=face[i]; op = tuple(op)
if op in self.cubes:
p[i]-=(d-pad)*face[i]
if face[1]: self.dy = 0
break
return tuple(p)
class Window(pyglet.window.Window):
def set2d(self): glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0,self.width,0,self.height)
def set3d(self): glLoadIdentity(); gluPerspective(65,self.width/self.height,0.1,320); glMatrixMode(GL_MODELVIEW); glLoadIdentity()
def on_resize(self,w,h): glViewport(0,0,w,h); self.load_vertex_lists(w,h)
def setLock(self,state): self.set_exclusive_mouse(state); self.mouseLock = state
mouseLock = False; mouse_lock = property(lambda self:self.mouseLock,setLock)
def __init__(self,*args):
super().__init__(*args)
pyglet.clock.schedule(self.update)
self.keys = pyglet.window.key.KeyStateHandler()
self.push_handlers(self.keys)
self.model = Model()
self.player = Player(self.model.cubes.cubes)
self.mouse_lock = True
self.fps = pyglet.clock.ClockDisplay()
self.reticle = None
self.block = 0
def load_vertex_lists(self,w,h):
x,y = w/2,h/2; m = 10
if self.reticle: self.reticle.delete()
self.reticle = pyglet.graphics.vertex_list(4,('v2f',(x-m,y, x+m,y, x,y-m, x,y+m)),('c3f',(0,0,0, 0,0,0, 0,0,0, 0,0,0)))
self.water = pyglet.graphics.vertex_list(4,('v2f',(0,0, w,0, w,h, 0,h)),('c4f',[0.15,0.3,1,0.5]*4))
def update(self,dt):
self.player.update(dt,self.keys)
self.model.update(dt)
def on_mouse_motion(self,x,y,dx,dy):
if self.mouse_lock: self.player.mouse_motion(dx,dy)
def on_mouse_press(self,x,y,button,MOD):
if button == mouse.LEFT:
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[0]
if block: self.model.cubes.remove(block)
elif button == mouse.RIGHT:
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[1]
if block: self.model.cubes.add(block,self.model.ids[self.block],True)
def on_key_press(self,KEY,MOD):
if KEY == key.ESCAPE: self.dispatch_event('on_close')
elif KEY == key.E: self.mouse_lock = not self.mouse_lock
elif KEY == key.F: self.player.flying = not self.player.flying; self.player.dy = 0; self.player.noclip = True
elif KEY == key.C: self.player.noclip = not self.player.noclip
elif KEY == key.UP: self.block = (self.block-1)%len(self.model.ids)
elif KEY == key.DOWN: self.block = (self.block+1)%len(self.model.ids)
def on_draw(self):
self.clear()
self.set3d()
self.player.push()
self.model.draw()
block = self.model.cubes.hit_test(self.player.pos,self.player.get_sight_vector())[0]
if block:
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); glColor3d(0,0,0)
pyglet.graphics.draw(24,GL_QUADS,('v3f/static',flatten(cube_vertices(block,0.52))))
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glColor3d(1,1,1)
glPopMatrix()
self.set2d()
cubes = self.model.cubes.cubes; p = normalize(self.player.pos)
if p in cubes and cubes[p].name=='water': self.water.draw(GL_POLYGON)
self.fps.draw()
self.reticle.draw(GL_LINES)
def main():
window = Window(800,600,'Minecraft',True)
glClearColor(0.5,0.7,1,1)
glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glAlphaFunc(GL_GEQUAL,1)
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
pyglet.app.run()
if __name__ == '__main__':
main()

Related

Pyglet texture doesn't cover the square

Im programming a simple pyglet example in pyglet 1.3.0 but i have a problem. I have also tested other pyglet versions but the problem still there.
No error is displayed but the texture only appears in a part of the square. I'm using python 3.7.
Here is the code:
from pyglet.gl import *
from pyglet.window import key
import resources
import math
class Model:
def get_tex(self, file):
tex = pyglet.image.load(file).texture
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
return pyglet.graphics.TextureGroup(tex)
def __init__(self):
self.top = self.get_tex("grass_tex.jpg")
self.bottom = self.get_tex("grass_tex.jpg")
self.side = self.get_tex("terrain_tex.jpg")
self.batch = pyglet.graphics.Batch()
tex_coords = ("t2f", (0,0, 1,0, 1,1, 0,1, ))
#color = ("c3f", (1, 1, 1)*4)
x, y, z = 0, 0, -1
X, Y, Z = x+1, y+1, z+1
self.batch.add(4, GL_QUADS, self.top, ("v3f", (x,y,z, X,y,z, X,Y,z, x,Y,z, )), tex_coords)
def draw(self):
self.batch.draw()
class Player:
def __init__(self):
self.pos = [0, 0, 0]
self.rot = [0, 0]
def update(self, dt, keys):
pass
class Window(pyglet.window.Window):
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluOrtho2D(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width/self.height, 0.05, 1000)
self.Model()
def setLock(self, state):
self.lock = state
self.set_exclusive_mouse(state)
lock = False
mouse_lock = property(lambda self:self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model = Model()
self.player = Player()
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
def on_key_press(self, KEY, MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.SPACE:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.model.draw()
if __name__ == "__main__":
window = Window(width=400, height=300, caption="gamelearn2", resizable=False)
glClearColor(0.5, 0.7, 1, 1)
pyglet.app.run()
And this is the error:
When you use a perspective projection gluPerspective, then the (0, 0) is in the center of the viewport.
You have to draw the quad with the texture from (-1, -1) to (1, 1) and at a depth of 1 (z = -1).
x, y, z = -1, -1, -1
X, Y = 1, 1
self.batch.add(4, GL_QUADS, self.top, ("v3f", (x,y,z, X,y,z, X,Y,z, x,Y,z, )), tex_coords)
Furthermore use a field of view angle of 90°:
class Window(pyglet.window.Window):
# [...]
def set3d(self):
self.Projection()
gluPerspective(90, self.width/self.height, 0.05, 1000)
self.Model()

Stop GL_TRIANGLE_STRIP from connecting last and first vertex?

I have been trying to build a Terrain Visualizer from OpenGL and using a height map from simplex noise. I have the generator all sorted and it produces both colored and non-colored images, I only want to visualized the colored ones. However there is this weird bump thing, that I believe is a result of GL_TRIANGLE_STRIP.
Picture of the artifact, oddities outlined in red:
My best guess is GL_TRIANGLE_STRIP attaching the first and last vertex, but I do not know.
Here is my code:
import pyglet
from pyglet.gl import *
from pyglet.window import key
import math
from PIL import Image
class Model:
def get_points_in_list(self, fp):
img = Image.open(fp)
self.points = [[0 for x in range(img.width)] for y in range(img.height)]
for y in range(img.height):
for x in range(img.width):
self.points[y][x] = ((x, img.getpixel((x, y))[3], y), img.getpixel((x, y)))
def add_points_to_batch(self):
ysiz = len(self.points)
xsiz = len(self.points[0])
# color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]
for yy in range(ysiz-1):
for xx in range(xsiz):
pos = self.points[yy][xx][0]
x, y, z = pos[0], pos[1], pos[2]
pos1 = self.points[yy+1][xx][0]
X, Y, Z = pos1[0], pos1[1], pos1[2]
color1 = (self.points[yy][xx][1][0], self.points[yy][xx][1][1], self.points[yy][xx][1][2])
color2 = (self.points[yy+1][xx][1][0], self.points[yy+1][xx][1][1], self.points[yy+1][xx][1][2])
self.batch.add(2, GL_TRIANGLE_STRIP, None,
('v3f', (x, y, z, X, Y, Z)),
('c3B', (color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]))
)
def __init__(self):
self.batch = pyglet.graphics.Batch()
self.points = None
self.get_points_in_list('Output_colored.png')
self.add_points_to_batch()
def draw(self):
self.batch.draw()
class Player:
def __init__(self, pos=(0, 0, 0), rot=(0, 0)):
self.pos = list(pos)
self.rot = list(rot)
def mouse_motion(self, dx, dy):
dx /= 8
dy /= 8
self.rot[0] += dy
self.rot[1] -= dx
if self.rot[0]>90:
self.rot[0] = 90
elif self.rot[0] < -90:
self.rot[0] = -90
def update(self,dt,keys):
sens = 1
s = dt*100
rotY = -self.rot[1]/180*math.pi
dx, dz = s*math.sin(rotY), math.cos(rotY)
if keys[key.W]:
self.pos[0] += dx*sens
self.pos[2] -= dz*sens
if keys[key.S]:
self.pos[0] -= dx*sens
self.pos[2] += dz*sens
if keys[key.A]:
self.pos[0] -= dz*sens
self.pos[2] -= dx*sens
if keys[key.D]:
self.pos[0] += dz*sens
self.pos[2] += dx*sens
if keys[key.SPACE]:
self.pos[1] += s
if keys[key.LSHIFT]:
self.pos[1] -= s
class Window(pyglet.window.Window):
def push(self,pos,rot):
glPushMatrix()
rot = self.player.rot
pos = self.player.pos
glRotatef(-rot[0],1,0,0)
glRotatef(-rot[1],0,1,0)
glTranslatef(-pos[0], -pos[1], -pos[2])
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluPerspective(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width/self.height, 0.05, 1000)
self.Model()
def setLock(self, state):
self.lock = state
self.set_exclusive_mouse(state)
lock = False
mouse_lock = property(lambda self:self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300,200)
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
self.model = Model()
self.player = Player((0.5,1.5,1.5),(-30,0))
def on_mouse_motion(self,x,y,dx,dy):
if self.mouse_lock: self.player.mouse_motion(dx,dy)
def on_key_press(self, KEY, _MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.E:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.push(self.player.pos,self.player.rot)
self.model.draw()
glPopMatrix()
if __name__ == '__main__':
window = Window(width=400, height=300, caption='Terrain Viewer', resizable=True)
glClearColor(0, 0, 0, 1)
glEnable(GL_DEPTH_TEST)
pyglet.app.run()

How to move Canvas Polygons around?

I want to move around objects in python tkinter, specifically polygons. The problem is in is_click function. I can't seem to figure out how to determine if I clicked the object. The code is not 100% complete yet, and moving around needs still need to be finished but I need to figure this out for now. I also have similar class where you can move around Circles and Rectangles, where is_click function is working, but as polygon has from 3 to 4 coordinates it is a bit more complicated. Run The classes for yourself to see what they are doing.
My code for polygons:
import tkinter
class Polygon:
def __init__(self, ax, ay, bx, by, cx, cy, dx=None, dy=None, color=None):
self.ax = ax
self.ay = ay
self.bx = bx
self.by = by
self.cx = cx
self.cy = cy
self.dx = dx
self.dy = dy
self.color = color
def is_click(self, event_x, event_y):
pass
def paint(self, g):
self.g = g
if self.dx is None:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
fill=self.color)
else:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
self.dx,self.dy,
fill=self.color)
def move(self, d_ax=0, d_ay=0, d_bx=0, d_by=0, d_cx=0, d_cy=0, d_dx=None, d_dy=None):
if d_dx is None:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy)
else:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.dx += d_dx
self.dy += d_dy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy, d_dx, d_dy)
class Tangram:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(width=800,height=800)
self.g.pack()
#all objects
self.add(Polygon(500,300,625,175,750,300, color='SeaGreen'))
self.add(Polygon(750,50,625,175,750,300, color='Tomato'))
self.add(Polygon(500,175,562.6,237.5,500,300, color='SteelBlue'))
self.add(Polygon(500,175,562.5,237.5,625,175,562.5,112.5, color='FireBrick'))
self.add(Polygon(562.5,112.5,625,175,687.5,112.5, color='DarkMagenta'))
self.add(Polygon(500,50,500,175,625,50, color='Gold'))
self.add(Polygon(562.5,112.5,687.5,112.5,750,50,625,50, color='DarkTurquoise'))
#end of all objects
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event.x, event.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = event.x, event.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self):
pass
def event_release(self):
pass
Tangram()
and code for Circle and Rectangle moving:
import tkinter, random
class Circle:
def __init__(self, x, y, r, color='red'):
self.x = x
self.y = y
self.r = r
self.color = color
def is_click(self, x, y):
return (self.x-x)**2+(self.y-y)**2 < self.r**2
def paint(self, g):
self.g = g
self.id = self.g.create_oval(self.x-self.r,self.y-self.r,
self.x+self.r,self.y+self.r,
fill=self.color)
def move(self, dx=0, dy=0):
self.g.delete(self.id)
self.x += dx
self.y += dy
self.paint(self.g)
class Rectangle:
def __init__(self, x, y, width, height, color='red'):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def is_click(self, x, y):
return self.x<=x<self.x+self.width and self.y<=y<self.y+self.height
def paint(self, g):
self.g = g
self.id = self.g.create_rectangle(self.x,self.y,
self.x+self.width,self.y+self.height,
fill=self.color)
def move(self, dx=0, dy=0):
self.x += dx
self.y += dy
self.g.move(self.id, dx, dy)
class Program:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(bg='white', width=400, height=400)
self.g.pack()
for i in range(20):
if random.randrange(2):
self.add(Circle(random.randint(50, 350),random.randint(50, 350), 20, 'blue'))
else:
self.add(Rectangle(random.randint(50, 350),random.randint(50, 350), 40, 30))
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, e):
ix = len(self.array)-1
while ix >= 0 and not self.array[ix].is_click(e.x, e.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = e.x, e.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self, e):
self.Object.move(e.x-self.ex, e.y-self.ey)
self.ex, self.ey = e.x, e.y
def event_release(self, e):
self.g.unbind('<B1-Motion>')
self.g.unbind('<ButtonRelease-1>')
self.Object = None
Program()
This is not the full anwser to your questions, but its too long for a comment. One way, that I would centrally consider, is to change/amend your code so that it uses find_closes method. With this method, you can determine which widget (i.e. polygon) is clicked very easily. As a quick prof of concept, you can make the following changes in the Tangram and ploygon class:
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event, self.g, event.x, event.y): # add event and canvas to argumetns
In polygon:
def is_click(self, event, g, event_x, event_y):
widget_id = event.widget.find_closest(event.x, event.y)
print(widget_id)
g.move(widget_id, 1, 1) # just dummy move for a clicked widget
pass
This will print the ids of clicked widgets/polygons and slightly move the clicked polygon. This can be used to select which object was clicked, and having this id object, you can for moving or whatever.
p.s. the full code that I used to test it is here.
Hope this helps.

Python code never reaching a certain part that it should

Hey I am creating a roguelike game with python using Libtcod. I have code where the game makes random rooms. It should be making multiple rooms until one crosses and then stops making the rooms. The problem is it always only creates 1 room. Here is the hole code:
import libtcodpy as libtcod;
SCREEN_WIDTH = 80;
SCREEN_HEIGHT = 50;
MAP_WIDTH = 80
MAP_HEIGHT = 45
ROOM_MAX_SIZE = 10
ROOM_MIN_SIZE = 6
MAX_ROOMS = 30
LIMIT_FPS = 20;
color_dark_wall = libtcod.Color(0, 0, 100)
color_dark_ground = libtcod.Color(50, 50, 150)
class Tile:
def __init__(self, blocked, block_sight = None):
self.blocked = blocked
if block_sight is None: block_sight = blocked
self.block_sight = block_sight
class Rect:
def __init__(self, x, y , w, h):
self.x1 = x
self.y1 = y
self.x2 = x + w
self.y2 = y + h
def center(self):
center_x = (self.x1 + self.x2) / 2
center_y = (self.y1 + self.y2) / 2
return (center_x, center_y)
def intersect(self, other):
return (self.x1 <= other.x2 and self.x2 >= other.x1 and
self.y1 <= other.y2 and self.y2 >= other.y1)
class Object:
def __init__(self, x, y, char, color):
self.x = x
self.y = y
self.char = char
self.color = color
def move(self, dx, dy):
if not map[self.x + dx][self.y + dy].blocked:
self.x += dx
self.y += dy
def draw(self):
libtcod.console_set_default_foreground(con, self.color)
libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
def clear(self):
libtcod.console_put_char(con, self.x, self.y, ' ', libtcod.BKGND_NONE)
def create_room(room):
global map
for x in range(room.x1 + 1, room.x2):
for y in range(room.y1 + 1, room.y2):
map[x][y].blocked = False
map[x][y].block_sight = False
def create_h_tunnel(x1, x2, y):
global map
for x in range(min(x1, x2), max(x1, x2) + 1):
map[x][y].blocked = False
map[x][y].block_sight = False
def create_v_tunnel(y1, y2, x):
global map
print("Hello")
for y in range(min(y1, y2), max(y1, y2) + 1):
map[x][y].blocked = False
map[x][y].block_sight = False
def make_map():
global map
map = [[ Tile(True)
for y in range(MAP_HEIGHT) ]
for x in range(MAP_WIDTH) ]
rooms = []
num_rooms = 0
for r in range(MAX_ROOMS):
w = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
h = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h -1)
new_room = Rect(x, y, w, h)
failed = False
for other_room in rooms:
if new_room.intersect(other_room):
failed = True
break
if not failed:
create_room(new_room)
(new_x, new_y) = new_room.center()
if num_rooms == 0:
player.x = new_x
player.y = new_y
else:
(prev_x, prev_y) = rooms[num_rooms-1].center()
if libtcod.random_get_int(0, 0, 1) == 1:
create_h_tunnel(prev_x, new_x, prev_y)
create_v_tunnel(prev_y, new_y, new_x)
else:
create_v_tunnel(prev_y, new_y, prev_x)
create_h_tunnel(prev_x, new_x, new_y)
rooms.append(new_room)
num_rooms += 1
def render_all():
global color_dark_wall, color_light_wall
global color_dark_ground, color_light_ground
for y in range(MAP_HEIGHT):
for x in range (MAP_WIDTH):
wall = map[x][y].block_sight
if wall:
libtcod.console_set_char_background(con, x, y, color_dark_wall, libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, color_dark_ground, libtcod.BKGND_SET)
for object in objects:
object.draw()
libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
def handle_keys():
key = libtcod.console_check_for_keypress()
if key.vk == libtcod.KEY_ENTER and key.lalt:
libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
elif key.vk == libtcod.KEY_ESCAPE:
return True #exit game
if libtcod.console_is_key_pressed(libtcod.KEY_UP):
player.move(0, -1)
elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
player.move(0, 1)
elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
player.move(-1, 0)
elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
player.move(1, 0)
libtcod.console_set_custom_font('terminal10x10_gs_tc.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD);
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'Lets Crawl', False);
libtcod.sys_set_fps(LIMIT_FPS);
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)
player = Object(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, '#', libtcod.green)
npc = Object(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, '#', libtcod.yellow)
objects = [npc, player]
#makes the map
make_map()
while not libtcod.console_is_window_closed():
render_all()
#libtcod.console_check_for_keypress()
libtcod.console_flush();
for object in objects:
object.clear()
exit = handle_keys()
if exit:
break
And here is the make_map() function where I suspect the problem is:
def make_map():
global map
map = [[ Tile(True)
for y in range(MAP_HEIGHT) ]
for x in range(MAP_WIDTH) ]
rooms = []
num_rooms = 0
for r in range(MAX_ROOMS):
w = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
h = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h -1)
new_room = Rect(x, y, w, h)
failed = False
for other_room in rooms:
if new_room.intersect(other_room):
failed = True
break
if not failed:
create_room(new_room)
(new_x, new_y) = new_room.center()
if num_rooms == 0:
player.x = new_x
player.y = new_y
else:
(prev_x, prev_y) = rooms[num_rooms-1].center()
if libtcod.random_get_int(0, 0, 1) == 1:
create_h_tunnel(prev_x, new_x, prev_y)
create_v_tunnel(prev_y, new_y, new_x)
else:
create_v_tunnel(prev_y, new_y, prev_x)
create_h_tunnel(prev_x, new_x, new_y)
rooms.append(new_room)
num_rooms += 1
After the program creates the room, it adds the room to the list but does it not continue to make rooms? Or is it that each room created is the same size (even though im passing random numbers) and it creates the first room and then stops? I don't understand...
I suspect you have an indentation error. Looks like everything after the definition of w, h, x and y is supposed to be part of the same for loop, so should be indented to the same level.

Mutli-threading python with Tkinter

I'm drawing little circles on a canvas with these functions :
This is the function that will draw the circles :
class Fourmis:
def __init__(self, can, posx, posy, name, radius):
self.can = can
self.largeur_can = int(self.can.cget("width"))
self.hauteur_can = int(self.can.cget("height"))
self.posx = posx
self.posy = posy
self.name = name
self.radius = radius
self.ball1 = self.can.create_oval(self.posy, self.posx, self.posy+radius, self.posx+radius, outline=self.name, fill=self.name, width=2)
self.nx = randrange(-10,10,1)
self.nx /= 2.0
self.ny = randrange(-10,10,1)
self.ny /= 2.0
#self.can.bind("<Motion>", self.destruction, add="+")
self.statut = True
self.move()
def move(self):
if self.statut == True :
self.pos_ball = self.can.coords(self.ball1)
self.posx_ball = self.pos_ball[0]
self.posy_ball = self.pos_ball[1]
if self.posx_ball < 0 or (self.posx_ball + self.radius) > self.largeur_can:
self.nx = -self.nx
if self.posy_ball < 0 or (self.posy_ball + self.radius) > self.hauteur_can:
self.ny = -self.ny
self.can.move(self.ball1, self.nx, self.ny)
self.can.after(10, self.move)
this one creates the canvas and the circles :
class App(Frame):
def __init__(self):
self.root=Tk()
self.can=Canvas(self.root,width=800,height=600,bg="black")
self.can.pack()
self.create(50, "green")
self.create(50, "purple")
def mainloop(self):
self.root.mainloop()
def create(self, i, name):
for x in range(i):
self.x=Fourmis(self.can,100,400, name,0)
I call these lines to run the project :
jeu = App()
jeu.mainloop()
What is the correct way to execute self.create(50, "green") and self.create(50, "purple") in different threads?
I have tried the following, but could not get it to work.:
class FuncThread(threading.Thread):
def __init__(self, i, name):
self.i = i
self.name = name
threading.Thread.__init__(self)
def run(self):
App.create(self, self.i, self.name)
Is someone able to tell me how to run these threads?
When this functionality is needed, what you do is schedule the events you wish to perform by putting them in a queue shared by the threads. This way, in a given thread you specify that you want to run "create(50, ...)" by queueing it, and the main thread dequeue the event and perform it.
Here is a basic example for creating moving balls in a second thread:
import threading
import Queue
import random
import math
import time
import Tkinter
random.seed(0)
class App:
def __init__(self, queue, width=400, height=300):
self.width, self.height = width, height
self.canvas = Tkinter.Canvas(width=width, height=height, bg='black')
self.canvas.pack(fill='none', expand=False)
self._oid = []
self.canvas.after(10, self.move)
self.queue = queue
self.canvas.after(50, self.check_queue)
def check_queue(self):
try:
x, y, rad, outline = self.queue.get(block=False)
except Queue.Empty:
pass
else:
self.create_moving_ball(x, y, rad, outline)
self.canvas.after(50, self.check_queue)
def move(self):
width, height = self.width, self.height
for i, (oid, r, angle, speed, (x, y)) in enumerate(self._oid):
sx, sy = speed
dx = sx * math.cos(angle)
dy = sy * math.sin(angle)
if y + dy + r> height or y + dy - r < 0:
sy = -sy
self._oid[i][3] = (sx, sy)
if x + dx + r > width or x + dx - r < 0:
sx = -sx
self._oid[i][3] = (sx, sy)
nx, ny = x + dx, y + dy
self._oid[i][-1] = (nx, ny)
self.canvas.move(oid, dx, dy)
self.canvas.update_idletasks()
self.canvas.after(10, self.move)
def create_moving_ball(self, x=100, y=100, rad=20, outline='white'):
oid = self.canvas.create_oval(x - rad, y - rad, x + rad, y + rad,
outline=outline)
oid_angle = math.radians(random.randint(1, 360))
oid_speed = random.randint(2, 5)
self._oid.append([oid, rad, oid_angle, (oid_speed, oid_speed), (x, y)])
def queue_create(queue, running):
while running:
if random.random() < 1e-6:
print "Create a new moving ball please"
x, y = random.randint(100, 150), random.randint(100, 150)
color = random.choice(['green', 'white', 'yellow', 'blue'])
queue.put((x, y, random.randint(10, 30), color))
time.sleep(0) # Effectively yield this thread.
root = Tkinter.Tk()
running = [True]
queue = Queue.Queue()
app = App(queue)
app.create_moving_ball()
app.canvas.bind('<Destroy>', lambda x: (running.pop(), x.widget.destroy()))
thread = threading.Thread(target=queue_create, args=(queue, running))
thread.start()
root.mainloop()

Categories

Resources