Making a sprite character jump naturally with pyglet and pymunk - python

This code displays the image assassin1.png on a black screen. This image has a pymunk body and shape associated with it. There is also an invisible static pymunk object called floor present beneath it. Gravity is induced on the image and it is resting on the invisible floor.
I would like to make my image jump naturally when I press the UP key. How can I implement this?
import pyglet
import pymunk
def assassin_space(space):
mass = 91
radius = 14
inertia = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, inertia)
body.position = 50, 80
shape = pymunk.Circle(body, radius)
space.add(body, shape)
return shape
def add_static_line(space):
body = pymunk.Body()
body.position = (0,0)
floor = pymunk.Segment(body, (0, 20), (300, 20), 0)
space.add_static(floor)
return floor
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img, space):
self.space = space
pyglet.sprite.Sprite.__init__(self, img, self.space.body.position.x, self.space.body.position.y)
def update(self):
self.x = self.space.body.position.x
self.y = self.space.body.position.y
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), space = assassin_space(space))
pyglet.clock.schedule(self.update)
add_static_line(space)
def on_draw(self):
self.clear()
self.batch_draw.draw()
self.player1.draw()
space.step(1/50.0)
def on_key_press(self, symbol, modifiers):
if symbol == pyglet.window.key.UP:
print "The 'UP' key was pressed"
def update(self, dt):
self.player1.update()
space.step(dt)
if __name__ == "__main__":
space = pymunk.Space() #
space.gravity = (0.0, -900.) #
window = Game()
pyglet.app.run()

You need to apply an impulse to the body. Impulse is a change in momentum, which is mass times velocity. I assume you want your asassin to jump straight up. If that is the case, you have to apply the impulse to the center of the body.
body.apply_impulse(pymunk.Vec2d(0, 60), (0, 0))

Writing a platformer using a physics library like PyMunk is really difficult. I would strongly advise against it, and instead to manage physics like jumping in your own code.
The problem is that realistic physics like pymunk's come with many side effects that you really don't want. For example, when your character is running sideways, they will experience a frictional drag against the floor, which cannot operate through their center of mass, so will tend to make them rotate or fall over. You may find ways to counteract this, but these will have other undesirable side-effects. For example, if you make your character a squat shape with a large flat bottom edge, then this will also affect collision detection. If you reduce their friction with the floor, this will mean they don't slow down over time. You may add still more refinements to correct for these things, but they will have yet more side-effects. The examples I give here are just the tip of the iceberg. There are many more complex effects that will cause big problems.
Instead, it is massively simpler to have a variable representing your character's velocity, and add that to their position every frame. Detect if they have hit anything like a platform, and if so, set their horizontal or vertical velocity such that they do not move into the platform. This also comes with some subtle problems, but they are generally much easier to fix than a physics simulation.

Related

Rotating the player about the center point in pygame

I am making a game where there are two players and they can shoot each other. Their movement will be defined by a rotation around a fixed point, the point will be(600, 300), which is the center of our screen. The player will keep rotating around the point as long as they are pressing a certain button(which is keep providing force to our player) else they will fall(due to gravity). I think it would help to think of it as a ball attached to a point using a string. The string is attached as long as a button is pressed and gets unattached as soon as the button is released and the ball flies off. Here is my player class
class Player:
def __init__(self):
self.pos = [500, 200]
self.width = 30
self.height = 30
self.player = pygame.image.load("player.png").convert_alpha()
self.player = pygame.transform.scale(self.player, (self.width, self.height))
self.rect = self.player.get_rect()
self.rotated_player = None
self.anguler_vel = 0
self.Fg = 0.05
self.Fp = 0
self.arm_length = 0
Fp is the force perpendicular to the force of gravityFg. Fg is the force which is pulling it down on our player. Fp is defined by math.sin(theta) * Fg. I am keeping track of Fp because i want the player to keep moving in the direction of rotation after its unattatched from the string. arm_length is the length of the string.
I have a Point class, which is the point about which our player will rotate. Here's the point class.
class Point:
def __init__(self,x, y):
self.pos = [x, y]
dx = self.pos[0] - player.pos[0]
dy = self.pos[1] - player.pos[1]
self.angle = math.atan2(dy, dx)
Now, i need help with the actual rotation itself. I am aware that adding a certain value to the angle every single frame would make it go around. But how would i make it go around a certain point that i specify and how would the arm length tie into this?. I find that it is really difficult to implement all of this because the y-axis is flipped and all the positional values have to be scaled down when using them in calculations because of the FPS rate. Any help on how this is done would be appreciated as well. Thanks
When you use pygame.transform.rotate the size of the new rotated image is increased compared to the size of the original image. You must make sure that the rotated image is placed so that its center remains in the center of the non-rotated image. To do this, get the rectangle of the original image and set the position. Get the rectangle of the rotated image and set the center position through the center of the original rectangle. e.g.:
def rotate_center(image, rect, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = rect.center)
return rotated_image, new_rect
screen.blit(*rotate_center(image, image_rect, angle))
Alos see How do I rotate an image around its center using PyGame? and How to rotate an image(player) to the mouse direction?

Python: Drawing a Diagonal Line in Animation

I am trying to make an animation but I am not sure how to draw a diagonal line and move it.
import pygame
import sys
WINDOW=pygame.display.set_mode((800,300))
RED=(255,0,0)
WHITE=(255,255,255)
CRIMSON=(220,20,60)
BURGUNDY=(102,0,0)
CERULEAN=(153,255,255)
PINK=(255,102,102)
FPS=100
fpsClock=pygame.time.Clock()
x=0
pygame.display.set_caption("Animation")
while True:
for event in pygame.event.get():
if event.type=="QUIT":
pygame.quit()
sys.exit()
#Animation
WINDOW.fill(CERULEAN)
x=x+1
pygame.draw.circle(WINDOW, CRIMSON, (x,100),20)
pygame.draw.circle(WINDOW, BURGUNDY, (x, 92),5)
pygame.draw.line(WINDOW, PINK, (x,30),(x,70),3)
pygame.display.update()
fpsClock.tick(FPS)
The drawing is supposed to be a fish with a triangle as its tail. I originally tried to use the polygon function but wasn't sure how to input the x and where to input the x so just decided to draw three lines for the triangle.
I just need help as to how and where I would input the x into the line or even polygon function. Like for the circle one would simply put it first but how would it be for a line and/or polygon function?
Use pygame.draw.aalines() instead.
Give it a list of points forming the triangle.
Your problem here is:
X is increasing indefinitely
It increases too fast.
If you want to make this nice, make a object called Fish, with a method move, which wil move the fish. This way you will have clean code and minimal confusion.
You must remove the old fish from the window, then draw the new one on the new position. That's why you should keep the fish in its own object.
Choose one point of the fish that will act as an universal position indicator. E.g. a nose or centre of the mass, or something. Then you just change that pos and your move method adjusts all other coordinates accordingly.
EDIT:
This is an example. I didn't try it, just put it together to show you how it is done.
I hope this will make it clearer. You see, I draw the fish once on itsown surface, then I move this surface around.
As you move the mouse, the fish will be moved.
It may be slow, and flickery, and stuff. This code has some problem as yours.
The new fish should be drawn every 4 pixels, not each one.
As fish is in an object you can have multiple fishes of different sizes.
Each fish keeps portion of a screen that replaces in an origsurf attribute.
Before it moves, it returns the screen in previous position.
So your fishes can overlap on the screen.
To make all work smooth, you will have to do some more work.
For example, there are no safeguards against going over display bounds.
As I said, it is an example on how it is done, not a full code.
class Fish:
def __init__ (self, pos=(0, 0), size=(60, 40), bgcolour=(255, 255, 255)):
self.pos = pos; self.size = size
# Use a little surface and draw your fish in it:
self.surf = surf = pygame.Surface(size)
surf.fill(bgcolour)
# Draw something:
pygame.draw.aalines(surf, (0, 0, 0), 1, [(0, 0), (0, size[1]), (size[0], size[1]/2)]) # Triangle
# Save how screen looks where fish will be:
self.origsurf = Surface(size)
sub = screen.subsurface((pos, size))
self.origsurf.blit(sub)
# Show the fish:
screen.blit(surf, pos)
pygame.display.flip()
def move (self, newpos):
# Remove the fish from old position
screen.blit(self.origsurf, self.pos)
# Save how screen looks where fish will be:
self.origsurf = Surface(self.size)
sub = screen.subsurface((newpos, self.size))
self.origsurf.blit(sub)
# Then show the fish on new location:
screen.blit(self.surf, newpos)
self.pos = newpos
pygame.display.flip()
screen = pygame.display.set_mode()
fish = Fish()
FPS=100
fpsClock=pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type=="QUIT":
pygame.quit()
sys.exit()
elif event.type==pygame.MOUSEMOTION:
fish.move(event.pos)
fpsClock.tick(FPS)

creating a transparent surface to draw pixels to in pygame

I've created a surface that I've used pixel array to put pixels on, but i want to make the surface transparent but leaving the pixels opaque, I've tried making the surface transparent then drawing the pixels tot he surface but that just makes the pixels also transparent, any help or something I've missed?
-Edit- Hopefully this'll help in some way, this is the class object that creates the surface that is the galaxy
Also I have stated what I've tried, there's not much more to tell
class Galaxy(object):
def __init__(self,posx=0,posy=0,radius=0,depth=0):
radius = int(radius)
self.size = [radius*2,radius*2,depth]
self.posx = posx
self.posy = posy
self.radius = radius
#create array for stars
self.starArray = []
#create surface for stars
self.surface = pygame.Surface([radius*2,radius*2])
self.starPixel = pygame.PixelArray(self.surface)
#populate
for x in range(radius*2):
for y in range(radius*2):
#generate stars
num1 = noise.snoise2(x+posx,y+posy,repeatx=radius*10,repeaty=radius*10)
distance = math.sqrt(math.pow((x-radius),2)+math.pow((y-radius),2))
if distance < 0:
distance = distance * -1
#print(x,y,"is",distance,"from",radius,radius)
val = 5
#glaxy density algorithm
num = (num1 / ( ((distance+0.0001)/radius)*(val*10) )) * 10
#density
if num > (1/val):
#create star
self.starArray.append(Stars(x,y,seed=num1*100000,distance=distance))
#print(num*1000)
self.addPixels()
#adds all star pixels to pixel array on surface
def addPixels(self):
for i in self.starArray:
self.starPixel[i.x,i.y] = i.colour
del self.starPixel
#sends to screen to await rendering
def display(self):
screen.displaySurface(self.surface,[self.posx+camPosX,self.posy+camPosY])
Use MyGalaxy.set_colorkey(SomeUnusedRGB) to define a zero-alpha (invisible) background colour, fill MyGalaxy with that colour, then draw the pixels on top of that. You can use pixelArray functions to draw to that surface, but you're probably better to use MyGalaxy.set_at(pixelLocationXY, pixelColourRGB) instead, for reasons of managability and performance.
Make sure that SomeUnusedRGB is never the same as any pixelColourRGB, or those pixels won't appear (since pygame will interpret them as invisible). When you blit MyGalaxy to wherever you want it, it ought to only blit the non-SomeUnusedRGB-coloured pixels, leaving the rest unaltered.
(This is the best I can offer you without knowing more about your code; revise the question to include what you're already trying, and I'll update this answer.)

How can I make my circles fly off the screen in pygame?

I am a begginner at python and I'm trying to make a circle game. So far it draws a circle at your mouse with a random color and radius when you click.
Next, I would like the circle to fly off the screen in a random direction. How would I go about doing this? This is the main chunk of my code so far:
check1 = None
check2 = None
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit
if event.type == MOUSEBUTTONDOWN:
last_mouse_pos = pygame.mouse.get_pos()
if last_mouse_pos:
global check
color1 = random.randint(0,255)
color2 = random.randint(0,255)
color3 = random.randint(0,255)
color = (color1,color2,color3)
radius = random.randint (5,40)
posx,posy = last_mouse_pos
if posx != check1 and posy != check2:
global check1, check2
screen.lock()
pygame.draw.circle(screen, color, (posx,posy), radius)
screen.unlock()
check1,check2 = posx,posy
pygame.display.update()
Again, I want the circle to fly off the screen in a random direction.
I have made a few attempts but no successes yet.
Also, thanks to jdi who helped me s
When you create the circle (on click), generate 2 random numbers. These will be your (x,y) components for a two dimensional Euclidean velocity vector:
# interval -1.0 to 1.0, adjust as necessary
vx, vy = ( (random.random()*2) -1, (random.random()*2) - 1 )
Then after the ball has been created, on each iteration of the game loop, increment your ball's position by the velocity vector:
posx, posy = posx + vx, posy + vy
Note that the overall speed might be variable. If you want to always have a speed of 1.0 per seconds, normalize the vector:
To normalize the vector, you divide it by its magnitude:
So in your case:
And hence:
So in Python, after importing math with import math:
mag = math.sqrt(vx*vx + vy*vy)
v_norm = vx/mag, vy/mag
# use v_norm instead of your (vx, vy) tuple
Then you can multiply this with some speed variable, to get reliable velocity.
Once you progress to having multiple objects moving around and potentially off screen, it is useful to remove the offscreen objects which have no chance of coming back, and have nothing to do with your program anymore. Otherwise, if you keep tracking all those offscreen objects while creating more, you get essentially a memory leak, and will run out of memory given enough time/actions.
While what you are doing right now is quite simple, assuming you haven't already, learning some basic vector math will pay itself off very soon. Eventually you may need to foray into some matrix math to do certain transformations. If you are new to it, its not as hard as it first looks. You can probably get away with not studying it, but you will save yourself effort later if you start attempting to do more ambitious things.
Right now, you are doing the following (drastically simplifying your code)...
while True:
if the mouse was clicked:
draw a circle on the screen where the mouse was clicked
Let's make things a little easier, and build up gradually.
Start with the circle without the user clicking
To keep things simple, let's make the circle near the top left of the screen, that way we can always assume there will be a circle (making some of the logic easier)
circle_x, circle_y = 10,10
while True:
draw the circle at circle_x, circle_y
pygame.display.update()
Animate the circle
Before going into too much about "random directions", let's just make it easy and go in one direction (let's say, always down and to the right).
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
draw the circle at circle_x, circle_y
update the display
Now, every time through the loop, the center of the circle moves a bit, and then you draw it in its new position. Note that you might need to reduce the values that you add to circle_x and y (in my code, 0.1) in case the circle moves too fast.
However, you'll notice that your screen is now filling up with circles! Rather than one circle that is "moving", you're just drawing the circle many times! To fix this, we're going to "clear" the screen before each draw...
screen = ....
BLACK = (0,0,0) # Defines the "black" color
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
screen.fill(BLACK)
draw the circle at circle_x, circle_y
update the display
Notice that we are "clearing" the screen by painting the entire thing black right before we draw our circle.
Now, you can start work the rest of what you want back into your code.
Keep track of multiple circles
You can do this by using a list of circles, rather than two circle variables
circles = [...list of circle positions...]
while True:
# Update
for circle in circles:
... Update the circle position...
# Draw
screen.fill(BLACK)
for circle in circles:
draw the circle at circle position # This will occur once for each circle
update the display
One thing to note is that if you keep track of the circle positions in a tuple, you won't be able to change their values. If you're familiar with Object Oriented Programming, you could create a Circle class, and use that to keep track of the data relating to your circles. Otherwise, you can every loop create a list of updated coordinates for each circle.
Add circle when the user clicks
circles = []
while True:
# event handling
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
circles.append( pos ) # Add a new circle to the list
# Update all the circles
# ....
# Draw
clear the screen
for circle_position in circles:
draw the circle at circle_position # This will occur once for each circle
update the display
Have the circle move in a random direction
This is where a good helping of math comes into play. Basically, you'll need a way to determine what to update the x and y coordinate of the circle by each loop. Keep in mind it's completely possible to just say that you want it to move somewhere between -1 and 1 for each axis (X, y), but that isn't necessarily right. It's possible that you get both X and Y to be zero, in which case the circle won't move at all! The next Circle could be 1 and 1, which will go faster than the other circles.
I'm not sure what your math background is, so you might have a bit of learning to do in order to understand some math behind how to store a "direction" (sometimes referred to as a "vector") in a program. You can try Preet's answer to see if that helps. True understanding is easier with a background in geometry and trigonometry (although you might be able to get by without it if you find a good resource).
Some other thoughts
Some other things you'll want to keep in mind:
Right now, the code that we're playing with "frame rate dependent". That is, the speed in which the circles move across the screen is entirely dependent on how fast the computer can run; a slower computer will see the circles move like snails, while a faster computer will barely see the circles before they fly off the screen! There are ways of fixing this, which you can look up on your own (do a search for "frame rate dependence" or other terms in your favorite search engine).
Right now, you have screen.lock() and screen.unlock(). You don't need these. You only need to lock/unlock the screen's surface if the surface requires it (some surfaces do not) and if you are going to manually access the pixel data. Doing things like drawing circles to the screen, pygame in lock/unlock the surfaces for you automatically. In short, you don't need to deal with lock/unlock right now.

PyGame Circular Collision Issues - Circle Interior

I'm making a puzzle game that requires the user to 'draw' circles onto a background to get a ball to the exit. They create circles by holding their mouse button, the circle grows; when it is big enough, they let go and it is 'punched' into the physical space and balls then react to it.
I have a problem, however, that when two circles are intersecting (so a ball should pass through), if the intersection is not larger than the diameter of the ball the ball collides with the interior of the circle as usual.
This may be a little hard to comprehend, so here's a link to the screencast showing the problem (You can't embed videos on Stack Overflow): http://www.youtube.com/watch?v=3dKyPzqTDhs
Hopefully that made my problem clear. Here is the Python / PyGame code for the Ball and Circle classes:
class Ball():
def __init__(self, (x,y), size, colourID):
"""Setting up the new instance"""
self.x = x
self.y = y
self.size = size
self.exited = False
self.colour = setColour(colourID)
self.thickness = 0
self.speed = 0.01
self.angle = math.pi/2
def display(self, surface):
"""Draw the ball"""
# pygame.gfxdraw.aacircle(screen,cx,cy,new_dist,settings['MINIMAP_RINGS'])
if self.exited != True:
pygame.draw.circle(surface, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
"""Move the ball according to angle and speed"""
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
(self.angle, self.speed) = module_physicsEngine.addVectors((self.angle, self.speed), gravity)
self.speed *= drag
And the Circle class:
class Circle():
def __init__(self, (x,y), size, colourID):
"""Set up the new instance of the Circle class"""
self.x = x
self.y = y
self.size = size
self.colour = setColour(colourID)
self.thickness = 2
self.angle = 0 # Needed for collision...
self.speed = 0 # detection against balls
def display(self, surface):
"""Draw the circle"""
pygame.draw.circle(surface, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Within the main loop of the game (while running == True: etc.), this code is used to perform actions on each ball:
for b in balls:
b.move()
for i, ball in enumerate(balls):
for ball2 in balls[i+1:]:
collideBalls(ball, ball2)
collideCircle(b) # <---------------- This is the important line
collideExit(b)
b.display(screen)
And finally, the collideCircle(b) function, which is called once per ball to check for collisions with the interior of a circle, and also to check if the circles are intersecting.
def collideCircle(ball):
"""Check for collision between a ball and a circle"""
hit = False
closestDist = 0
for c in circles:
# Code cannot be replaced with physicsEngine.collideTest because it
# is slightly differnt, testing if ball [ball] inside a circle [c]
dx = c.x - ball.x
dy = c.y - ball.y
distance = math.hypot(dx, dy)
if distance <= c.size - ball.size:
# If BALL inside any CIRCLE
hit = False
break
else:
# If we're outside of a circle.
if closestDist < c.size - (distance - ball.size):
hit = c
closestDist = (c.size - (distance - ball.size))
if hit:
module_physicsEngine.circleBounce(hit, ball)
Ok, so I know that this has been a bit of a long and talky question, but I think you have all the information needed. Is the solution to make the balls interact correctly something to do with the line if distance <= c.size - ball.size:?
Anyway, thanks in advance!
Nathan out.
TL;DR - Watch the youtube video, and let me know why it's not working.
The problem is with unintended hits rather than missed ones. What you really want to check is if all parts of the ball are covered by some circle, while the check you're doing is if any circle only partially overlaps - but an override if any circle fully covers the ball.
I figure for any potential hit point, i.e. closest inner wall of a circle, let that point "walk" along the wall by checking its distance from all other circles. Should it then leave the ball, it was a false hit.
First you find the list of circles that touch the ball at all. As before, if any of them cover it, you can skip the rest of the checks. Also find the closest wall point to the ball for the circles. For each of those closest wall points, if it overlaps another circle, move it to the intersection point which is closest to the ball but further away than the current point. Discard it if it's outside the ball. Repeat the procedure for all circles, since more than two may overlap. Also note that the moving of the point may cause it to enter new circles.
You could precompute the intersection points and discard any that are a ball radius inside of any other circle.
This can surely be improved on, but it's a start, I think. I suspect a bug involving the case when both intersection points of a pair of circles overlap the ball, but a walk chain leads one of them outside the ball. Perhaps the initial collision points should be replaced only by both intersection points, not the closest.
I watched the video and I like the game principle. :)
Maybe the problem is that you break out of the loop as soon as you encounter a circle that encloses the ball. I'm referring to the snippet
if distance <= c.size - ball.size:
# If BALL inside any CIRCLE
hit = False
break
Why would you not check all of the other circles, in that case? There might be another circle yet unchecked that causes a hit.
Btw, I wouldn't say if condition == True:, that's unpythonic. Just say if condition:.

Categories

Resources