Why does this shape in Tkinter update slowly? - python

Attempted to do simple movement in tkinter:
import tkinter as tk
class GameApp(object):
"""
An object for the game window.
Attributes:
master: Main window tied to the application
canvas: The canvas of this window
"""
def __init__(self, master):
"""
Initialize the window and canvas of the game.
"""
self.master = master
self.master.title = "Game"
self.master.geometry('{}x{}'.format(500, 500))
self.canvas = tk.Canvas(self.master)
self.canvas.pack(side="top", fill="both", expand=True)
self.start_game()
#----------------------------------------------#
def start_game(self):
"""
Actual loading of the game.
"""
player = Player(self)
#----------------------------------------------#
#----------------------------------------------#
class Player(object):
"""
The player of the game.
Attributes:
color: color of sprite (string)
dimensions: dimensions of the sprite (array)
canvas: the canvas of this sprite (object)
window: the actual game window object (object)
momentum: how fast the object is moving (array)
"""
def __init__(self, window):
self.color = ""
self.dimensions = [225, 225, 275, 275]
self.window = window
self.properties()
#----------------------------------------------#
def properties(self):
"""
Establish the properties of the player.
"""
self.color = "blue"
self.momentum = [5, 0]
self.draw()
self.mom_calc()
#----------------------------------------------#
def draw(self):
"""
Draw the sprite.
"""
self.sprite = self.window.canvas.create_rectangle(*self.dimensions, fill=self.color, outline=self.color)
#----------------------------------------------#
def mom_calc(self):
"""
Calculate the actual momentum of the thing
"""
self.window.canvas.move(self.sprite, *self.momentum)
self.window.master.after(2, self.mom_calc)
#----------------------------------------------#
#----------------------------------------------#
root = tk.Tk()
game_window = GameApp(root)
Where self.momentum is an array containing 2 integers: one for the x movement, and another for the y movement. However, the actual movement of the rectangle is really slow (about 5 movements per second), with the self.window.master.after() time not seeming to have an effect.
Previously on another tkinter project I had managed to get really responsive tkinter movement, so I'm just wondering if there is a way I can minimize that movement updating time in this case, by either using a different style of OOP, or just different code altogether.
UPDATE: Turns out the time in the .after() method does matter, and it actually stacks onto the real time of the method. After using timeit to time calling the method, I got this output:
>>> print(timeit.timeit("(self.window.master.after(2, self.mom_calc))", number=10000, globals={"self":self}))
0.5395521819053108
So I guess the real question is: Why is that .after() method taking so long?
UPDATE 2: Tested on multiple computers, movement is still slow on any platform.

"The default Windows timer resolution is ~15ms. Trying to fire a timer every 1ms is not likely to work the way you want, and for a game is probably quite unnecessary (a display running a 60FPS updates only every ~16ms). See Why are .NET timers limited to 15 ms resolution?"
Found the solution at Python - tkinter call to after is too slow where Andrew Medico gave a good answer (in a comment).

I don't see the problem you report on Windows 10 using Python 3.6 at least. I tested the program as shown and had to add a root.mainloop() at the end. This showed no rectangle because the object has moved off the canvas too fast to see.
So I modified this to bounce between the walls and added a counter to print the number of mom_calc calls per second. With the after timeout set at 20ms I get 50 motion calls per second, as expected. Setting this to 2ms as in your post I get around 425 per second so there is a little error here and its taking about 2.3 or 2.4 ms per call. This is a bit variable as other processes can take up some of the time at this granularity.
Here is the (slightly) modified code:
import tkinter as tk
class GameApp(object):
"""
An object for the game window.
Attributes:
master: Main window tied to the application
canvas: The canvas of this window
"""
def __init__(self, master):
"""
Initialize the window and canvas of the game.
"""
self.master = master
self.master.title = "Game"
self.master.geometry('{}x{}'.format(500, 500))
self.canvas = tk.Canvas(self.master, background="white")
self.canvas.pack(side="top", fill="both", expand=True)
self.start_game()
#----------------------------------------------#
def start_game(self):
"""
Actual loading of the game.
"""
player = Player(self)
#----------------------------------------------#
#----------------------------------------------#
class Player(object):
"""
The player of the game.
Attributes:
color: color of sprite (string)
dimensions: dimensions of the sprite (array)
canvas: the canvas of this sprite (object)
window: the actual game window object (object)
momentum: how fast the object is moving (array)
"""
def __init__(self, window):
self.color = ""
self.dimensions = [225, 225, 275, 275]
self.window = window
self.movement = 0
self.movement_last = 0
self.properties()
#----------------------------------------------#
def properties(self):
"""
Establish the properties of the player.
"""
self.color = "blue"
self.momentum = [5, 0]
self.draw()
self.mom_calc()
self.velocity()
#----------------------------------------------#
def draw(self):
"""
Draw the sprite.
"""
self.sprite = self.window.canvas.create_rectangle(*self.dimensions, fill=self.color, outline=self.color)
#----------------------------------------------#
def mom_calc(self):
"""
Calculate the actual momentum of the thing
"""
pos = self.window.canvas.coords(self.sprite)
if pos[2] > 500:
self.momentum = [-5, 0]
elif pos[0] < 2:
self.momentum = [5, 0]
self.window.canvas.move(self.sprite, *self.momentum)
self.window.master.after(2, self.mom_calc)
self.movement = self.movement + 1
def velocity(self):
print(self.movement - self.movement_last)
self.movement_last = self.movement
self.aid_velocity = self.window.master.after(1000, self.velocity)
#----------------------------------------------#
#----------------------------------------------#
if __name__ == '__main__':
root = tk.Tk()
game_window = GameApp(root)
root.mainloop()

Related

How to set turtle tracer false using tkinter?

I have to generate two turtle windows and draw in each one, so I'm using tkinter to create and show the windows. My code currently opens the right screen and draws in it, but the turtle is really slow so I want to set the turtle tracer to false to use the update function, but I can't figure out how to.
This is my turtle_interpreter.py file, which has all the functions I use to draw the L-system:
import turtle
from tkinter import *
class Window(Tk):
def __init__(self, title, geometry):
super().__init__()
self.running = True
self.geometry(geometry)
self.title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy_window)
self.canvas = Canvas(self)
self.canvas.pack(side=LEFT, expand=True, fill=BOTH)
self.turtle = turtle.RawTurtle(turtle.TurtleScreen(self.canvas))
def update_window(self):
'''
sets window to update
'''
if self.running:
self.update()
def destroy_window(self):
'''
sets window to close
'''
self.running = False
self.destroy()
def drawString(turt, dstring, distance, angle):
'''Interpret the characters in string dstring as a series
of turtle commands. Distance specifies the distance
to travel for each forward command. Angle specifies the
angle (in degrees) for each right or left command. The list
of turtle supported turtle commands is:
F : forward
- : turn right
+ : turn left
'''
for char in dstring:
if char == 'F':
turt.forward(distance)
elif char == '-':
turt.right(angle)
elif char == '+':
turt.left(angle)
def place(turt, xpos, ypos, angle=None):
'''
places turtle at given coordinates and angle
'''
turt.penup()
turt.goto(xpos, ypos)
if angle != None:
turt.setheading(angle)
turt.pendown()
def goto(turt, xpos, ypos):
'''
moves turtle to given coordinates
'''
turt.penup()
turt.goto(xpos, ypos)
turt.pendown()
def setColor(turt, color):
'''
sets turtle color
'''
turt.color(color)
And this is the file where the functions get called. Running it draws the L-system.
import turtle_interpreter as turt_int
import lsystem_scene_three as lsystem
def turtle_scene_two():
'''
generates scene two
'''
# create window
win_two = turt_int.Window('Turtle Scene 2', '640x480+650+0')
# assign turtle
turt2 = win_two.turtle
# lsystem setup
lsystemFile = lsystem.Lsystem('lsystem_scene_two.txt')
tstr = lsystemFile.buildString(4)
# draw stuff
turt_int.setColor(turt2, (0, 0, 0))
turt_int.place(turt2, 0, -200, 90)
turt_int.drawString(turt2, tstr, 4, 90)
# update window (loop)
while win_two.running:
win_two.update_window()
turtle_scene_two()
Hope this makes sense. Let me know if it doesn't.
Appreciate your help!
Tried a few things but nothing was promising. Calling turtle generates another screen (which I don't want).
Since you didn't provide all your code, I can't test this, so I'm guessing a good start would be changing this:
self.turtle = turtle.RawTurtle(turtle.TurtleScreen(self.canvas))
to something like:
screen = turtle.TurtleScreen(self.canvas)
screen.tracer(False)
self.turtle = turtle.RawTurtle(screen)

How to set canvas camera position in python 3.7

I am writing a mario-like game (non-commercial, I'm just trying to brush up on coding) and I need to follow one of two players. Messing with canvas.move doesn't work for my needs, I was wondering if there was some way to actually control what position it renders from (such as one player offscreen and the camera moves to make him onscreen). My code:
from tkinter import *
import math,time
## Declare your many GAME CLASSES here
class Camera:
def __init__(self,game):
self.cnv=game.canvas
self.tracker=None
def track(self,player):
self.tracker=player
def run(self):
cords=self.cnv.coords(self.tracker.id)
self.cnv.move(ALL,250-cords[0],250-cords[1])
class Human:
def __init__(self,game):
game.tk.bind_all("<KeyPress>",self.press)
game.tk.bind_all("<KeyRelease>",self.release)
self.left=False
self.right=False
self.jump=False
def run(self,player):
if self.left:
player.left()
if self.right:
player.right()
if self.jump:
player.jump()
self.jump=False
def press(self,event):
if event.keysym=="Left":
self.left=True
if event.keysym=="Right":
self.right=True
if event.keysym=="Up":
self.jump=True
def release(self,event):
if event.keysym=="Left":
self.left=False
if event.keysym=="Right":
self.right=False
def hitx(self):
pass
def hity(self):
pass
class Computer:
def __init__(self):
self.direction=1
def run(self,player):
if player.onground:
if self.direction==1:
player.right()
if self.direction==-1:
player.left()
def hitx(self):
self.direction*=-1
def hity(self):
pass
class Player:
def __init__(self,startx,starty,game,controller):
self.id=game.canvas.create_image(250+startx,250+starty,anchor=CENTER,image=game.player_images[0])
self.game=game
self.xv=0
self.yv=0
self.controller=controller
self.speed=4
self.face=0
self.side=0
self.onground=False
def left(self):
self.xv-=self.speed
self.side=1
self.face=tick%2
def right(self):
self.xv+=self.speed
self.side=0
self.face=tick%2
def jump(self):
if self.onground:
self.yv=math.sqrt(self.speed)*-10
def look(self):
bricks=[]
for x in self.game.tileset:
coords=self.game.canvas.coords(x[0])
playercoords=self.game.canvas.coords(self.id)
xdifference=abs(coords[0]-playercoords[0])
ydifference=abs(coords[1]-playercoords[1])
if xdifference<250 and ydifference<250:
bricks.append(coords)
def run_slf(self):
if not self.onground:
self.face=2
self.set_face()
self.face=0
self.onground=False
self.game.canvas.move(self.id,self.xv,0)
## Work in progress: Do the X COLLISIONS FOR THE PLAYER
bounds=self.getbounds()
if len(self.game.canvas.find_overlapping(*bounds))>1:
while len(self.game.canvas.find_overlapping(*bounds))>1:
self.game.canvas.move(self.id,abs(self.xv)/self.xv*-1,0)
bounds=self.getbounds()
self.xv=0
self.controller.hitx()
self.game.canvas.move(self.id,0,self.yv)
## Work in progress: Do the Y COLLISIONS FOR THE PLAYER
bounds=self.getbounds()
if len(self.game.canvas.find_overlapping(*bounds))>1:
while len(self.game.canvas.find_overlapping(*bounds))>1:
self.game.canvas.move(self.id,0,abs(self.yv)/self.yv*-1)
bounds=self.getbounds()
self.yv=0
self.onground=True
self.controller.hity()
self.controller.run(self)
self.xv*=0.8
self.yv+=1
def set_face(self):
self.game.canvas.itemconfig(self.id,image=game.player_images[self.face+(self.side*3)])
def getbounds(self):
width=25
height=45
cords=self.game.canvas.coords(self.id)
return [cords[0]-width,cords[1]-height,cords[0]+width,cords[1]+height]
class Game:
def __init__(self,width,height):
self.tileset=[]
self.players=[]
self.tk=Tk()
self.brick_types={"regular":[False,PhotoImage(file="blocks/brick_basic.png")]}
self.tk.resizable(0,0)
self.canvas=Canvas(self.tk,width=width,height=height,background="white")
self.canvas.pack()
self.player_images=[PhotoImage(file="marioAnim/face.png"),PhotoImage(file="marioAnim/walk.png"),PhotoImage(file="marioAnim/jump.png"),PhotoImage(file="marioAnim/face-2.png"),PhotoImage(file="marioAnim/walk-2.png"),PhotoImage(file="marioAnim/jump-2.png")]
def run(self):
self.tk.update_idletasks()
self.tk.update()
for x in self.players:
x.run_slf()
def addbrick(self,x,y,tp):
self.tileset.append([self.canvas.create_image(x*50+250,y*50+250,anchor="nw",image=self.brick_types[tp][1]),self.brick_types[tp][0]])
def addline(self,x,y,xd,yd,length,tp):
for i in range(0,length):
self.addbrick(x+xd*i,y+yd*i,tp)
def addplayer(self,player):
self.players.append(player)
## Declare your GLOBAL VARIABLES here.
game=Game(500,500)
human=Human(game)
computer=Computer()
cplayer=Player(-50,0,game,computer)
hplayer=Player(50,0,game,human)
tick=0
camera=Camera(game)
camera.track(cplayer)
## BUILD TILESET
game.addline(-8,4,1,0,16,'regular')
game.addbrick(-5,3,'regular')
game.addbrick(4,3,'regular')
## ADD PLAYERS
game.addplayer(cplayer)
game.addplayer(hplayer)
while 1:
tick+=1
camera.run()
game.run()
time.sleep(0.02)
I am using python 3.7 with Tkinter.
You are asking how to programatically scroll the canvas. The xview and yview methods of the canvas control what portion of the full drawable area is visible at the current time: xview, xview_moveto, xview_scroll, yview, yview_moveto, and yview_scroll.
The xview_scroll and yview_scroll methods accept an integer amount, and then the string "units" or "pages". "units" refers to the distance defined by the xscrollincrement and yscrollincrement attributes. "pages" causes the window to scroll in increments of 9/10ths of the window width or height.
For example, if you want to be able to scroll by single pixels you can set xscrollincrement to 1, and the use xview_scroll to move left or right.
canvas.configure(xscrollincrement=1)
...
canvas.xview_scroll(1, "units")

Create a fullscreen app in the secondary screen using tkinter and python 3.7.2

I'm trying to create a simple slideshow using Tkinter and Python 3.7.2. I want the slide show to display images on secondary screen and in fullscreen mode. I have tried to use only one window and two windows as suggested here. This is the code I have written:
import tkinter as tk
from PIL import Image, ImageTk
class App(tk.Tk):
'''Tk window/label adjusts to size of image'''
def __init__(self, image_files, x, y, delay):
# the root will be self
tk.Tk.__init__(self)
# set width. height, x, y position
self.geometry('%dx%d+%d+%d'%(912,1140,0,0)) #Window on main screen
#create second screen window
self.top2 = tk.Toplevel(self,bg="grey85")
self.top2.geometry('%dx%d+%d+%d'%(912,1140,-912,0)) # The resolution of the second screen is 912x1140.
#The screen is on the left of the main screen
self.top2.attributes('-fullscreen', False) #Fullscreen mode
self.pictures = image_files
self.picture_display = tk.Label(self.top2, width=912, height=1140)
self.picture_display.pack()
self.delay = delay
self.index = 1
self.nImages = len(image_files)
def start_acquisition(self):
if self.index == self.nImages+1:
self.destroy()
return
self.load = Image.open(self.pictures[self.index-1])
self.render = ImageTk.PhotoImage(self.load)
self.picture_display['image'] = self.render
self.index += 1
self.after(self.delay, self.start_acquisition)
def run(self):
self.mainloop()
# set milliseconds time between slides
delay = 3500
image_files = [
'1805Circle Test Output.bmp', #images resolution is 912x1140
'8233Circle Test Input.bmp',
'cross.bmp'
]
x = 0 #Not used currently
y = 0 #Not used currently
app = App(image_files, x, y, delay)
app.start_acquisition()
app.run()
print('Finished')
The code works as expected when the fullscreen attribute is "False". As soon as I put this attribute "True" the "top2" window appears on the main screen. The same thing happen if only one window is used. Could you please help me find a solution for this problem? thx

Kivy performance degradation over time

I am learning Kivy, and to make it more fun I am creating a small 2D game. For now its just a tank that can be controlled with WASD and shot projectiles with o.
The problem is that the FPS degrades over time. This happens even if I do nothing in the game. I don´t have an FPS counter, but its something like half the FPS after a minute of gametime.
My feeling is that the problem lies somewhere in the updating of canvas in widgets. Since the slowdowns occour even if the player does nothing for the whole game, its seems like there is data somewhere that is just added and added. I don´t know how to better explain it, other than its weird ...
A quick overview of how the game is programmed so far:
The main widget is the class Game. It detects keypresses and runs "Clock.schedule_interval-function".
The Tank widget is a child of Game. It holds some data and loads the hull and turret sprites via Kivys Image widget, which becomes its child. It has its own update function that updates everything tank-related, including setting the position of its canvas and rotating the hull and turret image canvas. The update-function in the Tank widget class is inwoked by "Clock.schedule_interval" in Game class.
The Shots widget does the same as the Tank widget, only it holds data for each shot fired instead
"Clock schedule_interval"-function holds a list of each shot widget, and deletes them when they get off screen. However, the slowdown problems persist even if no shots are fired.
I have attached the complete code. This might be excessive, but I don´t know wich part of it that provokes slowdowns. If you want to run the game, just put those four python-files in the same folder, and the images in a sub-folder called "images tank".
I hope someone can take a look at it :)
main.py:
#Import my own modules:
import tank
import shot
from stats import Stats
#Import kivy:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
#Set window size properties:
from kivy.config import Config
Config.set('graphics','resizable',0)
from kivy.core.window import Window
class Game(Widget):
def __init__(self):
#General settings:
super(Game, self).__init__()
Clock.schedule_interval(self.update, 1.0/60.0)
#Add keyboard:
self._keyboard = Window.request_keyboard (callback=None, target=self, input_type="text")
#Bind the keyboard to a function:
self._keyboard.bind(on_key_down=self.keypress)
self._keyboard.bind(on_key_up=self.keyUp)
#P1 tank starting values:
self.keypress_p1 = {"forward":False, "backward":False, "left":False, "right":False, "turret_left":False, "turret_right":False, "fire":False}
#P1 tank widget:
self.tank_p1 = tank.Tank()
self.add_widget(self.tank_p1)
#P1 shots list:
self.shotsList_p1 = []
#Keyboard press detection:
def keypress(self, *args):
key = args[2]
if key == "w":
self.keypress_p1["forward"]=True
if key == "s":
self.keypress_p1["backward"]=True
if key == "a":
self.keypress_p1["left"]=True
if key == "d":
self.keypress_p1["right"]=True
if key == "q":
self.keypress_p1["turret_left"]=True
if key == "e":
self.keypress_p1["turret_right"]=True
if key == "o":
self.keypress_p1["fire"]=True
#Keyboard button up detection:
def keyUp(self, *args):
key = args[1][1]
if key == "w":
self.keypress_p1["forward"]=False
if key == "s":
self.keypress_p1["backward"]=False
if key == "a":
self.keypress_p1["left"]=False
if key == "d":
self.keypress_p1["right"]=False
if key == "q":
self.keypress_p1["turret_left"]=False
if key == "e":
self.keypress_p1["turret_right"]=False
if key == "o":
self.keypress_p1["fire"]=False
#Parent update function that the clock runs:
def update(self, dt):
#Add new shots:
if self.keypress_p1["fire"]:
self.shot = shot.Shots(self.tank_p1.my_pos, self.tank_p1.my_angle+self.tank_p1.my_turretAngle)
self.shotsList_p1.append(self.shot)
self.add_widget(self.shot)
self.keypress_p1["fire"] = False
#P1 tank update:
self.tank_p1.update(self.keypress_p1)
#P1 shot update:
for i in range(len(self.shotsList_p1)-1,-1,-1):
self.shotsList_p1[i].update()
#Remove widgets that are outside the screen:
if ( 0<=self.shotsList_p1[i].my_pos[0]<Stats.winSize[0] and 0<=self.shotsList_p1[i].my_pos[1]<Stats.winSize[1] )==False:
self.remove_widget(self.shotsList_p1[i])
del self.shotsList_p1[i]
class MyApp(App):
def build(self):
game = Game()
Window.size = Stats.winSize
return game
MyApp().run()
tank.py:
#Import own modules:
from stats import Stats
#import python:
import math
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo
class Tank(Widget):
def __init__(self):
super(Tank, self).__init__()
#Position and rotation values for the tank:
self.my_pos = [0,0]
self.posChange = [0,0]
self.my_angle = 0
self.angleChange = 0
self.my_turretAngle = 0
self.turretAngleChange = 0
#Hull widget:
self.hull = Hull()
self.add_widget(self.hull)
self.hull.center_x = self.my_pos[0]
self.hull.center_y = self.my_pos[1]
#Turret widget:
self.turret = Turret()
self.add_widget(self.turret)
self.turret.center_x = self.my_pos[0]
self.turret.center_y = self.my_pos[1]
def update(self, keypress):
if keypress["forward"]:
self.my_pos[0] -= Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
self.my_pos[1] += Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
self.posChange[0] = -Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
self.posChange[1] = Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
if keypress["backward"]:
self.my_pos[0] -= Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
self.my_pos[1] += Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
self.posChange[0] = -Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
self.posChange[1] = Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
if keypress["left"]:
self.my_angle += Stats.hull_t1["left"]
self.angleChange = Stats.hull_t1["left"]
if keypress["right"]:
self.my_angle += Stats.hull_t1["right"]
self.angleChange = Stats.hull_t1["right"]
if keypress["turret_left"]:
self.my_turretAngle += Stats.turret_t1["left"]
self.turretAngleChange = Stats.turret_t1["left"]
if keypress["turret_right"]:
self.my_turretAngle += Stats.turret_t1["right"]
self.turretAngleChange = Stats.turret_t1["right"]
#Tank Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
#Rotate hull image:
with self.hull.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.hull.center
self.rot.angle = self.angleChange
with self.hull.canvas.after:
PopMatrix()
#Rotate turret image:
with self.turret.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.turret.center
self.rot.angle = self.turretAngleChange + self.angleChange
with self.turret.canvas.after:
PopMatrix()
#Reset pos, angle and turretAngle change values:
self.posChange = [0,0]
self.angleChange = 0
self.turretAngleChange = 0
#--------------------------------------------------------------------------------------------------
class Hull(Image):
def __init__(self):
super(Hull, self).__init__(source="images tank/Tank.png")
self.size = self.texture_size
class Turret(Image):
def __init__(self):
super(Turret, self).__init__(source="images tank/GunTurret.png")
self.size = self.texture_size
shot.py:
#Import own modules:
from stats import Stats
#import python:
import math
from copy import copy
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo
class Shots(Widget):
def __init__(self, tankPos, turretAngle):
super(Shots, self).__init__()
#Shot data:
self.my_pos = copy(tankPos)
self.my_angle = turretAngle
self.angleChange = self.my_angle
self.posChange = [ -Stats.shot_t1["speed"]*math.sin(math.radians(self.my_angle)), Stats.shot_t1["speed"]*math.cos(math.radians(self.my_angle)) ]
#Add image:
self.shotImg = ShotImg()
self.add_widget(self.shotImg)
self.shotImg.pos = self.my_pos
self.shotImg.center_x = self.my_pos[0]
self.shotImg.center_y = self.my_pos[1]
def update(self):
self.my_pos[0] += self.posChange[0]
self.my_pos[1] += self.posChange[1]
#Shot Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
#Rotate shot image:
if self.angleChange != 0:
with self.shotImg.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.shotImg.center
self.rot.angle = self.angleChange
with self.shotImg.canvas.after:
PopMatrix()
self.angleChange = 0
class ShotImg(Image):
def __init__(self):
super(ShotImg, self).__init__(source="images tank/Bullet.png")
self.size = self.texture_size
stats.py:
class Stats:
winSize = (800,800)
hull_t1 = {"forward":2, "backward":-2, "left":2, "right": -2}
turret_t1 = {"left":5, "right":-5}
shot_t1 = {"speed":3}
images should go in a subfolder called "images tank":
Bullet
GunTurret
Tank
The way you manage your position will create 8 new instructions per tank, and 6 per shot, every frame, which, at 60fps, will quickly create thousands of instructions, and for kivy will be slower and slower to process them.
#Tank Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
you don't want to do that, you want to create one Translate insruction (and same for Rotate) in your widget, and update it, move this block to __init__ and save Translate to self.translate for example, then in update, instead of using posChange, simply do self.translate.x, self.translate.y = self.my_pos
apply the same logic for rotation, and for shots, and the performances should be much more stable over time.
I had the same issue, I'm working on a space invaders game and it happened to be the way I treated my logic, I used "with self.canvas"
to draw my widgets, which appeared to be on the long term exhaustive as widgets that get generated on canvas are not being cleared, which is an important thing to be concerned about.
I Fixed it with creating a class for my objects (enemy, bullet, player) and when I need lets say 6 enemies to be created, I create a list of enemy widgets to keep track of them, and them add them to my on my main game widget by using self.add_widget(YourWidgetItem)
and simply when the widget has ended its purpose, like an enemy dying, I set my dead variable for this widget to be true, then scan for dead widgets and remove them from my main game widget by using self.remove_widget(YourWidgetItem)
this is one of many other possible solutions, you just need to take into consideration killing the widget that get off your screen to no overload your game on higher levels
You can check my project if it would help:
https://github.com/steveroseik/Invaders_Game

Make intro screen with pyglet

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

Categories

Resources