Python: How do I detect visible shape detection in Tkinter - python

I am new to Python and I have a program that has 2 classes, one is essentially a rectangle and the other is essentially a circle. I am drawing them using the Canvas in Tkinter in the way below:
def draw(self):
self.canvas.delete("all")
self.rect.draw(self.canvas)
self.ball.draw(self.canvas)
The Ball class has its location variables and diameter variables and the Rect class has its location and dimension variables.
I am wondering how I detect the collision between these two "shapes". I know that one was is to treat the Ball as a square and do basic rectangle collision but I would like to know how to be precise.
I was also wondering if there was anything similar in Python to the way shape collision can be done in Java. In my Java game I use the following code to detect collision between any 2 Shapes:
public boolean collisionCheck(Shape a, Shape b) {
Area aA = new Area(a);
Area aB = new Area(b);
aA.intersect(aB);
return !aA.isEmpty();
}
Is there anything similar to this simple solution in Python?
And if not how would I go about circle-rectangle collision in Python?
Thank you for any help

I managed to figure this out using a method that Tkinter's Canvas object has. Every time something is drawn using Canvas it is given an ID. So as long as you record that ID somewhere you can use the find_overlapping method that the Canvas object has.
Say you have an object, in my case a custom Platform object, that keeps the ID stored in a variable. I did so like this:
def draw_platform(self, canvas): #Located in the Platform Class
self.ID = canvas.create_rectangle(self.x, self.y, self.x+self.w, self.y+self.h)
Now I can use this ID to determine if there are any object overlapping it.
def check_collision(self, plat, other_plat):
result = self.canvas.find_overlapping(plat.x, plat.y, plat.x + plat.w, plat.y + plat.h)
for i in result:
if i == other_plat.ID:
return True
return False
result returns a tuple of the ID's that are located within the rectangle bounds entered into find_overlapping. You can than loop through the tuple to see if any of the ID's match your other shape.
This works great for Rectangle-Rectangle collision and Circle-Rectangle collision.

Related

Control over which turtle renders on top of another

I'm currently trying to make 3d graphics using Turtle.
I have a list of objects that have inherited the Turtle class and each one has an attribute frontFactor - this value is the distance of the turtle to the camera.
How can I make the turtles closer to the camera render on top of the other turtles behind it?
My current code trying to do this:
finished = []
highestIndex = 0
print("FRAME")
for object in objects:
for i in range(len(object.particles)):
curHighest = 0
for j in range(len(object.particles)):
if object.particles[j].frontFactor > curHighest and (not (j in finished)):
curHighest = object.particles[j].frontFactor
highestIndex = j
object.particles[highestIndex].move(0,0,0)
print(object.particles[highestIndex].frontFactor)
finished.append(highestIndex)
where objects has an attribute of particles which is a list,
and every item in this list has a value frontFactor that determines the distance to the virtual camera.
This function goes through each particle on the screen (currently i only have one object), starting from the particles farthest away from the camera and ending at the particles nearest the camera.
This is what I want to happen,
but this is what it looks like instead. (darker particles are further away)
I've heard of a rule which states "last to move is on top" although I don't know how to implement that in this situation.

How to dynamically instantiate objects from a certain class [duplicate]

This question already has answers here:
How do I create variable variables?
(17 answers)
Closed 2 years ago.
I'm making a simple ball game using a 2D map. The amount of balls on the map is variable. When starting the game I load the map and look for balls (The balls are unique letters). I append these ball names to a list.
I also have a Ball class. __init__ contains the coordinates of the ball and the class contains a couple of methods.
Here comes the problem:
I want to create objects of each ball using the Ball class, with the object name corresponding to the letter belonging to the ball.
I tried this:
(The map is a txt file looking like this:)
0 0 0 0
0 0 X 0
0 A 0 B
Where the zero's represent empty tiles.
class Ball:
def __init__( self, x, y):
self.x = x
self.y = y
map_data = np.genfromtxt('map.txt', dtype=str)
ball_names = []
for row in variables.map_data:
for i in row:
if i.isalpha():
ball_names.append(i)
for ball in ball_names:
coordinates = np.where(map_data == ball)
ball = Ball(coordinates[0], coordinates[1])
But then I want the object to be named after the string contained by the variable ball, not 'ball'.
I know this can be done using exec(), but I've been told to avoid that function.
Generating variables dynamically with exec is a bad idea. As in fact variables will be totally hidden from developer. Supporting such code will be a nightmare and it's going to be a giant source of problems.
Instead, why don't you create a dict of balls?
my_balls = dict()
for ball in ball_names:
coordinates = np.where(map_data == ball)
my_balls[ball] = Ball(coordinates[0], coordinates[1])
But if you really want to create a variable, then:
exec(f'{ball} = Ball({coordinates[0]}, {coordinates[1]})')

Why doesn't this circle disappear upon collision?

These seem to be the relevant portions of a slightly larger program. These are essentially the collision detection functions. bubbles is the original list of circles. I want the circle collided with to disappear on impact. I'm fairly sure my problem lies down there in "if collide ==" conditional.
def getDistance(point1,point2):
a= point1.getX()
b= point2.getX()
c= point1.getY()
d= point2.getY()
distance= math.sqrt((b-a)**2 + ((d-c)**2))
return distance
def balloonBubbleCollide(balloon,bubble):
point1 = balloon.getCenter()
point2= bubble.getCenter()
distance= getDistance(point1, point2)
if distance <= 30:
return True
def check(balloon, bubbles, window):
for bubble in bubbles:
collide = balloonBubbleCollide(balloon, bubble)
if collide == True:
bubbles.remove(bubble)
Assume main is running them in proper order. Just don't want to bog the post down with code.
You should not modify a list using remove while iterating over it.
To filter out the colliding bubbles use instead something like:
def check(balloon, bubbles, window):
bubbles[:] = [bubble for bubble in bubbles
if not baloonBubbleCollide(balloon, bubble)]
the code shown will create a first new list of bubble objects to keep (using a list comprehension) and then replace the current content of the bubbles list with it at once.

How can I just draw a specific sprite in a Group with pygame draw()?

<Group(4 sprites)>
I have 4 sprites in a group they all ready to draw with the line,
stuff_to_draw.draw(screen)
They all currently draw at the same time.
Is it possible to do something like this
stuff_to_draw[0].draw(screen)
I've looked at documentation for pygame Groups, but can not find any helpful information about selecting a specific element in the group.
What am I trying to achieve?
The sprite group currently holds a answer sprite which consists of a location (x,y) and a image of the letter to blit.
Currently across all maps the answer is blitted instead of on the current question. So I would like to be able to say on question one stuff_to_draw[0] then question two stuff_to_draw[1].
First, add a self.type to your each of your sprites. Give it a value, such as self.type = 1. Then call this when you want to draw a certain sprite or group of sprites:
for sprite in group: #Replace group with your sprite group
if sprite.type == 1: #Or whatever type you want
sprite.draw(screen)
elif sprite.type == 2:
sprite.draw(screen)
#Etc...
Next, in your sprite class make sure you inherit from pygame.sprite.Sprite:
class my_sprite_class(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__()
#Etc...
#Add this draw function so we can draw individual sprites
def draw(self, screen):
screen.blit(self.image, self.rect)
I forgot that individual sprites don't have a draw function, just the groups. #pmoleri #jackdh my bad
Please read this post here to get an idea of what the class can do.
If you have any more questions just comment below.
You could blit any individual sprite, but I don't recommend it. This would be the way:
sprite = stuff_to_draw.sprites()[0]
screen.blit(sprite.image, sprite.rect)
My recommendation is to keep in the Sprites Group what you want to draw at a moment, and keep the rest in a separate list or group.

pygame: drawing text to Surface before drawing on screen

There are a lot of these question already, but I can't figure out what's wrong. compounding the problem the code is spread out over a class. I'm trying to create a scrolling panel. This is class slot which displays some phrase, and acts as a linked list to allow growth (and hopefully compact code)
code:
variables: the_game: instance of module pygame, font: instance of module pygame.font, phrase: string being pushed, self.bitmap: Surface object of slot to be drawn to screen, num: number indicating slot number, because they are supposed to be in sequence.
class slot(object):
def __init__(self,x,y,num,dimx,dimy,the_game,font):
...
self.wordnum = convertnum(num)+". "
self.phrase=""
self.bitmap = the_game.Surface((dimx,dimy))
self.updatesprite(the_game,font)
function updatesprite for clearing and re-blitting whatever phrase the slot is supposed to hold when changed:
def updatesprite(self,the_game,font):
self.bitmap.fill((240,240,240))
phrase = self.wordnum
if(self.has_phrase==True):
phrase+=self.phrase
text = font.render(phrase,1,(0,0,255))
the_game.Surface.blit(text,self.bitmap,[self.x+2, self.y+2])
...
for moving back a phrase or sentence onto the next slot when another one is "pushed" on a previous slot
def mypush(self,num,phrase,the_game,font):
if (num > self.num):
if (self.has_next==False):
self.addmore(num-self.num,the_game,font)
self.next.mypush(num,phrase,the_game,font)
self.updatesprite(the_game,font)
elif (num == self.num):
if (self.has_phrase==True):
self.mypush(self.num+1,self.phrase,the_game,font)
else:
self.has_phrase=True
self.phrase=phrase
self.updatesprite(the_game,font)
function for setting the phrase of a slot:
def setphrase(self,phrase,realx,realy,virty,realdim,mousex,mousey,the_game,font):
...
if (self.has_phrase==True):
self.mypush(self.num+1,self.phrase,the_game,font)
self.has_phrase=True
self.phrase=phrase
self.updatesprite(the_game,font)
and then the method for drawing to the screen:
def mydraw(self,the_game,scrn,realx,realy,virty,realdim):
...
holder = self.bitmap.subsurface(0,self.e_y-virty,self.dim_x,self.dim_y-(self.e_y-virty))
scrn.blit(holder,[self.x,0])
if(self.has_next==True):
self.next.mydraw(the_game,scrn,realx,realy,virty,realdim)
code that was abbreviated out is for determining if the user clicked on a slot, determining screen coordinates from scrolling coordinates, etc.
The reason for it being written like this is because: a) I expect there to be quite a lot of slots potentially for one panel. I figured drawing to a large bitmap and grabbing what I need wouldn't be smart, or scalable. b) individual slots may be deleted
I can make examples work, but not this work. I can see the actual bitmap: self.bitmap, being drawn to the screen but no string. IDK whats going on
updated updatesprite method:
def updatesprite(self,the_game,font):
self.bitmap.fill((240,240,240))
phrase = self.wordnum
if(self.has_phrase==True):
phrase+=self.phrase
text = font.render(phrase,1,(0,0,255))
**self.bitmap.blit(text,[1, 1])** #compare to original
heh

Categories

Resources