Making sprites bounce off of each other - python

To download the code, follow the link:
Background:
So I've been going through pygame tutorials since I'm new to it and I found Eli Bendersky's well-known tutorial. I was going through part one and was attempting to add my own flair to it by making it "Social Cats". The cats would wander around and if they touched each other then they would graze each other and go their separate ways. In other words the same thing Eli had but with collision detection and new sprites. I figure this would be good practice. I've spent the past few days researching collision detection and how different people do it, but I've yet to see one that would work with my scenario or something similar. I'm beginning to see how small of a community I'm in.
Objective:
Ultimately, I am trying to make it so when a cat runs into another one, the one that collided will go off in a random direction that equals 45 degrees less or more than its current direction.
Problem:
I'm importing vec2d and I have my Cat class and my main. I would like to do the collision detection in the main because later on I will create a GameManager class that watches over what's going on. According to OOP, the cats shouldn't know about each other anyways. I've been having trouble getting the collision detection to work. I've tried a couple of different ways. In both ways nothing happens when they touch each other. I'm getting the impression what I'm wanting to do is way more complex than how I'm perceiving it. How am I screwing this up? I feel as if I've wasted enough time on this one aspect. Of course, that's the learning process. Thoughts?
Way 1:
mainWindow.fill(_white_)
for cat in cats:
cat.update(timePassed)
cat.blitme()
catsCollided = pg.sprite.spritecollide(cat, catGroup, True)
print pg.sprite.spritecollide(cat, catGroup, False)
for cat in catsCollided:
cat.currentDirection.angle = randint(int(cat.currentDirection.angle - 45), int(cat.currentDirection.angle + 45))
Way 2:
mainWindow.fill(_white_)
for cat in cats:
cat.update(timePassed)
cat.blitme()
print pg.sprite.spritecollide(cat, catGroup, False)
tempCatList = list(cats)
for catA in cats:
tempCatList.remove(catA)
for catB in cats:
if catA.rect.colliderect(catB.rect):
cats[cats.index(catA)].currentDirection.angle = randint(int(cat.currentDirection.angle - 45), int(cat.currentDirection.angle + 45))

Your first way is correct, however there are just a few bugs. Sprite collide is the best way to do it. First of all, there are very few circumstances when you want the third argument in sprite collide to be true, and unless I completely misunderstand how your code is doing it, you do not want to use True. When you specify True, it will automatically delete both sprites upon collision. The other thing is that you want to make sure to filter out self collisions. Basically, if a sprite runs sprite collide on itself it registers a collision with itself. One final thing about your code is that although your randint chooser might work (you may want to test what it is returning though), random.choice() would be a better fit for what you are looking for. When these changes are implemented, it looks something like this:
mainWindow.fill(_white_)
for cat in cats:
cat.update(timePassed)
cat.blitme()
catsCollided = pg.sprite.spritecollide(cat, catGroup, False) #False makes it so colliding sprites are not deleted
print pg.sprite.spritecollide(cat, catGroup, False)
for cat in catsCollided: #by the way although this is perfectly fine code, the repetition of the cat variable could be confusing
if cat != self: #checks that this is not a self collision
cat.currentDirection.angle = random.choice([int(cat.currentDirection.angle - 45), int(cat.currentDirection.angle + 45])

Related

Python3, TkInter: Box fade-out with itemconfig evidently waits time between steps, but does not shift box's color as should

I'm a human trying to make an animated musical keyboard type thing so keystrokes play noises and flash pretty colors on screen that make ape brain happy. I don't want to bore you too much, that's all you really need to know to get what's going on. I think? Let me know.
Point is, I want to make the rectangle that flashes colors on keypress, fade back to gray on key release. I figured I'd do this with time, having a list of colors, iterate through them in steps back to gray setting the rectangle's fill to each with canvas.itemconfig with a like 0.01-second delay after each step. But the rectangle behaves as it did before I added the flair: key-press, cyan, key-release, gray. No animation. I tried lengthening the steps to 1 second (looong, but on purpose to see if I was just missing something; the same cyan shade stuck for 4 steps' worth of time, it should have iterated), and setting the rectangle color in the final step to an ugly magenta. The magenta stuck obviously because I never bothered changing it until another keypress cyanned it again. So why aren't the shades iterating. Also, feel free to call your band The Iterating Shades.
My code, slimmed down as much as possible:
# Blah blah blah, setup, but here's the key release handler (The part *I think* that's messing crap up).
def key_release(e):
if (e.char == 'q'):
for i in ["#0088dd", "#005e99", "#002841", "#2e2e2e"]:
canvas.itemconfig(rect, fill=i)
time.sleep(0.04)
canvas.itemconfig(kaq, fill="gray20")
elif (e.char == 'e'):
for i in ["#ff9500", "#9e5d00", "#523000", "#2e2e2e"]:
canvas.itemconfig(rect, fill=i)
time.sleep(0.04)
canvas.itemconfig(kae, fill="gray20")
time.sleep(0.04)
There. Short(?) code, and I didn't blab on too long. All's well that ends well!
Got any ideas? Fixes? Solutions? Pitchforks to throw? Let me know, with a healthy amount of thanks from me to you. I hope this glitch is just not me being silly! Have a great day! And let me know too if you need more code-- but some people are big on Minimum Reproducible Examples, so I kept it short here.
Edit. The answer below does not work. Sorry Jason, not your fault!!
My new code looks like this, but does the same thing. I had to declare a global variable called blue_step and have the method reference it because I was having trouble figuring out how to use .after() with arguments ... but that's beside the point. Anyway:
# ...
def cycle_blue():
global blue_step, rect
canvas.itemconfig(rect, fill=["#0088dd", "#005e99", "#002841", "#2e2e2e"][blue_step])
# ...
def key_release(e):
global blue_step
blue_step = 0
if (e.char == 'q'):
# for i in ["#0088dd", "#005e99", "#002841", "#2e2e2e"]:
# canvas.itemconfig(rect, fill=i)
# time.sleep(0.04)
for i in range(0, 3):
canvas.after(400, cycle_blue)
blue_step += 1
# ...
Any help?
Previous thoughts
According to #jasonharper:
The only thing that time.sleep() accomplishes in a Tkinter program is to
lock up the GUI completely. Delays and event-driven programming don't get
along very well - the proper approach is to use .after() to schedule the
next step of your animation a little bit into the future, and then return to
the mainloop.
I'll try this method and update in a few days. It may not work, but I'll mark this correct until then. Thank you, #jasonharper.
Edit: I cannot mark correct for two days.

How to spawn another sprite after one is "removed"

I am trying to make a game with similar concepts to the snake game for a project. I want to make it such that when the player "eats" the "food", another one appears randomly elsewhere.
I have tried several codes from here and other sources but I can't seem to get them to run. There will always be different errors.
This is the code that I am using for now. I got it from online and it worked for the person but it doesn't work for me. My guess is that list.remove might be the problem with this code but I don't know how to edit it:
food_collide = pygame.sprite.spritecollide(player,food_list,False)
for food in food_collide:
score += 1
food_list.remove(food)
all_sprite_list.remove(food)
food.update()
all_sprite_list.update()
Seems to me that you never create a new instance of Food() after you kill the previous one (when eated by the player). So obviously the game doesn't spawn a new food.
This quick fix of the for food in food_collide should works:
for food in food_collide:
score += 1
food_list.remove(food)
newfood = Food()
food_list.add(newfood)
all_sprite_list.add(newfood)
all_sprite_list.remove(food)
food_list.update()
all_sprite_list.update()

Colliding Rects

I have been working on this game, but I have a question when it comes to collision detection. Can I check for collision and have it return True:
example:
def collide(self, EnemyTank):
tank_collision = pygame.sprite.collide_rect(self.rect, EnemyTank.rect)
if tank_collision == True:
return True
And then make it perform an action like this:
if player.collide == True:
e_tank_x += 0
I am new to programming so please bear with me, I am trying as hard as I can and any comments or suggestions would also be very appreciated.
I'm not an expert of pygame, but it sound perfectly legitimate. I would just take away the middle man in your initial function:
def collide(self, EnemyTank):
return pygame.sprite.collide_rect(self.rect, EnemyTank.rect)
And you need to adjust the test you use, as you want to actually check the collision, while as you wrote it it just test if the function collide exist ;)
You can also use the implicit testing, removing some unnecessary character (best abith are best learned early)
if player.collide(EnemyTank):
do your action here
good luck with your game!

Efficient collision detection

I am using python and pyglet in a 2d game but I have come across a problem in collision detection. My code to check for collision looks like this:
def distance(self,target):
return math.sqrt((self.x-target.x)**2 + (self.y-target.y)**2)
def check_collision(self):
for i in list_of_mobs:
if self.distance(i) < (self.width/2 + i.width/2):
return True
It checks the distance from each sprite with "target" being another sprite. The thing I am unsure of is "should I be checking for collisions between all the sprites?" I have over 200 mobs (even more I hope in the finished product) and it becomes unplayable when the collisions are checked. Is there someway to only check sprites within a certain distance without losing speed?
EDIT:
I had a read around google and found out that a lot of the speed was spent doing the same collisions. i.e. sprite1 was checked with sprite2 and sprite2 was checked with sprite1. So I made a few changes to the check collision function and it runs quicker now but still less than 20 fps
def check_collision(self):
global mobs_to_collide
if mobs_to_collide == []:
mobs_to_collide = list_of_mobs[:]
if self in mobs_to_collide:
mobs_to_collide.remove(self)
for i in mobs_to_collide:
if self.distance(i) < (self.width/2 + i.width/2):
return True
(it may contain some inefficient code/useless stuff. I was playing around with it a bit)
EDIT2:
I decided to use rabbyt as the sprite library. The collisions are fast and easy. I replaced the code above ^^ with:
rabbyt.collisions.collide(mobs_to_collide)
This returns a list of lists (I'm not sure if that's the correct term) with the objects that collided. I'm still working on how to translate that into an "if collided:" type statement but I am making progress. If anyone is in a similar situation, I would reccomend using rabbyt.
A simple way to improve the speed on this, could be to remove the square root operation.
def distancesq(self,target):
return (self.x-target.x)**2 + (self.y-target.y)**2
def check_collision(self):
for i in list_of_mobs:
# Square this distance to compensate
if self.distancesq(i) < (self.width/2 + i.width/2)**2:
return True
Probably it is already too late, but I had exactly the same problem. I managed to solve it by calculating collisions only for visible objects, like that:
for object_ in objects:
if not object_.visible: # pyglet.sprite.Sprite() provides this flag
continue
# rest of your collision detection

top down friction in pymunk

Been struggling with this for a couple of days, hard to find code examples on the net.
I'm making a topdown game and having trouble getting the player to move on key press. At the moment i'm using add_force or add_impulse to move the player in a direction, but the player doesn't stop.
I've read about using surface friction between the space and the player to simulate friction and here is how it's done in the tank.c demo.
However I don't understand the API enough to port this code from chipmunk into pymunk.
cpConstraint *pivot = cpSpaceAddConstraint(space, cpPivotJointNew2(tankControlBody, tankBody, cpvzero, cpvzero));
So far, I have something that looks like this:
class Player(PhysicalObject):
BASE_SPEED = 5
VISIBLE_RANGE = 400
def __init__(self, image, position, world, movementType=None):
PhysicalObject.__init__(self, image, position, world)
self.mass = 100
self.CreateBody()
self.controlBody = pymunk.Body(pymunk.inf, pymunk.inf)
self.joint = pymunk.PivotJoint(self.body, self.controlBody, (0,0))
self.joint.max_force = 100
self.joint.bias_coef = 0
world.space.add(self.joint)
I don't know how to add the constraint of the space/player to the space.
(Need someone with 1500+ rep to create a pymunk tag for this question).
Joe crossposted the question to the Chipmunk/pymunk forum, and it got a couple of more answers there. http://www.slembcke.net/forums/viewtopic.php?f=1&t=1450&start=0&st=0&sk=t&sd=a
Ive pasted/edited in parts of my answer from the forum below:
#As pymunk is python and not C, the constructor to PivotJoint is defined as
def __init__(self, a, b, *args):
pass
#and the straight conversion from c to python is
pivot1 = PivotJoint(tankControlBody, tankBody, Vec2d.zero(), Vec2d.zero())
# but ofc it works equally well with 0 tuples instead of the zero() methods:
pivot2 = PivotJoint(tankControlBody, tankBody, (0,0), (0,0))
mySpace.add(pivot1, pivot2)
Depending on if you send in one or two arguments to args, it will either use the cpPivotJointNew or cpPivotJointNew2 method in the C interface to create the joint. The difference between these two methods is that cpPivotJointNew want one pivot point as argument, and the cpPivotJointNew2 want two anchor points. So, if you send in one Vec2d pymunk will use cpPivotJointNew, but if you send in two Vec2d it will use cpPivotJointNew2.
Full PivotJoint constructor documentation is here: PivotJoint constructor docs
I'm not familiar with either system you've mentioned, but some basic game ideas that may relate are:
If you add a force (or impulse) which affects movement, for the entity to stop, you must also take it away. In my games if I had a function AddImpulse()/AddForce() I would have a corresponding one such as Apply_Friction() which would reverse the effect by however much you want (based on terrain?) until movespeed is zero or less. I personally wouldn't bother with this method for movement unless needed for gameplay since it can add more computations that its worth each update.
There should be some way to track KeyPressed and/or KeyPosition and then using those x/y coordinates are incrememnted based on player speed. Without knowing what you've tried or how much the API does for you, it's hard to really say more.
Hope this helps. If this is stuff you already knew kindly ignore it.

Categories