Collision detection between circle and rectangle - python

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;

Related

How to check for collision between two boxes in graphics.py?

How to check for collision between two boxes using John Zelle's graphics.py module?
EDIT:
I have found a way to find collisions between any two classes that have x,y,width,height. Its a bit messy but it does the job:
def collided(self, collider):
if self.x < collider.x + collider.width and\
self.x + self.width > collider.x and\
self.y < collider.y + collider.height and\
self.y + self.height > collider.y:
return True
return False
If anyone has a better way of doing this, it would be greatly appreciated!
Here's a little demo program that illustrates a fairly succinct way of doing this — it contains a function named intersect() which checks whether two instances of the graphics module's Rectangle class intersect.
A slightly tricky aspect of doing that is because the two Points used to define Rectangle aren't required to be its lower-right and upper-left corners, which the logic in intersect() function requires. To handle that, a helper function named canonical_rect() is used to make that's the case for each of arguments it's passed.
from graphics import *
from random import randint
WIDTH, HEIGHT = 640, 480
def rand_point():
""" Create random Point within window's limits. """
return Point(randint(0, WIDTH), randint(0, HEIGHT))
def rand_rect():
""" Create random Rectangle within window's limits. """
p1, p2 = rand_point(), rand_point()
while p1 == p2: # Make sure points are different.
p2 = rand_point()
return Rectangle(p1, p2)
def canonical_rect(rect):
""" Return new Rectangle whose points are its lower-left and upper-right
extrema - something Zelle graphics doesn't require.
"""
p1, p2 = rect.p1, rect.p2
minx = min(p1.x, p2.x)
miny = min(p1.y, p2.y)
maxx = max(p1.x, p2.x)
maxy = max(p1.y, p2.y)
return Rectangle(Point(minx, miny), Point(maxx, maxy))
def intersect(rect1, rect2):
""" Determine whether the two arbitrary Rectangles intersect. """
# Ensure pt 1 is lower-left and pt 2 is upper-right of each rect.
r1, r2 = canonical_rect(rect1), canonical_rect(rect2)
return (r1.p1.x <= r2.p2.x and r1.p2.x >= r2.p1.x and
r1.p1.y <= r2.p2.y and r1.p2.y >= r2.p1.y)
def main():
# Initialize.
win = GraphWin('Box Collision', WIDTH, HEIGHT, autoflush=False)
center_pt = Point(WIDTH // 2, HEIGHT // 2)
box1 = Rectangle(Point(0, 0), Point(0, 0))
box2 = Rectangle(Point(0, 0), Point(0, 0))
msg = Text(center_pt, "")
# Repeat until user closes window.
while True:
box1.undraw()
box1 = rand_rect()
box1.draw(win)
box2.undraw()
box2 = rand_rect()
box2.draw(win)
if intersect(box1, box2):
text, color = "Collided", "red"
else:
text, color = "Missed", "green"
msg.undraw()
msg.setText(text)
msg.setTextColor(color)
msg.draw(win)
win.update()
try:
win.getMouse() # Pause to view result.
except GraphicsError:
break # User closed window.
if __name__ == '__main__':
main()

PyQt5: All Items of GraphicsScene have coordinates 0.0

I used the following source and modified it a bit, to get the following mini example:
import sys
from PyQt5 import QtCore, QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self):
super(GraphicsScene, self).__init__()
self.setSceneRect(0, 0, 600, 400)
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
x = event.scenePos().x()
y = event.scenePos().y()
self.addRect(x, y, 100, 100)
elif event.buttons() == QtCore.Qt.RightButton:
for elem in self.items():
print(elem.x())
super(GraphicsScene, self).mousePressEvent(event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
scene = GraphicsScene()
w = QtWidgets.QGraphicsView(scene)
w.resize(610, 410)
w.show()
sys.exit(app.exec_())
The idea is, to create new rectangles by making left mouse clicks (this works already) and delete the nearest rectangle by making a right mouse click. I know, how I can find the nearest rectangle, but for this I need the coordinates of the existing rectangles. If we add a new rectangle to the scene, we do the following:
self.addRect(x, y, 100, 100)
But if I iterate over all elements in the scene, and try to get the x-coordinate of the elements using this:
for elem in self.items():
print(elem.x())
print(elem.pos().x())
print(elem.scenePos().x())
then all the print-outputs are zero. I had already a look at the docu, but as I understand it I am doing exactly what the docu recommends. Do you know what I am doing wrong?
EDIT:
Of course, I could save all the coordinates in an additional list, compute the nearest rectangle with the values in that list, delete each rectangle by using:
for elem in self.items():
self.removeItem(elem)
and plot the remaining rectangles. However, I hope there is a cleaner version for this. :)
As the documentation explains:
Note that the item's geometry is provided in item coordinates, and its position is initialized to (0, 0). For example, if a QRect(50, 50, 100, 100) is added, its top-left corner will be at (50, 50) relative to the origin in the item's coordinate system.
So there are two choices:
add a rectangle with the specified size but at position (0, 0), then move it at the required position:
rectItem = self.addRect(0, 0, 100, 100)
rectItem.setPos(x, y)
use the coordinates in addRect and get the actual position based on the top left corner of the rectangle:
for elem in self.items():
pos = elem.pos()
if isinstance(elem, QtWidgets.QGraphicsRectItem):
pos += elem.rect().topLeft()

Moving surface within smaller surface doesn't show previously hidden components

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.

Pymunk: prevent dynamic objects to move into static objects

I have a dynamic object to which I set different velocity values. However when this dynamic body hits a static body, it overlaps partly with the static shape until this collision is resolved and it moves back.
Is there a way in pymunk to make the dynamic body stop exactly at the borders of the static body, even when velocity is applied in this direction? If there are collision conflicts I would rather have them solved in another way than to make the two shapes overlap.
Applying forces and impulses are not really an option since I want to have a constant velocity.
(The below code needs to be executed twice to work.)
import pymunk
import pyglet
from PIL import Image
from PIL import ImageDraw
# setup of pyglet
window = pyglet.window.Window()
main_batch = pyglet.graphics.Batch()
keys = pyglet.window.key.KeyStateHandler()
window.push_handlers(keys)
# setup of pymunk
space = pymunk.Space()
"""MOVABLE CIRCLE"""
# creating pyglet sprite
circle_img = Image.new('RGBA', (50,50))
draw = ImageDraw.Draw(circle_img)
draw.ellipse((1, 1, 50-1, 50-1), fill=(255,0,0))
circle_img.save('circle.png')
pyglet_circle_img = pyglet.resource.image('circle.png')
pyglet_circle_img.anchor_x = pyglet_circle_img.width/2
pyglet_circle_img.anchor_y = pyglet_circle_img.height/2
circle_sprite = pyglet.sprite.Sprite(pyglet_circle_img, window.width/2, window.height/2, batch=main_batch)
# creating pymunk body and shape
mass = 2
radius = 25
moment = pymunk.moment_for_circle(mass, 0, radius)
circle_body = pymunk.Body(mass, moment)
circle_body.position = circle_sprite.position
circle_shape = pymunk.Circle(circle_body, 25)
circle_shape.elasticity = 0.0
space.add(circle_body, circle_shape)
"""STATIC SQUARE"""
# creating pyglet sprite
square_img = Image.new('RGBA', (70,70))
draw = ImageDraw.Draw(square_img)
draw.rectangle([(0, 0), (70-1, 70-1)], fill=(0,255,0))
square_img.save('square.png')
pyglet_square_img = pyglet.resource.image('square.png')
pyglet_square_img.anchor_x = pyglet_square_img.width/2
pyglet_square_img.anchor_y = pyglet_square_img.height/2
square_sprite = pyglet.sprite.Sprite(pyglet_square_img, 3*window.width/4, window.height/2, batch=main_batch)
# creating pymunk body and shape
square_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
square_body.position = square_sprite.position
square_shape = pymunk.Poly(square_body, [(-35,-35),(-35,35),(35,35),(35,-35)])
square_shape.elasticity = 0.0
space.add(square_body, square_shape)
def update(dt):
space.step(dt)
circle_sprite.position = circle_body.position
print(circle_body.position)
key_pressed = False
if keys[pyglet.window.key.LEFT]:
circle_body.velocity = (-100,0)
key_pressed = True
elif keys[pyglet.window.key.RIGHT]:
circle_body.velocity = (100, 0)
key_pressed = True
if keys[pyglet.window.key.UP]:
circle_body.velocity = (0, 100)
key_pressed = True
elif keys[pyglet.window.key.DOWN]:
circle_body.velocity = (0, -100)
key_pressed = True
if not key_pressed:
circle_body.velocity = (0,0)
#window.event
def on_draw():
window.clear()
main_batch.draw()
pyglet.clock.schedule_interval(update, 1/60.)
pyglet.app.run()
In general the answer it that you should expect strange effects when you set the position or velocity manually of a body, since that it tricking the physics engine (just like you would get strange effects in real life if
something teleported around)
Its also the case that a small overlap is to be expected when objects collide, that is how the collision is solved. There are two properties on the space that you can use to control this a bit, collision_slop and collision_bias.
However, you can experiment with some manual fixes which might help. One way would be to move the objects away once a collision happen. You can do this with a collision callback.
Here is a quick example you can put just before the update function in your example to prevent the overlap:
circle_shape.collision_type = 1
h = space.add_wildcard_collision_handler(circle_shape.collision_type)
def f(arbiter, space, data):
ps = arbiter.contact_point_set
arbiter.shapes[0].body.position += ps.normal * ps.points[0].distance
h.post_solve = f
(in your real code you need to add some fail safes and also account for more than one point in case you have more complex shapes)

Connecting edges of picture in python

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.

Categories

Resources