Python Pyglet Using External Fonts For Labels - python

I'm working on a project at the moment and I have been trying to change the fonts of the labels in the pyglet library to fonts that I found online but I couldn't get it to work. I tried searching online for an hour now and nothing seems to be working. Added some code for reference:
font.add_file('ZukaDoodle.ttf')
ZukaDoodle = font.load('ZukaDoodle.ttf', 16)
PlayLabel = pyglet.text.Label('Go', font_name='ZukaDoodle', font_size=100, x=window.width // 2,
y=window.height - 450, anchor_x='center', anchor_y='center',
batch=buttons_batch,
color=(0, 0, 0, 1000), width=250, height=130)

So the error is pretty simple. The loaded font-name is not ZukaDoodle, it's Zuka Doodle with a space. Here's a working executable sample:
from pyglet import *
from pyglet.gl import *
font.add_file('ZukaDoodle.ttf')
ZukaDoodle = font.load('ZukaDoodle.ttf', 16)
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.keys = {}
self.mouse_x = 0
self.mouse_y = 0
self.PlayLabel = pyglet.text.Label('Go', font_name='Zuka Doodle', font_size=100,
x=self.width // 2,
y=self.height - 450,
anchor_x='center', anchor_y='center',
color=(255, 0, 0, 255),
width=250, height=130)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
self.keys[symbol] = True
def render(self):
self.clear()
self.PlayLabel.draw()
## Add stuff you want to render here.
## Preferably in the form of a batch.
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
The key difference here is font_name='Zuka Doodle'.
Also, the alpha channel usually doesn't need to be higher than 255 since that's the max value of a colored byte, so unless you're using 16-bit color representation per channel, 255 will be the maximum value.

Related

How to handle QPropertyAnimation for a large number of widgets

I want to have many colored dots constantly move across a background. (Description of code below): A PolkaDot widget is constructed with a random color, size, position, and duration. The QPropertyAnimation moves the widget across the screen from left to right, restarting at a new height when the animation ends. 100 PolkaDot widgets are constructed in the Background widget, which is enough to make it appear like tons of new dots are constantly rushing in from the left side of the screen.
However, the 100 property animations seem to consume a lot of CPU power, causing it to slow down and look un-smooth. Is there another way to achieve a similar result? Try running the code below.
import sys, random
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
colors = [QColor('#f00'), QColor('#00f'), QColor('#0f0'), QColor('#ff0'),
QColor('#fff'), QColor('#ff6000'), QColor('#6b00ff'), QColor('#f0f')]
class PolkaDot(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedSize(50, 50)
self.color = random.choice(colors)
self.r = random.randrange(5, 22)
self.move(random.randrange(w), random.randrange(h))
self.anim = QPropertyAnimation(self, b'pos')
self.anim.finished.connect(self.run)
self.anim.setDuration(random.randrange(3000, 9000))
self.anim.setStartValue(QPoint(self.x() - (w + 60), self.y()))
self.anim.setEndValue(QPoint(w + 60, self.y()))
self.anim.start()
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing)
qp.setBrush(self.color)
qp.setPen(QPen(self.color.darker(130), self.r / 5))
qp.drawEllipse(QPoint(25, 25), self.r, self.r)
def run(self):
y = random.randrange(h)
self.anim.setDuration(random.randrange(3000, 9000))
self.anim.setStartValue(QPoint(-60, y))
self.anim.setEndValue(QPoint(w + 60, y + random.randrange(-50, 50)))
self.anim.start()
class Background(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(window)
polka_dots = [PolkaDot(self) for i in range(100)]
self.setStyleSheet('background-color: #000')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QDesktopWidget().availableGeometry()
w, h = window.width(), window.height()
gui = Background()
gui.show()
sys.exit(app.exec_())
I sort of found a solution. For some reason, creating the dots in the background widget's paint event and calling repaint() on QTimer.timeout() to update the XY coordinates is much more efficient than using QPropertyAnimation.
tick = 24
class Dot(object):
def __init__(self):
self.x = random.randrange(-w - 60, 0)
self.randomize()
def randomize(self):
self.color = random.choice(colors)
self.r = random.randrange(5, 22)
self.y = random.randrange(h)
self.x_speed = random.randrange(3000, 9000)
self.y_speed = random.randrange(-50, 50)
def move(self):
self.x += w * tick / self.x_speed
self.y += self.y_speed * tick / self.x_speed
if self.x > w:
self.x = -60
self.randomize()
class Background(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(window)
self.setStyleSheet('background-color: #000')
self.dots = [Dot() for i in range(150)]
self.timer = QTimer()
self.timer.setInterval(tick)
self.timer.timeout.connect(self.animate)
self.timer.start()
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing)
for dot in self.dots:
qp.setBrush(dot.color)
qp.setPen(QPen(dot.color.darker(130), dot.r / 5))
qp.drawEllipse(QPoint(dot.x, dot.y), dot.r, dot.r)
def animate(self):
for d in self.dots:
d.move()
self.repaint()

tkinter: object with different movement patterns

I'm working on a simulation in which some cubes of the same class are moving randomly. My aim is to give them another moving pattern, when they fulfill some characteristics (for example their object number).
My Problem:
If they fulfill the characteristics, how can I "switch off" the first moving pattern and activate the next?
Here a strongly simplified example of the simulation, and how it doesn't work:
from tkinter import *
from random import *
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
self.master = master
self.canvas = canvas
self.number = number
self.x1 = x1
self.y1 = y1
self.x2 = x1 + 15
self.y2 = y1 + 15
self.color = color
self.rectangle = canvas.create_rectangle(x1, y1, self.x2, self.y2, fill=color)
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.canvas.after(100, self.movementII)
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
canvas = Canvas(master, width=900, height=600)
canvas.pack()
master.title("Simulation")
cube = Cubes(master, canvas, 2, randint(50, 800), randint(25, 500), "black")
cube.movement()
mainloop()
how can I "switch off" the first moving pattern and activate the next?
When you call after, it returns a unique identifier. You can save that identifier and then later pass it to after_cancel to cancel the job if it hasn't already run.
I'm not entirely clear what you want, but if you want to turn off the old movement when you switch to the new, it would look something like this:
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
...
self.after_id = None
...
def cancel(self):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = None
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.cancel()
self.after_id = self.canvas.after(100, self.movementII)
self.after_id = self.canvas.after(100, self.movement)
A better way might be to have a single method that you call with after, and it simply calls the appropriate method.
For example, something like this:
def move(self):
if self.number == 1:
self.movement_1()
elif self.number == 2:
self.movement_2()
self.canvas.after(100, self.move)
def movement_1(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
def movement_2(self):
canvas.move(self.rectangle, 0, 0)
Then, to switch the movement method, just change self.number and it will automatically be called at the appropriate time.

PyGame Simulation Coordinate Bug

I want to create a simulation. (not sure where it will head)
I created a Human class and HumanRace class.
The HumanRace class contains every Human class object within an list.
Currently I'm using 2 HumanRaces and 10 Humans per race.
Humans get represented with a circle, the color of it is assigned to the HumanRace. Also there will be a circle representing the spawn area of this race, although the math behind this is a bit sloppy.
Humans spawn within the spawn area of their race. That's where the problem comes in. If I render only 1 race everything is okay, but if I do render the second too, it will redraw those positions with a different color, even tho my the Humans added to the race have different positions. So I guess my render definition is incorrect.
Main.py
import sys, pygame
from HumanRace import *
from random import randint
BLACK = 0, 0, 0
WHITE = 255, 255, 255
GREEN = 124, 252, 0
RED = 255, 0, 0
PURPLE = 255, 23, 240
BLUE = 0, 68, 255
YELLOW = 255, 255, 0
humanRaces = []
def generateHuman(race):
spawnpos = (race.getSpawnX(), race.getSpawnY())
rsize = race.getSize()
rHP = randint(10, 100)
rATK = randint(20, 100)
rAGIL = randint(20, 50)
x = randint(spawnpos[0] - rsize, spawnpos[0] + rsize)
y = randint(spawnpos[1] - rsize, spawnpos[1] + rsize)
print(x, y, rHP)
return Human(x, y, rHP, rATK, rAGIL)
def generateHumans(race, amount):
for i in range(0, amount):
newHuman = generateHuman(race)
race.addHuman(newHuman)
barbaren = HumanRace("Barbar", 300, 320)
barbaren.setColor(GREEN)
barbaren.setSpawnColor(PURPLE)
generateHumans(barbaren, 4)
chinesen = HumanRace("Chinese", 100, 100)
chinesen.setColor(YELLOW)
chinesen.setSpawnColor(BLUE)
generateHumans(chinesen, 4)
humanRaces.append(barbaren)
humanRaces.append(chinesen)
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Fighting Era")
clock = pygame.time.Clock()
def renderHumanRaceSpawn():
raceNumber = len(humanRaces)
for i in range(0, raceNumber):
currRace = humanRaces[i]
scolor = currRace.getSpawnColor()
spawnX = currRace.getSpawnX()
spawnY = currRace.getSpawnY()
radius = currRace.getSize()
pygame.draw.circle(screen, scolor, (spawnX, spawnY), radius * 2, 0)
def renderHumanRace():
for i in range(0, 2):
currRace = humanRaces[i]
color = currRace.getColor()
for x in range(0, currRace.getSize()):
currHuman = currRace.getAt(x)
posX = currHuman.getPosX()
posY = currHuman.getPosY()
pygame.draw.circle(screen, color, (posX, posY), 3, 0)
while 1:
clock.tick(246)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(BLACK)
renderHumanRaceSpawn()
renderHumanRace()
pygame.display.flip()
Human and HumanRace Class
class Human:
def __init__(self, posX, posY, HP, ATK, AGIL):
self.posX = posX
self.posY = posY
self.HP = HP
self.ATK = ATK
self.AGIL = AGIL
def getIndex(self):
return self.index
def getPosX(self):
return self.posX
def getPosY(self):
return self.posY
def setPosX(self, posX):
self.posX = posX
def setPosY(self, posY):
self.posY = posY
from Human import *
class HumanRace:
size = 0
humans = []
spawn = (0, 0)
color = 0, 0, 0
spawnColor = 1, 1, 1
def __init__(self, name, spawnX, spawnY):
self.name = name
self.spawnX = spawnX
self.spawnY = spawnY
def getName(self):
return self.name
def getSpawnX(self):
return self.spawnX
def getColor(self):
return self.color
def setColor(self, color):
self.color = color
def getSpawnColor(self):
return self.spawnColor
def setSpawnColor(self, color):
self.spawnColor = color
def getSpawnY(self):
return self.spawnY
def getSize(self):
return self.size
def addHuman(self, human):
global size
self.humans.append(human)
self.size += 1
def getAt(self, index):
return self.humans[index]
def removeAt(self, index):
global size
self.humans.delete(index)
self.size -= 1
Picture of Window
Your humans list inside the HumanRace class is a class variable. All instances of HumanRace will share the same humans list. You should change it to an instance variable by putting inside the __init__ function.
Something like this:
class HumanRace:
def __init__(self, name, spawnX, spawnY):
self.name = name
self.spawn = (spawnX, spawnY)
self.size = 0 # dont really need this, you can just len(humans)
self.humans = []
self.color = 0, 0, 0
self.spawnColor = 1, 1, 1

Redraw circle - python

I have this bit of code here:
from tkinter import *
class player():
def __init__(self, radius, xcoordinate = 0, ycoordinate = 0):
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
def moveRight(self, event):
self.xcoordinate += 25
self.draw()
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.ycoordinate += 25
self.draw()
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
world = client()
world.title("World")
world.bind('<Right>', self.moveRight)
world.bind('<Left>', self.moveLeft)
canvas = Canvas(world, width=200, height=200, borderwidth=0,highlightthickness=0, bg="black")
canvas.grid()
canvas.draw_player(self.xcoordinate, self.ycoordinate, self.radius, fill="blue", width=4)
world.mainloop()
class client(Tk):
def __init__(self):
super().__init__()
def draw_player(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
Canvas.draw_player = draw_player
p1 = player(50)
p1.draw()
The problem is that whenever I press the right or left arrow keys, it calls the draw() method. The draw() method constructs a new client object and etc.
So you end up opening multiple windows each with a circle with different x and y coordinates. How do I make this such that when I call draw() it only edits the x and y coordinates and redraws the circle on the same window?
Please no suggestions to use pygame, my IDE gets errors when I try to import the module.
The code can be simplified by passing the move parameters to a single function.
from tkinter import *
from functools import partial
class player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', partial(self.move_oval, 25, 0))
self.master.bind('<Left>', partial(self.move_oval, -25, 0))
self.master.bind('<Up>', partial(self.move_oval, 0, -25))
self.master.bind('<Down>', partial(self.move_oval, 0, 25))
self.draw() ## called once
def move_oval(self, x, y, event):
self.canvas.move(self.oval_id, x, y)
print("key pressed", x, y)
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = player(master, 50)
master.mainloop()
I assume that
redraws the circle on the same window
means moving the circle, and does not mean drawing a second circle in the same space. Use the move() function for a canvas object to do this http://effbot.org/tkinterbook/canvas.htm . Note that you have to save a reference to the object to be moved. Also your moveLeft() function doesn't.
class Player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', self.moveRight)
self.master.bind('<Left>', self.moveLeft)
self.draw() ## called once
def moveRight(self, event):
self.canvas.move(self.oval_id, 25, 0)
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.canvas.move(self.oval_id, 0, 25)
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = Player(master, 50)
master.mainloop()

pyglet: changing the image of a sprite instance when pressing a button

This code displays the image assassin1.png on a black background. As soon as I press the key the image moves to the right and stops moving as soon as I release the key.
As soon as I press the key it should also change to the image assassin2.png and when I release the key it should change back to assassin1.png.
This code however never displays the assassin2.png image while moving. Why is this so and how can I fix this?
import pyglet
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img):
pyglet.sprite.Sprite.__init__(self, img, x = 50, y = 30)
def stand(self):
self.img = pyglet.image.load("assassin1.png")
return self
def move(self):
self.img = pyglet.image.load('assassin2.png')
return self
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"))
self.keys_held = []
self.schedule = pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
def on_draw(self):
self.clear()
self.batch_draw.draw()
self.player.draw()
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player = self.player.move()
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
self.player = self.player.stand()
print "The 'RIGHT' key was released"
def update(self, interval):
if pyglet.window.key.RIGHT in self.keys_held:
self.player.x += 50 * interval
if __name__ == "__main__":
window = Game()
pyglet.app.run()
I looked at the pyglet's source code and i think the problem is here:
self.img = pyglet.image.load(...
The image is stored in self.image variable, not self.img. So changing to:
self.image = pyglet.image.load(...
should update the image that the sprite is using.

Categories