I have functioning code but there are a few things which I would like to change about it but don't know how to so thought i'd ask here. My code is as follows:
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#Define the target, source and output arrays. Source has to be completely white otherwise it kills everything
def initialize(x,y):
xarr = np.zeros(x)
yarr = np.zeros(y)
target = np.meshgrid(xarr,yarr)
target = target[0]
source = np.meshgrid(xarr,yarr)
source = source[0]
output = np.meshgrid(xarr,yarr)
output = output[0]
for i in range(x):
for n in range(y):
source[n][i] = 1
return target, source, output
# creates trap between XTrapMin-XTrapMax and YTrapMin-YTrapMax on Array
def trap(xtmi,xtma,xs,ytmi,ytma,ys,array):
for i in range(xs):
if xtmi < i < xtma:
for n in range(ys):
if ytmi < n < ytma:
array[n][i] = 255
return
#Returns the amplitude of a complex number
def Amplitude(x):
if isinstance(x, complex):
return np.sqrt(x.real**2+x.imag**2)
else:
return np.abs(x)
#Returns the phase of a complex number
def Phase(z):
return np.angle(z)
#Main GS algorithm implementation using numpy FFT package
#performs the GS algorithm to obtain a phase distribution for the plane, Source
#such that its Fourier transform would have the amplitude distribution of the plane, Target.
def GS(target,source):
A = np.fft.ifft2(target)
for i in range(5):
B = Amplitude(source) * np.exp(1j * Phase(A))
C = np.fft.fft2(B)
D = Amplitude(target) * np.exp(1j * Phase(C))
A = np.fft.ifft2(D)
output = Phase(A)
return output
#Make array into PIL Image
def mkPIL(array):
im = Image.fromarray(np.uint8(array))
return im
def up():
global ytmi
global ytma
ytmi -= 10
ytma -= 10
return
def down():
global ytmi
global ytma
ytmi += 10
ytma += 10
return
def right():
global xtmi
global xtma
xtmi += 10
xtma += 10
return
def left():
global xtmi
global xtma
xtmi -= 10
xtma -= 10
return
xtmi = 125
xtma = 130
xs = 1024
ytmi = 0
ytma = 5
ys = 768
root = tk.Tk()
root.attributes('-fullscreen', True)
def main():
app = Lower(root)
root.mainloop()
class Lower:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master).pack()
self.displayimg = tk.Button(self.frame, text = 'Display', width = 25, command = self.plot)
self.displayimg.pack()
self.makewidg()
def makewidg(self):
self.fig = plt.figure(figsize=(100,100), frameon=False) #changing figsize doesnt cange the size of the plot display
self.fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
self.fig.tight_layout()
self.ax = self.fig.add_subplot(111)
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
self.canvas.get_tk_widget().pack(expand=True)
self.canvas.figure.tight_layout()
self.canvas.draw()
self.new_window()
def new_window(self):
self.newWindow = tk.Toplevel()
self.app = Display(self.newWindow)
def plot(self):
global xtmi, xtma, xs, ytmi, ytma, ys, i
target,source,output=initialize(xs,ys)
trap(xtmi,xtma,xs,ytmi,ytma,ys,target)
output = GS(target,source)
self.ax.imshow(output, cmap='gray')
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas.draw()
self.ax.clear()
def kill(self):
root.destroy()
class Display:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
self.up = tk.Button(self.frame, text = 'Up', width = 25, command = up)
self.up.pack()
self.down = tk.Button(self.frame, text = 'Down', width = 25, command = down)
self.down.pack()
self.right = tk.Button(self.frame, text = 'Right', width = 25, command = right)
self.right.pack()
self.left = tk.Button(self.frame, text = 'Left', width = 25, command = left)
self.left.pack()
self.kill = tk.Button(self.frame, text = 'Kill', width = 25, command = self.kill)
self.kill.pack()
def kill(self):
root.destroy()
main()
Currently the button displayimg from the class Lower is displayed above the image, is there a way in which I can have the display button on the Display class and still have it manipulate the image on the Lower screen? Also, I intend to display the window opened by Lower on a separate monitor, but can't drag it seeing as it is fullscreen, is there a way around that I can get it on my second monitor?
I try that as such:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = Lower.plot(Lower))
self.displayimg.pack()
But this causes a misreference I think as I get an error code
AttributeError: type object 'Lower' has no attribute 'ax'
Call to Lower.plot
You are using Lower.plot as your button command. It needs one argument, self which must be an instance of Lower - so Lower.plot(Lower) is passing a class where an instance is expected. Instead you need to use the app instance you've made, and call app.plot(). The arguement self is automatically the instance itself, this is fundamental to OOP in python. Calling the method on an instance passes self as the first arg, so it's missing from the call. Calling Lower.plot(...) is calling the method on the class Lower, so there is no instance, and you have to supply your own. I'd avoid calling methods without an instance like this in this situation, and use your app instance.
Command for the display button
Your button creation becomes something like:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = app.plot)
If you need to pass additional args to plot, you need to delay the function call so it happens on click, not on creation of the button. You can use lambda : app.plot("red", 10, whatever) to make a nameless function, taking no arguments, that when called will go on to call app.plot with the given args.
Positioning the window
You can control the position of the app window using wm_geometry:
app.wm_geometry("200x200+100+500")
Will cause the app window to be 200px by 200px, positioned 100px left and 500px down from the origin, and on a windows machine, this is the top left corner of your primary monitor. You can keep the width and height the same and just move the window with eg
app.wm_geometry("+100+500")
You can use more scripting to build the string +{xpos}+{ypos} with whichever values you like to match your desktop layout.
Related
I want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.
If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.
If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.
How can you call the same function twice with the same arguments and get different results. What is different.
I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.
In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.
I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.
I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.root = Tk()
self.name = name # Filename
myframe = Frame(self.root)
myframe.pack(fill=BOTH, expand=YES)
self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
self.open_img()
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.root.state("zoomed")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
What should happen in the video:
I click run and the image is displayed immediately, without having to click the fix button.
What does happen in the video:
I click run and the image is not displayed until I click the fix button, afterwhich it works.
Changing
self.root.state("zoomed") to self.root.state("normal")
in your code (I am working on Python3) I can only get:
[
the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?
and now the code seems to work with me:
from time import sleep
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent, **kwargs):
# Canvas.__init__(self,parent,**kwargs)
print(kwargs)
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
# self.height = self.winfo_reqheight()
# self.width = self.winfo_reqwidth()
self.height = self.winfo_height()
self.width = self.winfo_width()
# self.height = height
# self.width = width
# self.__dict__.update(kwargs)
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = (event.width)//self.width
hscale = (event.height)//self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.pippo = Tk()
self.name = name # Filename
self.myframe = Frame(self.pippo)
self.myframe.pack(side = BOTTOM, expand=YES)
# myframe.pack(fill=BOTH, expand='TRUE')
self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
# sleep(2)
self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
self.b.pack(side=TOP)
self.open_img()
# self.pippo.mainloop() ## use it if you eliminate def run
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
# self.cv.create_image(self.pippo.winfo_screenwidth()/2,
# self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
self.cv.create_image(self.pippo.winfo_width()/2,
self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.pippo.state("normal")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.pippo.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else
Here my window image results before ad after pressing fix button :
At the end found out that your code does work as long as you use
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
The problem is here. self.root.winfo_screenwidth()
Change it to self.cv.width. I don't know why.
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
Change the last line to
self.cv.create_image(self.cv.width/2,
self.cv.height/2, image=self.img, tags=('all'))
Fixes the issue.
Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.
self.cv.width returns the width of the canvas object.
I would like to increment the number in Tkinter Label by 1 when a
Tkinter Button is pressed.
This is the code I implemented so far but doesn't work:
import random
from tkinter import *
class Game:
def __init__(self, root):
self.f = Frame(root, height = 1000, width = 350)
self.f.pack()
self.b = Button(self.f,text = 'Increase',height = 2, width = 15, command = self.buttonClick(1))
self.b.grid(row = 3, column = 1)
self.b.pack()
def buttonClick(self, num):
b = 1
if num == 1:
a = Label(text = str(b))
a.pack()
root = Tk()
root.geometry('350x1000')
mb = Game(root)
root.mainloop()
Few things were wrong in your code that needed correction:
You can't use two different layout managers (you are using grid and also pack and that is not allowed unfortunately).
Every time you call the method command = self.buttonClick(1) you pass the same argument 1. You don't increase anything. Also you should use lambda functions here: command = lambda: self.buttonClick(1). Because in your approach you pass the the command parameter what self.buttonClick(1) returns (your buttonClick method won't even execute!).
But still there is easier approach with using IntVar object.
The whole working code:
from tkinter import *
class Game:
def __init__(self, root):
self.f = Frame(root, height = 1000, width = 350)
self.f.pack()
self.number = IntVar(0)
self.a = Label(self.f, textvariable = self.number)
self.a.pack()
self.b = Button(self.f, text = 'Increase', height = 2, width = 15, command = self.buttonClick)
self.b.pack()
def buttonClick(self):
self.number.set(self.number.get() + 1)
root = Tk()
root.geometry('350x1000')
mb = Game(root)
root.mainloop()
I am creating a TicTacToe game in tkinter, consisting of a 3x3 grid made out of buttons.
In the code below, once a player has drawn on a tile (by clicking on the button), the program should remove this tile from the list 'self.flattenedButtons'. This is to prevent the computer (player 2) from drawing on the same tile.
The method this check is made in is self.add_move(). This works on all buttons apart from the bottom right, I assume this is as I took away 1 from the ending range. If I do not do this I am given an 'out of range' error.
How would I change my method so it works on all buttons?
CODE:
from tkinter import *
from functools import partial
from itertools import *
import random
class Window(Frame):
def __init__(self, master = None): # init Window class
Frame.__init__(self, master) # init Frame class
self.master = master # allows us to refer to root as master
self.rows = 3
self.columns = 3
self.guiGrid = [[None for x in range(self.rows)] for y in range(self.columns)] # use this for the computer's moves
self.buttonText = StringVar(value = '')
self.buttonText2 = StringVar(value = 'X')
self.buttonText3 = StringVar(value = 'O')
self.button_ij = None
self.flattenedButtons = []
self.create_window()
self.add_buttons()
def create_window(self):
self.master.title('Tic Tac Toe')
self.pack(fill = BOTH, expand = 1)
for i in range(0,3):
self.grid_columnconfigure(i, weight = 1)
self.grid_rowconfigure(i, weight = 1)
def add_buttons(self):
rows = 3
columns = 3
for i in range (rows):
for j in range(columns):
self.button_ij = Button(self, textvariable = self.buttonText, command = lambda i=i, j=j: self.add_move(i,j))
self.guiGrid[i][j] = self.button_ij # place button into 2d array to access later on
self.flattenedButtons.append(self.button_ij)
self.button_ij.grid(row = i,column = j, sticky =E+W+S+N)
def add_move(self, i,j):
pressedButton = self.guiGrid[i][j]
self.guiGrid[i][j].config(textvariable =self.buttonText2)
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
root = Tk() # creating Tk instance
rootWidth = '500'
rootHeight = '500'
root.geometry(rootWidth+'x'+rootHeight)
ticTacToe = Window(root) # creating Window object with root as master
root.mainloop() # keeps program running
It is not recommended to operate the list when you iterate it.
If your code is:
for i in range(0, len(self.flattenedButtons)-1):
if (self.flattenedButtons[i] == pressedButton):
self.flattenedButtons.remove(self.flattenedButtons[i])
print('removed')
else:
pass
print(self.flattenedButtons)
You will see that your button 9 will never be removed.
Change your for loop to a easy list-comprehension:
self.flattenedButtons = [i for i in self.flattenedButtons if i != pressedButton]
print(self.flattenedButtons)
You will see the change.
I am working on a gui to animate coin flips one at a time continuously.
I have two classes cointoss.py and flipbell.py.
Cointoss class generates the value change for coins and flipbell is used to animate the process.
As if now I have the code working to animate one coin at a time but not all the coins at a time.
When I say all coins this is the logic: first one coin comes down according to the value change, next another one comes down but the first coin value also gets updated accordingly and so on.
I need help how to move forward with what I have tried so far. I have used for loops to animate the process and I was thinking of using recursive method to have the logic part.
Any help with existing code or ideas to move forward would be great.
flipbell.py
from tkinter import Tk, Canvas, Button, W, E
import random
from math import pi, sin, cos
from cointoss import *
class FlipBell(object):
"""
GUI to simulate cointoss.
"""
def __init__(self, wdw, dimension, increment, delay):
"""
Determines the layout of the GUI.
wdw : top level widget, the main window,
dimension : determines the size of the canvas,
increment : step size for a billiard move,
delay : time between updates of canvas.
"""
wdw.title('Coin flips and Bell Curve')
self.dim = dimension # dimension of the canvas
self.inc = increment
self.dly = delay
self.togo = False # state of animation
# initial coordinates of the ball
self.xpt = self.dim/2
self.ypt = 0
self.cnv = Canvas(wdw, width=self.dim,\
height=self.dim, bg='white')
self.cnv.grid(row=0, column=0, columnspan=2)
self.bt0 = Button(wdw, text='start',\
command=self.start)
self.bt0.grid(row=1, column=0, sticky=W+E)
self.bt1 = Button(wdw, text='stop',\
command=self.stop)
self.bt1.grid(row=1, column=1, sticky=W+E)
def map2table(self, pnt):
"""
Keeps the ball on the canvas table.
"""
if pnt < 0:
(quo, rest) = divmod(-pnt, self.dim)
else:
(quo, rest) = divmod(pnt, self.dim)
return rest
def placecoin(self, xpt, ypt):
self.cnv.create_oval(xpt-1, ypt-1, xpt+1, ypt+1,\
width=2, outline='red', fill='red', tags='coin')
def drawball(self):
"""
Draws the ball on the canvas.
"""
xpt = self.map2table(self.xpt)
ypt = self.map2table(self.ypt)
self.cnv.delete('dot')
self.cnv.create_oval(xpt-1, ypt-1, xpt+1, ypt+1,\
width=1, outline='black', fill='red', tags='dot')
def animate(self):
"""
Performs the animation.
"""
self.drawball()
val = []
for k in range(400):
val1 = CoinToss.cointoss(3,k,self.dim//2)
val.append(val1)
points = {}
for i in range(1,401):
points[i] = 0
for i in range(0,400):
for j in range(0,400):
(xpt, ypt) = (self.xpt, self.ypt)
self.xpt = val[i][1][j]
# print("x %d",self.xpt)
self.ypt = ypt + 1
# print("y %d",self.ypt)
self.cnv.after(self.dly)
self.drawball()
self.cnv.update()
#Puts the coin on top each other
if self.ypt == 400:
if points[self.xpt]>=1:
self.placecoin(val[i][1][-1],400-points[self.xpt])
else:
self.placecoin(val[i][1][-1],400)
points[self.xpt]+=3
self.ypt = 0
def start(self):
"""
Starts the animation.
"""
self.togo = True
self.animate()
def stop(self):
"""
Stops the animation.
"""
self.togo = False
def main():
"""
Defines the dimensions of the canvas
and launches the main event loop.
"""
top = Tk()
dimension = 400 # dimension of canvas
increment = 10 # increment for coordinates
delay = 1 # how much sleep before update
num_flips = 3
num_value = dimension//2
FlipBell(top, dimension, increment, delay)
top.mainloop()
if __name__ == "__main__":
main()
cointoss.py
from random import randint
import random
class CoinToss:
coin = 0
def __init__(self, value,num_flip):
# self.id = 1
self.v = value
self.state = 1
self.flip = num_flip
CoinToss.coin += 1
def cointoss(self,coin,value):
print('The ball at the start: ball: %d, state: %d, value: %d' % (coin, self, value))
value_change = value
coin_change = []
for i in range(1,400+1):
value = value_change
value_change = CoinToss.flip(value)
print('after flip %d, ball: %d, state: %d, value: %d' % (i,coin, i, value_change))
coin_change.append(value_change)
return([coin,coin_change])
def flip(self):
rand_value = randint(0, 1)
if rand_value == 1:
self +=1
else:
self -=1
return self
You have named both a function and a variable "flip" in CoinToss which is confusing. Also, you use the "tags" keyword and it should be "tag". There is more than one way to code this. The code below is not a complete solution but a simple example that shows how to use the CoinToss class to create and move an individual ball (doesn't check for move off of canvas). The FlipBell class stores each CoinToss instance in a list and calls the "flip" function for each class each time a ball is created. You could also use "after" within the CoinToss class to have the flip function call itself repeatedly.
from tkinter import *
from random import randint
class FlipBell(object):
"""
GUI to simulate cointoss.
"""
def __init__(self, wdw, dimension, delay):
"""
Determines the layout of the GUI.
wdw : top level widget, the main window,
dimension : determines the size of the canvas,
increment : step size for a billiard move,
delay : time between updates of canvas.
"""
wdw.title('Coin flips and Bell Curve')
self.cnv = Canvas(wdw, width=dimension,
height=dimension, bg='white')
self.cnv.grid()
self.ct_instances=[]
self.colors=["blue", "red", "yellow", "gray", "green"]
self.delay=delay
self.offset=0
self.create_next()
def create_next(self):
""" create one ball for each color in self.colors
and call each existing ball's flip function to
move it a random amount
"""
x=5
y=5
incr=10*self.offset
CT=CoinToss(self.cnv, x+incr, y+incr, self.colors[self.offset])
##save each CoinToss (ball) instance in a list
self.ct_instances.append(CT)
self.offset += 1
## call flip (move ball) for each instance
for instance in self.ct_instances:
instance.flip()
if self.offset < len(self.colors):
self.cnv.after(self.delay, self.create_next)
class CoinToss:
def __init__(self, canvas, start_x, start_y, color):
self.cnv=canvas
self.cointoss(start_x, start_y, color)
def cointoss(self, start_x, start_y, color):
self.this_ball=self.cnv.create_oval(start_x-5, start_y-5, start_x+5, start_y+5,
outline='black', fill=color, tag="dot")
def flip(self):
""" move the ball created for this class instance by a random amount
"""
rand_value = randint(10, 50)
self.cnv.move(self.this_ball, rand_value, rand_value)
if __name__ == "__main__":
top = Tk()
dimension = 400 # dimension of canvas
delay = 500 # how much sleep before update --> 1/2 second
num_flips = 3
FP=FlipBell(top, dimension, delay)
top.mainloop()
I am trying to make a simple game with pyglet, and it has to include an intro screen. Unfortunately, it's been proving more difficult than I expected.
The following code is a simpler version of what I am trying to do.
import pyglet
from game import intro
game_window = pyglet.window.Window(800, 600)
intro.play(game_window)
#game_window.event
def on_draw():
game_window.clear()
main_batch.draw()
def update(dt):
running = True
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1/120.0)
main_batch = pyglet.graphics.Batch()
score_label = pyglet.text.Label(text = 'RUNNING GAME', x = 400, y = 200, batch=main_batch)
pyglet.app.run()
Where game/intro.py has the following written in it:
import pyglet
from time import sleep
def play(game_window):
game_window.clear()
studio = pyglet.text.Label('foo studios', font_size=36, font_name='Arial', x=400, y=300)
studio.draw()
sleep(5)
This opens a window (the intro window) and waits 5 seconds, after which the message "RUNNING GAME" appears, but the "foo studios" message does not appear.
Clearly I am doing something wrong.
I am not very experienced with pyglet, but I managed to get the game running (needs a bit of tweaking, but it's essentially done). All I need left is the intro screen.
If anyone knows a good way of doing an intro screen (just with text, I don't need any animations of music for now), I would be very grateful.
You're better off creating classes based on for instance pyglet.sprite.Sprite and using those objects as "windows" or "screens".
Feels like i'm pasting this code everywhere but use this, and in "def render()` put the different "scenarios"/"windows" you'd wish to be rendered at the time.
import pyglet
from time import time, sleep
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.frames = 0
self.framerate = pyglet.text.Label(text='Unknown', font_name='Verdana', font_size=8, x=10, y=10, color=(255,255,255,255))
self.last = time()
self.alive = 1
self.refreshrate = refreshrate
def on_draw(self):
self.render()
def render(self):
self.clear()
if time() - self.last >= 1:
self.framerate.text = str(self.frames)
self.frames = 0
self.last = time()
else:
self.frames += 1
self.framerate.draw()
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events() # <-- This is the event queue
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()
What does it is the fact that you have a rendering function that clears and flips the entire graphical memory X times per second and you descide which objects are included in that render perior in the render function.
Try it out and see if it helps.
Here is a example using the above example, it consists of 3 things:
* A main window
* A Intro screen
* A Menu screen
You can ignore class Spr() and def convert_hashColor_to_RGBA(), these are mere helper functions to avoid repetative code further down.
I will also go ahead and mark the important bits that actually do things, the rest are just initation-code or positioning things.
import pyglet
from time import time, sleep
__WIDTH__ = 800
__HEIGHT__ = 600
def convert_hashColor_to_RGBA(color):
if '#' in color:
c = color.lstrip("#")
c = max(6-len(c),0)*"0" + c
r = int(c[:2], 16)
g = int(c[2:4], 16)
b = int(c[4:], 16)
color = (r,g,b,255)
return color
class Spr(pyglet.sprite.Sprite):
def __init__(self, texture=None, width=__WIDTH__, height=__HEIGHT__, color='#000000', x=0, y=0):
if texture is None:
self.texture = pyglet.image.SolidColorImagePattern(convert_hashColor_to_RGBA(color)).create_image(width,height)
else:
self.texture = texture
super(Spr, self).__init__(self.texture)
## Normally, objects in graphics have their anchor in the bottom left corner.
## This means that all X and Y cordinates relate to the bottom left corner of
## your object as positioned from the bottom left corner of your application-screen.
##
## We can override this and move the anchor to the WIDTH/2 (aka center of the image).
## And since Spr is a class only ment for generating a background-image to your "intro screen" etc
## This only affects this class aka the background, so the background gets positioned by it's center.
self.image.anchor_x = self.image.width / 2
self.image.anchor_y = self.image.height / 2
## And this sets the position.
self.x = x
self.y = y
def _draw(self):
self.draw()
## IntoScreen is a class that inherits a background, the background is Spr (our custom background-image class)
## IntoScreen contains 1 label, and it will change it's text after 2 seconds of being shown.
## That's all it does.
class IntroScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(IntroScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.intro_text = pyglet.text.Label('Running game', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y, multiline=False, width=width, height=height, color=(100, 100, 100, 255), anchor_x='center')
self.has_been_visible_since = time()
def _draw(self): # <-- Important, this is the function that is called from the main window.render() function. The built-in rendering function of pyglet is called .draw() so we create a manual one that's called _draw() that in turn does stuff + calls draw(). This is just so we can add on to the functionality of Pyglet.
self.draw()
self.intro_text.draw()
if time() - 2 > self.has_been_visible_since:
self.intro_text.text = 'foo studios'
## Then we have a MenuScreen (with a red background)
## Note that the RED color comes not from this class because the default is black #000000
## the color is set when calling/instanciating this class further down.
##
## But all this does, is show a "menu" (aka a text saying it's the menu..)
class MenuScreen(Spr):
def __init__(self, texture=None, width=300, height = 150, x = 10, y = 10, color='#000000'):
super(MenuScreen, self).__init__(texture, width=width, height=height, x=x, y=y, color=color)
self.screen_text = pyglet.text.Label('Main menu screen', font_size=8, font_name=('Verdana', 'Calibri', 'Arial'), x=x, y=y+height/2-20, multiline=False, width=300, height=height, color=(100, 100, 100, 255), anchor_x='center')
def _draw(self):
self.draw()
self.screen_text.draw()
## This is the actual window, the game, the glory universe that is graphics.
## It will be blank, so you need to set up what should be visible when and where.
##
## I've creates two classes which can act as "screens" (intro, game, menu etc)
## And we'll initate the Window class with the IntroScreen() and show that for a
## total of 5 seconds, after 5 seconds we will swap it out for a MenuScreeen().
##
## All this magic is done in __init__() and render(). All the other functions are basically
## just "there" and executes black magic for your convencience.
class Window(pyglet.window.Window):
def __init__(self, refreshrate):
super(Window, self).__init__(vsync = False)
self.alive = 1
self.refreshrate = refreshrate
self.currentScreen = IntroScreen(x=320, y=__HEIGHT__/2, width=50) # <-- Important
self.screen_has_been_shown_since = time()
def on_draw(self):
self.render()
def on_key_down(self, symbol, mod):
print('Keyboard down:', symbol) # <-- Important
def render(self):
self.clear()
if time() - 5 > self.screen_has_been_shown_since and type(self.currentScreen) is not MenuScreen: # <-- Important
self.currentScreen = MenuScreen(x=320, y=__HEIGHT__-210, color='#FF0000') # <-- Important, here we switch screen (after 5 seconds)
self.currentScreen._draw() # <-- Important, draws the current screen
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
event = self.dispatch_events()
sleep(1.0/self.refreshrate)
win = Window(23) # set the fps
win.run()