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()
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.