I have an image of a map. I would like to make the left and right (East and west) edges of the map connect so that you can scroll forever to the right or left and keep scrolling over the same picture. I've looked around and can't find anything on the topic (likely because I don't know what to call it). I would also like to have the picture in a frame that I can grab and drag to move the picture around. I was trying to do this in Tkinter, but I have a feeling there are probably easier ways to do this.
(actually, you are asking 2 different, not very precise questions)
scroll forever: Independent from python a common approach is to
mirror the images at the edges so you can implement a virtually
endless world from 1 or some images (tiles of the map).
GUI framework/API: From my experience Qt (so in your case maybe PyQt) is
well documented and designed to quite easily realize OS independent
GUI.
I was able to get the results I wanted with pygame.
blit(source, dest, area=None, special_flags = 0) -> Rect
I setup a rectangle twice the width of my map, and set up a function to always have two maps drawn side by side. I added functions to move the map as a % of the tile width.
SCREENRECT = Rect(0, 0, 6025, 3010)
...
class Arena:
speed = 15
def __init__(self):
w = SCREENRECT.width
h = SCREENRECT.height
self.tilewidth = self.oceantile.get_width()
self.tileheight = self.oceantile.get_height()
print self.tilewidth, self.tileheight
self.counter = 0
self.counter2 = 0
self.ocean = pygame.Surface((w+self.tilewidth,h)).convert()
for x in range(w/self.tilewidth):
for y in range(h/self.tileheight):
self.ocean.blit(self.oceantile, (x*self.tilewidth, y*self.tileheight))
def left(self):
self.counter = (self.counter - self.speed) % self.tilewidth
def right(self):
self.counter = (self.counter + self.speed) % self.tilewidth
def up(self):
if self.counter2 > 0: self.counter2 = (self.counter2 - self.speed) % self.tileheight
def down(self):
if self.counter2 < 1140: self.counter2 = (self.counter2 + self.speed) % self.tileheight
screen.blit(arena.map, (0, 0), (arena.counter, arena.counter2, SCREENRECT.width, SCREENRECT.height))
I then used the blit function to draw the map, with x and y pixels shaved off via the area input.
blit(source, dest, area=None, special_flags = 0) -> Rect
screen.blit(arena.map, (0, 0), (arena.counter, arena.counter2, SCREENRECT.width, SCREENRECT.height)).
Currently I control the scrolling of the mouse with the keyboard, but the grab and drag functionality shouldn't be to hard to figure out with the pygame modules.
Related
I wrote some code to show a circle and a rectangle randomly on the screen with PyQt6. and I want to detect if these two objects have a collision then I make them red otherwise I make them green.
But how should I detect whether there is a collision or not?
here is my code
from random import randint
from sys import argv
from PyQt6.QtCore import QRect, QTimer, Qt, QMimeData
from PyQt6.QtGui import QColor, QKeyEvent, QMouseEvent, QPainter, QPen, QPaintEvent, QBrush, QDrag
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QMainWindow, QPushButton
class Window(QMainWindow):
def __init__(self) -> None:
super().__init__()
screenWidth = 1920
screenHeight = 1080
self.isRunning = True
self.windowWidth = 1200
self.windowHeight = 800
self.clockCounterVariable = 0
self.milSec = 0
self.seconds = 0
self.minutes = 0
self.hours = 0
self.setWindowTitle("Smart rockets")
self.setGeometry((screenWidth - self.windowWidth) // 2, (screenHeight - self.windowHeight) // 2, self.windowWidth, self.windowHeight)
self.setLayout(QVBoxLayout())
self.setStyleSheet("background-color:rgb(20, 20, 20);font-size:20px;")
self.clock = QTimer(self)
self.clock.timeout.connect(self.clockCounter)
self.clock.start(10)
button = QPushButton("Refresh", self)
button.setGeometry(20,self.windowHeight - 60,self.windowWidth - 40,40)
button.setStyleSheet("background-color:rgb(80, 80, 80);font-size:20px;")
button.setCheckable(True)
button.clicked.connect(self.refreshRectAndCircle)
rectangleWidth = randint(50, 500)
rectangleHeight = randint(50, 500)
self.rectangle = QRect(randint(0, self.windowWidth - rectangleWidth), randint(0, self.windowHeight - rectangleHeight - 80), rectangleWidth, rectangleHeight)
circleRadius = randint(50, 200)
self.circle = QRect(randint(0, self.windowWidth - circleRadius), randint(0, self.windowHeight - circleRadius - 80), circleRadius, circleRadius)
self.show()
def dragEnterEvent(self, event) -> super:
event.accept()
def keyPressEvent(self, event: QKeyEvent) -> super:
key = QKeyEvent.key(event)
if key == 112 or key == 80: # P/p
if self.isRunning:
self.clock.stop()
print("pause process")
self.isRunning = False
else:
print("continue process")
self.isRunning = True
self.clock.start(10)
elif (key == 115) or (key == 83): # S/s
self.closeWindow()
return super().keyPressEvent(event)
def mousePressEvent(self, event: QMouseEvent) -> super:
if event.buttons() == Qt.MouseButton.LeftButton:
if self.isRunning:
self.clock.stop()
print("pause process")
self.isRunning = False
else:
print("continue process")
self.isRunning = True
self.clock.start(10)
return super().mousePressEvent(event)
def clockCounter(self) -> None:
self.clockCounterVariable += 1
self.update()
def paintEvent(self, a0: QPaintEvent) -> super:
painter = QPainter()
self.milSec = self.clockCounterVariable
self.seconds, self.milSec = divmod(self.milSec, 100)
self.minutes, self.seconds = divmod(self.seconds, 60)
self.hours, self.minutes = divmod(self.minutes, 60)
painter.begin(self)
painter.setPen(QPen(QColor(255, 128, 20), 1, Qt.PenStyle.SolidLine))
painter.drawText(QRect(35, 30, 400, 30), Qt.AlignmentFlag.AlignLeft, "{:02d} : {:02d} : {:02d} : {:02d}".format(self.hours, self.minutes, self.seconds, self.milSec))
if self.collided():
painter.setPen(QPen(QColor(255, 20, 20), 0, Qt.PenStyle.SolidLine))
painter.setBrush(QBrush(QColor(128, 20, 20), Qt.BrushStyle.SolidPattern))
else:
painter.setPen(QPen(QColor(20, 255, 20), 0, Qt.PenStyle.SolidLine))
painter.setBrush(QBrush(QColor(20, 128, 20), Qt.BrushStyle.SolidPattern))
painter.drawRect(self.rectangle)
painter.drawEllipse(self.circle)
painter.end()
return super().paintEvent(a0)
def refreshRectAndCircle(self) -> None:
rectangleWidth = randint(50, 500)
rectangleHeight = randint(50, 500)
self.rectangle = QRect(randint(0, self.windowWidth - rectangleWidth), randint(0, self.windowHeight - rectangleHeight - 80), rectangleWidth, rectangleHeight)
circleRadius = randint(50, 200)
self.circle = QRect(randint(0, self.windowWidth - circleRadius), randint(0, self.windowHeight - circleRadius - 80), circleRadius, circleRadius)
self.update()
def collided(self) -> bool:
# return True if collided and return False if not collided
circle = self.circle
rect = self.rectangle
if __name__ == "__main__":
App = QApplication(argv)
window = Window()
App.exec()
how should I detect whether there is a collision between the circle and the rectangle or not?
While you can achieve this with math functions, luckily Qt provides some useful functions that can make this much easier.
You can achieve this with three steps - or even just one (see the last section).
Check the center of the circle
If the center of the circle is within the boundaries of the rectangle, you can always assume that they collide. You're using a QRect, which is a rectangle that is always aligned to the axis, making things much easier.
Mathematically speaking you just need to ensure that the X of the center is between the smallest and biggest X of the left and right vertical lines of the rectangle, then the same for the Y.
Qt allows us to check if QRect.contains() the QRect.center() of the circle.
def collided(self) -> bool:
center = self.circle.center()
if self.rectangle.contains(center):
return True
Check the vertexes of the rectangle
If the length between the center of the circle and any of the vertexes of the rectangle is smaller than the radius, you can be sure that they are within the circle area.
Using the basic Pythagorean equation, you can know the hypotenuse created between the center and each of the vertexes of the rectangle, and if the hypotenuse is smaller than the radius, it means that they are within the circle.
With Qt we can use QLineF with the center and the vertexes (topLeft(), topRight(), bottomRight() and bottomLeft()), whenever any of the lengths is smaller than the radius, it means that the vertex is within the circle. Using QPolygonF we can easily iterate through all vertexes in a for loop.
# ...
center = QPointF(center)
radius = self.circle.width() / 2
corners = QPolygonF(QRectF(self.rectangle))[:4]
for corner in corners:
if QLineF(center, corner).length() < radius:
return True
Check the closest side of the rectangle
It is possible that the circle only collides with a side of the rectangle: the center of the circle is outside of the rectangle, and none of the vertexes are within the circle.
Consider this case:
In this situations, the collision always happens whenever the perpendicular line of the closest side of the rectangle is smaller than the radius:
Using math, we'll need to get the line perpendicular to the closest side, going toward the center of the circle, computing the angle between the side and the lines connecting the center with each vertex (shown in orange above), then with the help of some trigonometry, get the cathetus of one of the triangles (shown in red): if the length of that line is smaller than the radius, the shapes collide.
Luckily again, Qt can help us. We can get the two closest points using the lines created in the section "Check the vertexes of the rectangle" above, get the side of those points and compute a perpendicular angle that will be used to create a "diameter": starting from the center, we create two lines with opposite angles and the radius with the fromPolar(), then create the actual diameter with the external points of those lines. Finally, we check if that diameter intersects() with the side.
And this is the final function:
def collided(self) -> bool:
center = self.circle.center()
if self.rectangle.contains(center):
return True
# use floating point based coordinates
center = QPointF(center)
radius = self.circle.width() / 2
corners = QPolygonF(QRectF(self.rectangle))[:4]
lines = []
for corner in corners:
line = QLineF(center, corner)
if line.length() < radius:
return True
lines.append(line)
# sort lines by their lengths
lines.sort(key=lambda l: l.length())
# create the side of the closest points
segment = QLineF(lines[0].p2(), lines[1].p2())
# the perpendicular angle, intersecting with the center of the circle
perpAngle = (segment.angle() + 90) % 360
# the ends of the "diameter" per pendicular to the side
d1 = QLineF.fromPolar(radius, perpAngle).translated(center)
d2 = QLineF.fromPolar(radius, perpAngle + 180).translated(center)
# the actual diameter line
diameterLine = QLineF(d1.p2(), d2.p2())
# get the intersection type
intersection = diameterLine.intersects(segment, QPointF())
return intersection == QLineF.BoundedIntersection
Further considerations
when dealing with geometric shapes, you should consider using QPainterPath which actually makes the above extremely simpler:
def collided(self) -> bool:
circlePath = QPainterPath()
circlePath.addEllipse(QRectF(self.circle))
return circlePath.intersects(QRectF(self.rectangle))
Qt has a powerful (yet complex) Graphics View Framework that makes graphics and user interaction much more intuitive and effective; while the QPainter API is certainly easier for simpler cases, it may result in cumbersome (and difficult to debug) code as soon as your program requirements grow in complexity;
QMainWindow has its own, private and inaccessible layout manager, you cannot call setLayout() on it; use setCentralWidget() and set a layout to that widget eventually;
never use generic stylesheet properties for parent widgets (as you did for the main window) because it may result in awkward drawing of complex widgets like scroll areas; always use selector types for windows and containers instead;
unless you actually need to paint on the QMainWindow contents (which is a rare occurrence), you should always implement the paintEvent() on its central widget instead; otherwise, if you don't need QMainWindow features (menubar, statusbar, dock widgets and toolbars), just use a QWidget;
QTimer is not reliable for precise time measurement: if any function called while it's running requires more time than the timeout interval, the connected function will always be called afterwards; use QElapsedTimer instead;
in paintEvent() just use painter = QPainter(self), remove painter.begin(self) (it's implicit using the above) and painter.end() (unnecessary, since it's automatically destroyed when the function returns);
don't create unnecessary instance attributes (self.milSec, self.seconds, etc) that will be almost certainly overwritten sooner or later, and that you're not using elsewhere; the paint event must always return as soon as possible and must be always optimized as much as possible;
I'm coding some custom GUI objects for usage in pygame menus, while coding a scrollable box I hit an error.
This box works by moving a surface (which contains the components which are moved when scrolling) within a smaller surface which acts like a window to the confined surface. The surfaces mostly display correctly: the contents of the inner surface which are visible initially (the parts which fit within the window surface) display correctly, but when the inner surface is moved to reveal previously hidden components they are not displayed, the initial visible move correctly and are displayed when they return.
I think the issue is with the outer surface's clipping area thinking that only the already revealed components should be displayed and that the others are still hidden but I don't know.
The custom GUI components always have a Rect (returns the bounding rect for that component) and Draw (blits the component to the screen) functions.
Here is the code for the scroll area (and it's parent class):
class ScrollArea(BaseComponent):
"Implements a section of screen which is operable by scroll wheel"
def __init__(self,surface,rect,colour,components):
"""surface is what this is drawn on
rect is location + size
colour is colour of screen
components is iterable of components to scroll through (they need Draw and Rect functions), this changes the objects location and surface
"""
super().__init__(surface)
self.rect = pygame.Rect(rect)
self.colour = colour
self.components = components
self.Make()
def HandleEvent(self, event):
"Pass events to this; it enables the area to react to them"
if event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos) and self._scroll_rect.h > self.rect.h:
if event.button == 4: self.scroll_y = min(self.scroll_y + 15,self._scroll_y_min)
if event.button == 5: self.scroll_y = max(self.scroll_y - 15,self._scroll_y_max)
def Make(self):
"Updates the area, activates any changes made"
_pos = self.rect.topleft
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self.rect = pygame.Rect(_pos,self._sub_surface.get_rect().size)
self._sub_surface.unlock()#hopefully fixes issues
self._scroll_surf = pygame.Surface(self.rect.size)
self._scroll_rect = self._scroll_surf.get_rect()
scroll_height = 5
for component in self.components:
component.surface = self._scroll_surf
component.Rect().y = scroll_height
component.Rect().x = 5
component.Draw()
scroll_height += component.Rect().h + 5
self._scroll_rect.h = max(self.rect.h,scroll_height)
self.scroll_y = 0
self._scroll_y_min = 0
self._scroll_y_max = -(self._scroll_rect.h - self.rect.h)
def Draw(self):
"Draw the area and its inner components"
self._sub_surface.fill((255, 255, 255, 0))
self._sub_surface.blit(self._scroll_surf,(0,self.scroll_y))
pygame.draw.rect(self._sub_surface,self.colour,((0,0),self.rect.size),2)
self.surface.blit(self._sub_surface,self.rect.topleft)
def Rect(self):
"Return the rect of this component"
return self.rect
class BaseComponent:
def __init__(self,surface):
"surface is what this is drawn on"
self.surface = surface
def HandleEvent(self,event):
"Pass events into this for the component to react ot them"
raise NotImplementedError()
def Make(self):
"Redo calculations on how component looks"
raise NotImplementedError()
def Draw(self):
"Draw component"
raise NotImplementedError()
def ReDraw(self):
"Call Make then draw functions of component"
self.Make()
self.Draw()
def Rect(self):
"Return the rect of this component"
raise NotImplementedError()
To test this I used this code and a label component:
screen_width = 640
screen_height = 480
font_label = pygame.font.Font("freesansbold.ttf",22)
screen = pygame.display.set_mode((screen_width,screen_height))
grey = (125,125,125)
def LoadLoop():
#objects
scroll_components = []
for i in range(20):
scroll_components.append(Components.Label(screen,(0,0),str(i),font_label,grey))
scroll_area = Components.ScrollArea(screen,Components.CenterRect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
clock = pygame.time.Clock()
running = True
while running:
#events
for event in pygame.event.get():
scroll_area.HandleEvent(event)
if event.type == pygame.QUIT:
running = False
pygame.quit()
exit()
#graphics
screen.fill(black)
scroll_area.Draw(components)
#render
pygame.display.update()
clock.tick(60)
This is the label component's code (it basically just prints text to screen with the location given as it's center):
class Label(BaseComponent):
"Class which implements placing text on a screen"
def __init__(self,surface,center,text,font,text_colour):
"""surface is what this is drawn on
center is the coordinates of where the text is to be located
text is the text of the label
font is the font of the label
text_colour is the text's colour
"""
super().__init__(surface)
self.center = center
self.text = text
self.font = font
self.text_colour = text_colour
self.Make()
def HandleEvent(self,event):
"Labels have no events they react to,\nso this does nothing"
def Make(self):
"(Re)creates the label which is drawn,\nthis must be used if any changes to the label are to be carried out"
self._text_surf = self.font.render(self.text, True, self.text_colour)
self._text_rect = self._text_surf.get_rect()
self._text_rect.center = self.center
def Draw(self):
"Draw the label , will not react to any changes made to the label"
self.surface.blit(self._text_surf,self._text_rect)
def Rect(self):
"Return the rect of this component"
return self._text_rect
This is the window produced by this code:
Before scrolling
After scrolling
I also did it with a different size of ScrollArea, one of the Labels was positioned through the bottom and it was cut in half, when scrolled the cut remained.
Please help.
Sidenote on conventions
First, a sidenote on conventions: class names should start with an uppercase letter, function and method names should be all lowercase.
They are conventions, so you are free to not follow them, but following the conventions will make your code more readable to people used to them.
The quick fix
The error is in the ScrollArea.Make() method. Look carefully at these two lines:
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self._scroll_surf = pygame.Surface(self.rect.size)
self._sub_surface is the surface of the window of the scroll area. self._scroll_surf is the scrolling surface. The latter should be higher, but you set them to the same size (same width is fine, same height not).
Obviously when you loop over your component list to blit the Label, the ones which are outside self._sub_surface are also outside self._scroll_surf and hence are not blit at all. You should make self._scroll_surf higher. Try for example:
self._scroll_surf = pygame.Surface((self.rect.width, self.rect.height*10)
Better would be to estimate the proper height to contains all your labels, which should be scroll_height, but you calculate it later in the method, so you should figure how to do properly this part.
A general advice
In general, I think you have a design problem here:
for i in range(20):
scroll_components.append(Label(screen,(0,0),str(i),font_label,grey))
scroll_area = ScrollArea(screen, pygame.Rect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
When you create each label, you pass the screen as the reference surface where the Draw method blits.
But these labels should be blitted on the scroll_surf of your ScrollArea. But you cannot do it because you have not instantiated yet the ScrollArea, and you cannot instantiate before the scroll area because you require the Labels to be passed as an argument.
And in fact in the ScrollArea.Make() method you overwrite each label surface attribute with the _scroll_surf Surface.
I think would be better to pass to ScrollArea a list of strings, and let the ScrollArea.__init__() method to create the labels.
It will look less patched and more coherent.
I am using python cocos2D and am trying to display a map of many tiles. About 100x100 32px tiles. It works alright when I am zoomed in by as soon as I zoom out to view more it gets very choppy.
I also tried using my own sprites and that was even worse. I am not sure why it so bad considering I have seen pygame handle more (and far more complex) sprites than this no problem. Does the tile map version have something to do with the way that I scale?
EDIT: I managed to hugely increase performance using a batch (which I had no idea was a thing and took some looking around to find). That should probably be mentioned somewhere less obscure (unless I am just stupid and missed it). In any case I am still interested in feedback about how to improve this (i.e how many sprites per batch?) and how to improve tilemap?
class Tile(cocos.sprite.Sprite):
def __init__(self, image, position):
super().__init__(image, position=position)
class GridLayer(cocos.layer.ScrollableLayer):
def __init__(self):
super().__init__()
for x in range(50):
for y in range(50):
self.add(Tile('tile.png', (x*32, y*32)))
class Scroll(cocos.layer.ScrollingManager):
is_event_handler = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
win_size = Vector2(*cocos.director.director.get_window_size())
self.set_focus(win_size.x / 2, win_size.y / 2)
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
if buttons == 1:
a, b = self.fx - dx, self.fy - dy
self.force_focus(a, b)
def on_mouse_scroll(self, x, y, buttons, dir):
if self.scale < 1: dir *= self.scale
self.scale += dir * 0.1
def main():
cocos.director.director.init(1024, 600, resizable=True)
scroll = Scroll()
# This is the time map version:
grid = cocos.tiles.load('tmp.tmx')['Tile Layer 1']
scroll.add(grid)
# This is the custom sprite version:
# scroll.add(GridLayer())
main_scene = cocos.scene.Scene(scroll)
cocos.director.director.run(main_scene)
if __name__ == '__main__':
main()
Started playing with python's tkinter today and ran into some problems.
I created an animation that moves a ball around the screen, with a given speed. (and when it hits the screen, it goes back)
Why does my ball look bad? it's shape is not uniform? (its like blinking a lot)
Is there a better way to do it?
the code:
from tkinter import *
import time
WIDTH = 800
HEIGHT = 500
SIZE = 100
tk = Tk()
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, bg="grey")
canvas.pack()
color = 'black'
class Ball:
def __init__(self):
self.shape = canvas.create_oval(0, 0, SIZE, SIZE, fill=color)
self.speedx = 3
self.speedy = 3
def update(self):
canvas.move(self.shape, self.speedx, self.speedy)
pos = canvas.coords(self.shape)
if pos[2] >= WIDTH or pos[0] <= 0:
self.speedx *= -1
if pos[3] >= HEIGHT or pos[1] <= 0:
self.speedy *= -1
ball = Ball()
while True:
ball.update()
tk.update()
time.sleep(0.01)
errors after terminating the program:
Traceback (most recent call last):
File "C:/..py", line 29, in <module>
ball.update()
File "C:/Users/talsh/...py", line 20, in update
canvas.move(self.shape, self.speedx, self.speedy)
File "C:\Users\...\tkinter\__init__.py", line 2585, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".!canvas"
Is it normal? Am I doing anything wrong?
I would imaging the problem is coming from sleep(). The methods sleep() and wait() should not be used in tkinter as they will pause the entire application instead of just providing a timer.
Update:
Its also not a good idea to name a method the same name as a built in method.
you have self.update() and update() is already in the name space for canvas. Change self.update() to something else like: self.ball_update()
UPDATE:
It looks like tikinter refreshes at a 15ms rate and trying to fire an even faster than that might cause issues. The closest I was able to get to stopping the circle from distorting while moving at the same rate as your original code was to change the timer to 30ms and to change your speed variables to 9 from 3.
Always make sure you have mainloop() at the end of you tkinter app. mainloop() is required to make sure tkinter runs properly and without there may be bugs caused by it missing so at the end add tk.mainloop()
You should use after() instead. This should probably be done using a function/method as your timed loop. Something like this:
def move_active(self):
if self.active == True:
self.ball_update()
tk.after(30, self.move_active)
tk.update()
Replace your while loop with the above method and add the class attribute self.active = True to your __init__ section. Let me know if this clears up your stuttering:
from tkinter import *
import time
WIDTH = 800
HEIGHT = 500
SIZE = 100
tk = Tk()
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, bg="grey")
canvas.pack()
color = 'black'
class Ball:
def __init__(self):
self.shape = canvas.create_oval(0, 0, SIZE, SIZE, fill=color)
self.speedx = 9 # changed from 3 to 9
self.speedy = 9 # changed from 3 to 9
self.active = True
self.move_active()
def ball_update(self):
canvas.move(self.shape, self.speedx, self.speedy)
pos = canvas.coords(self.shape)
if pos[2] >= WIDTH or pos[0] <= 0:
self.speedx *= -1
if pos[3] >= HEIGHT or pos[1] <= 0:
self.speedy *= -1
def move_active(self):
if self.active == True:
self.ball_update()
tk.after(30, self.move_active) # changed from 10ms to 30ms
ball = Ball()
tk.mainloop() # there should always be a mainloop statement in tkinter apps.
Here are some links to Q/A's related to refresh timers.
Why are .NET timers limited to 15 ms resolution?
Why does this shape in Tkinter update slowly?
All that being said you may want to use an alternative that might be able to operate at a faster refreash rate like Pygame
UPDATE:
Here is an image of what is happening to the circle while its moving through the canvas. As you can see its getting potions of the circle visibly cut off. This appears to happen the faster the update is set. The slower the update( mostly above 15ms) seams to reduce this problem:
After suffering the same flattened leading edges of fast moving objects I am inclined to agree with #Fheuef's response, although I solved the problem in a different manner.
I was able to eliminate this effect by forcing a redraw of the entire canvas just by resetting the background on every update.
Try adding:
canvas.configure(bg="grey")
to your loop. Of course we compromise performance, but it's a simple change and it seems a reasonable trade off here.
Basically I've found that this has to do with the way Tkinter updates the canvas image : instead of redrawing the whole canvas everytime, it forms a box around things that have moved and it redraws that box. The thing is, it seems to use the ball's old position (before it moved) so if the ball moves too fast, its new position is out of the redraw box.
One simple way to solve this however is to create a larger invisible ball with outline='' around it, which will move to the ball's position on every update, so that the redraw box takes that ball into account and the smaller one stays inside of it. Hope that's clear enough...
I have written a turtle program in python, but there are two problems.
It goes way too slow for larger numbers, I was wonder how I can speed up turtle.
It freezes after it finishes and when clicked on, says 'not responding'
This is my code so far:
import turtle
#Takes user input to decide how many squares are needed
f=int(input("How many squares do you want?"))
c=int(input("What colour would you like? red = 1, blue = 2 and green =3"))
n=int(input("What background colour would you like? red = 1, blue = 2 and green =3"))
i=1
x=65
#Draws the desired number of squares.
while i < f:
i=i+1
x=x*1.05
print ("minimise this window ASAP")
if c==1:
turtle.pencolor("red")
elif c==2:
turtle.pencolor("blue")
elif c==3:
turtle.pencolor("green")
else:
turtle.pencolor("black")
if n==1:
turtle.fillcolor("red")
elif n==2:
turtle.fillcolor("blue")
elif n==3:
turtle.fillcolor("green")
else:
turtle.fillcolor("white")
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.up()
turtle.rt(9)
turtle.down()
By the way: I am on version 3.2!
Set turtle.speed("fastest").
Use the turtle.mainloop() functionality to do work without screen refreshes.
Disable screen refreshing with turtle.tracer(0, 0) then at the end do turtle.update()
Python turtle goes very slowly because screen refreshes are performed after every modification is made to a turtle.
You can disable screen refreshing until all the work is done, then paint the screen, it will eliminate the millisecond delays as the screen furiously tries to update the screen from every turtle change.
For example:
import turtle
import random
import time
screen = turtle.Screen()
turtlepower = []
turtle.tracer(0, 0)
for i in range(1000):
t = turtle.Turtle()
t.goto(random.random()*500, random.random()*1000)
turtlepower.append(t)
for i in range(1000):
turtle.stamp()
turtle.update()
time.sleep(3)
This code makes a thousand turtles at random locations, and displays the picture in about 200 milliseconds.
Had you not disabled screen refreshing with turtle.tracer(0, 0) command, it would have taken several minutes as it tries to refresh the screen 3000 times.
https://docs.python.org/2/library/turtle.html#turtle.delay
For reference, turtle being slow is an existing problem.
Even with speed set to max, turtle can take quite a long time on things like fractals.
Nick ODell reimplemented turtle for speed here: Hide Turtle Window?
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = x[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()