In my game I am creating a turret (a type of machine gun you use on the ground). The problem is that I am using a joystick to move the character, when the joystick is downwards the y speed is positive (so it can move downwards) the opposite is for if you move upwards. Then if checks your current angle and sees which direction you are pointing in if one of the if statements happen then it will allow you to move. What the main issue is that when I move my joystick upwards the gun points dowanrds.
I have already tried making a variable that stores the direction but that lead to the same problem so I discarded that idea and went back to the one I had before. There is also a turret stand where the turret is drawn onto
joystick = pygame.joystick.Joystick(0)
joystick.init()
turret_stand_pic = pygame.image.load("C:/knuckles_pictures/turret_stand.png").convert_alpha()
class Turret_stand():
def __init__(self, x, y, picture, picture_tag):
self.xpos = x
self.ypos = y
self.picture = picture
super().__init__()
self.picture = pygame.transform.scale(self.picture, (200, 200))
self.rect = self.picture.get_rect()
self.rect.x = self.xpos
self.rect.y = self.ypos
self.tag = picture_tag
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
turret_gun_pic = pygame.image.load("C:/knuckles_pictures/turret_gun.png").convert_alpha()
class Turret_gun():
def __init__(self, x, y, picture, picture_tag):
self.xpos = x
self.ypos = y
self.picture = picture
super().__init__()
self.picture = pygame.transform.scale(self.picture, (200, 80))
self.rect = self.picture.get_rect()
self.rect.x = self.xpos
self.rect.y = self.ypos
self.tag = picture_tag
self.previous_angle = 0
self.angle = -90
self.speed_x = 0
self.speed_y = 0
self.facing = "left"
def rotate(self, angle):
if angle != self.angle:
self.picture = pygame.transform.rotate(self.picture, angle)
"""self.angle += angle
self.previous_angle = self.angle"""
def draw(self):
if self.angle == 0:
screen.blit(self.picture, (self.xpos+70, self.ypos-70))
elif self.angle == -90:
screen.blit(self.picture, (self.xpos, self.ypos))
turret = Turret_gun(500, 370, turret_gun_pic, "turret")
turret_stand = Turret_stand(500, 400, turret_stand_pic, "turret stand")
while True:
[...]
if joystick:
move = 3
axis_x_two, axis_y_two = (joystick.get_axis(3), joystick.get_axis(2))
if abs(axis_x_two) > 0.1:
turret.speed_x = move * axis_x_two
turret.speed_y = move * axis_y_two
turret.speed_y = round(int(turret.speed_y))
turret.speed_x = round(int(turret.speed_x))
if turret.angle == -90 and turret.speed_y == -3 and turret.speed_x <= 1 and turret.speed_x >= -1:
turret.rotate(90)
if turret.angle == 0 and turret.speed_x == -3 and turret.speed_y <= 1 and turret.speed_y >= -1:
turret.rotate(-90)
turret.update()
turret.draw()
The actual results are that when you push the joystick upwards the machine gun points downwards. Heres what I mean:
[![enter image description here][1]][1]
In this case the turret ends up pointing downards:
[1]: https://i.stack.imgur.com/Aheix.jpg
[![enter image description here][2]][2]
What I should expect is that when I move the joystick upwards the turret points upwards.
[2]: https://i.stack.imgur.com/jFyg5.jpg
Sometimes the gun does not show when the joystick is pointing right:
Basically, you need to reverse the direction that you rotate the image. So, to flip it, rotate it an additional 180 degrees.
Change the line in your rotate method to
self.picture = pygame.transform.rotate(self.picture, angle+180)
I don't know your exact setup, but this might work only for the up/down case. The gun might still point right if you put the stick left. If this happens, change it to
self.picture = pygame.transform.rotate(self.picture, 180-angle)
This doesn't just flip it, the direction of rotation is reversed. Increasing the angle actually decreases the rotation.
Again, that might not work. It could point right if you move the stick up. If so, try changing the 180 to another number, like 90 or 270. This offsets the rotation by 90 degrees in one direction or the other.
self.picture = pygame.transform.rotate(self.picture, 90-angle)
or
self.picture = pygame.transform.rotate(self.picture, 270-angle)
Related
This question already has answers here:
Problems with moving an enemy towards a character in pygame
(1 answer)
Pygame doesn't let me use float for rect.move, but I need it
(2 answers)
Closed 8 months ago.
So I have a functioning shooting mechanic in python pygame for a top down shooter, where I am using the mouse position to aim the bullets by working out the angles, however when I do this, the bullets are shooting slightly off where the mouse position is. for instance: the mouse would be where the red arrow is drawn and the bullets will be shooting by a small amount in the wrong direction
Any help would be appreciated
code below:
main.py:
#-------------Imports-------------#
import pygame,sys
#import globals
from background import*
from player import*
#-------------Constants-------------#
WIDTH,HEIGHT = 500,500
WINDOW = pygame.display.set_mode((WIDTH,HEIGHT))
CLOCK = pygame.time.Clock()
BLACK = (0, 0, 0)
#-------------Instances-------------#
bg = Background()
player = Player()
#-------------Functions-------------#
def draw():
WINDOW.fill(BLACK)
bg.update(WINDOW)
player.update(WINDOW)
pygame.display.update()
#-------------Main Game Loop-------------#
def main():
#globals.intialise()
while 1:
CLOCK.tick(1)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
draw()
#globals.game_ticks += 1
if __name__ == "__main__":
main()
player.py
#-------------Imports-------------#
import pygame,math
#import globals
#-------------Constants-------------#
WIDTH,HEIGHT = 500,500
PLAYER_COLOUR = (255, 212, 112)
BLACK = (0,0,0)
PI = 3.14159265359
#-------------Classes-------------#
class Bullet:
def __init__(self,origin,angle):
self.speed = 20
self.x_speed,self.y_speed = self.speed*math.cos(math.radians(angle)),self.speed*math.sin(math.radians(angle))
self.rect = pygame.Rect(origin[0],origin[1],5,5)
def __del__(self):
pass
def update(self,window):
# move bullet
self.rect.x += self.x_speed
self.rect.y += self.y_speed
# draw bullet
pygame.draw.rect(window,BLACK,self.rect)
# check if bullet is out of the screen
if self.rect.x > WIDTH or self.rect.x < 0:
return -1
elif self.rect.y > HEIGHT or self.rect.y < 0:
return -1
class Player:
def __init__(self):
self.sprite = pygame.transform.scale(pygame.image.load("sprites/temp_player.png"),(50,50))
self.rect = pygame.Rect(250,250,50,50)
self.center = (self.rect.x,self.rect.y)
self.bullets = []
self.fire_rate = 12
def shoot(self,angle,window):
# update all bullets and delete if bullet is out of screen
for bullet in self.bullets:
if bullet.update(window) == -1:
self.bullets.remove(bullet)
del bullet
# instantiate bullet if mouse button pressed
#if pygame.mouse.get_pressed()[0] and globals.game_ticks % self.fire_rate == 0:
if pygame.mouse.get_pressed()[0]:
self.bullets.append(Bullet(self.rect.center,-angle))
def update(self,window):
mx,my = pygame.mouse.get_pos()
# find distance between mouse position and player position
diff_x,diff_y = mx-self.rect.x,my-self.rect.y
# word out angle between mouse and player
angle_rad = math.atan2(diff_y,diff_x)
angle = -360*angle_rad/(2*PI)
# adjust angle according to where we want to rotate the player
# when angle is bottom left
if abs(angle) > 90 and angle < 0:
a = 270-abs(angle)
# when angle is top left
elif abs(angle) > 90:
a = angle-90
# when angle is to the right
else:
a = angle - 90
# create new sprite that is rotated
rotated_image = pygame.transform.rotate(self.sprite,a)
# replace current rectangle with rotated sprite
self.rect = rotated_image.get_rect(center = self.center)
self.shoot(angle,window)
# add image to the screen
#window.blit(pygame.transform.rotate(self.sprite,a),self.rect)
background.py:
#-------------Imports-------------#
import pygame,random,ast,time,globals
#-------------Constants-------------#
WIDTH,HEIGHT = 500,500
TILE_DIMENSION = 9
TILE_SIZE = int(round(WIDTH/TILE_DIMENSION,0))
TO_EDGE = int((TILE_DIMENSION+1)/2)
#-------------Classes-------------#
class Background:
def __init__(self):
self.tiles = self.generate_screen()
self.centre = [2,2]
self.right = 0
self.up = 0
self.speed = 5
def generate_screen(self):
# generate original chunk of tiles
tiles = [[random.randint(100,200) for i in range(TILE_DIMENSION)] for j in range(TILE_DIMENSION)]
# eventually use image instead of random RGB value
return tiles
def movement(self,tile_rects):
keys = pygame.key.get_pressed()
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
# if player is on tile to the left of centre
if (self.right - self.speed) < -TILE_SIZE:
# reset movement and adjust centre
self.right = 0
self.centre[0] -= 1
else:
# add to movement if not on next tile
self.right -= self.speed
# move all rectangles in background to simulate player moving
for i in range(len(tile_rects)):
for j in range(len(tile_rects[0])):
tile_rects[i][j].x += self.speed
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
# if player is on tile to the right of centre
if (self.right + self.speed) > TILE_SIZE:
# reset movement and adjust centre
self.right = 0
self.centre[0] += 1
else:
# add to movement if not on next tile
self.right += self.speed
# move all rectangles in background to simulate player moving
for i in range(len(tile_rects)):
for j in range(len(tile_rects[0])):
tile_rects[i][j].x -= self.speed
if keys[pygame.K_w] or keys[pygame.K_UP]:
# if player is on tile above the centre
if (self.up + self.speed) > TILE_SIZE:
# reset movement and adjust centre
self.up = 0
self.centre[1] -= 1
else:
# add to movement if not on next tile
self.up += self.speed
# move all rectangles in background to simulate player moving
for i in range(len(tile_rects)):
for j in range(len(tile_rects[0])):
tile_rects[i][j].y += self.speed
if keys[pygame.K_s] or keys[pygame.K_DOWN]:
# if player is on tile below the centre
if (self.up - self.speed) < -TILE_SIZE:
# reset movement and adjust centre
self.up = 0
self.centre[1] += 1
else:
# add to movement if not on next tile
self.up -= self.speed
# move all rectangles in background to simulate player moving
for i in range(len(tile_rects)):
for j in range(len(tile_rects[0])):
tile_rects[i][j].y -= self.speed
return tile_rects
def update(self,window):
# rendering in brand new map chunks
# if part of the chunk trying to be rendered in is non-existant in the 2D map array to the left
if self.centre[0]-TO_EDGE < 0:
# check how many tiles it is offset by
for i in range(0-(self.centre[0]-TO_EDGE)):
# add new column of values at the beginning of the 2D array
for i in range(len(self.tiles)):
self.tiles[i].insert(0,random.randint(120,230))
# due to whole array being shifted to the right, adjust the centre accordingly
self.centre[0] += 1
# if part of the chunk trying to be rendered is non-existant in the 2D map array to the right
if self.centre[0]+TO_EDGE >= len(self.tiles[0]):
# check how many tiles it is offset by
for i in range((self.centre[0]+TO_EDGE)-(len(self.tiles[0])-1)):
# add a new column of values at the end of the 2D array
for i in range(len(self.tiles)):
self.tiles[i].append(random.randint(120,230))
# if part of the chunk trying to be rendered in is non-existant in the 2D array at the top
if self.centre[1]-TO_EDGE < 0:
# check how many tiles it is offset by
for i in range(0-(self.centre[1]-TO_EDGE)):
# add a new row at the top of the 2D array
self.tiles.insert(0,[random.randint(120,230) for i in range(len(self.tiles[0]))])
# due to whole array shifting downwards, adjust the centre accordingly
self.centre[1] += 1
# if part of the chunk trying to be rendered in is non-existant in the 2D array at the bottom
if self.centre[1]+TO_EDGE >= len(self.tiles):
# check how many tiles it is offset by
for i in range((self.centre[1]+TO_EDGE)-(len(self.tiles)-1)):
# add a new row at the bottom of the 2D array
self.tiles.append([random.randint(120,230) for i in range(len(self.tiles[0]))])
# determining which tiles should be rendered in according to the centre(where player would be)
t = []
for i in range(TILE_DIMENSION+2):
t.append([])
for j in range(TILE_DIMENSION+2):
try:
t[i].append(self.tiles[i+(self.centre[1]-TO_EDGE)][j+(self.centre[0]-TO_EDGE)])
except:
pass
# create a rectangle for each tile that is rendered in
tile_rects = [[pygame.Rect((i-1)*TILE_SIZE-self.right,(j-1)*TILE_SIZE+self.up,TILE_SIZE,TILE_SIZE) for i in range(TILE_DIMENSION+2)] for j in range(TILE_DIMENSION+2)]
tile_rects = self.movement(tile_rects)
# draw all rectangles
for i in range(TILE_DIMENSION+2):
for j in range(TILE_DIMENSION+2):
try:
pygame.draw.rect(window,(0,int(t[i][j]),0),tile_rects[i][j])
except:
pass
the background script doesnt affect anything, its just there as a background to make it easier to see, and you may have to make your own temp_player.png image to make it compatible
I don't understand why the sprite collision detection is not taking the image rotation into account.
I tried different functions but they didn't work out for me.
CarSprites.py:
import pygame, math, random
class CarSprite(pygame.sprite.Sprite):
MIN_FORWARD_SPEED = 5
ACCELERATION = 2
TURN_SPEED = 5
IS_DUMMY = False
def __init__(self, image, position, direction):
pygame.sprite.Sprite.__init__(self)
self.src_image = pygame.transform.scale(pygame.image.load(image), (51, 113))
self.position = position
self.rect = self.src_image.get_rect()
self.rect.center = self.position
self.speed = 0
self.direction = direction
self.k_left = self.k_right = self.k_down = self.k_up = 0
def update(self, deltat):
# SIMULATION
#speed
self.speed += (self.k_up + self.k_down)
if self.speed < self.MIN_FORWARD_SPEED:
self.speed = self.MIN_FORWARD_SPEED
self.speed += (self.k_up + self.k_down)
if self.speed > self.MIN_FORWARD_SPEED * 2:
self.speed = self.MIN_FORWARD_SPEED * 2
#direction
self.direction += (self.k_right + self.k_left)
x, y = self.position
rad = self.direction * math.pi / 180
x += -self.speed*math.sin(rad)
y += -self.speed*math.cos(rad)
self.position = (x, y)
self.image = pygame.transform.rotate(self.src_image, self.direction)
self.rect = self.image.get_rect()
self.rect.center = self.position
#Emulate friction with road and wind
if self.speed > self.MIN_FORWARD_SPEED :
self.speed += -0.1
class DummyCarSprite(pygame.sprite.Sprite):
#MIN_FORWARD_SPEED = 5
#MIN_REVERSE_SPEED = 10.1
#MAX_FORWARD_SPEED_ABOVE_MIN = 5
#ACCELERATION = 2
#TURN_SPEED = 5
def __init__(self, image, position, direction):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(pygame.image.load(image), (51, 113))
self.position = position
self.rect = self.image.get_rect()
self.rect.center = self.position
self.speed = 0
self.direction = direction
self.k_left = self.k_right = self.k_down = self.k_up = 0
if random.randint(0,1) == 1 :
self.direction = self.direction + 180
game.py
def GetDummyCars() :
allDummyCars = [
#Row1
#DummyCarSprite(getCarImage(), (211.9915431212928, 209.36603413022453), 180),
#DummyCarSprite(getCarImage(), (268.9915431212928, 209.36603413022453), 180),
DummyCarSprite(getCarImage(), (325.9915431212928, 209.36603413022453), 180),
DummyCarSprite(getCarImage(), (382.9915431212928, 209.36603413022453), 180)
#etc. etc.
]
dummyCars = []
for dummyCar in allDummyCars :
if random.randint(0,1) == 1 :
dummyCars.append(dummyCar)
return pygame.sprite.RenderPlain(*dummyCars)
playerCar = CarSprite(playerCarImagePath, (1550, 100), 90)
playerCar_group = pygame.sprite.RenderPlain(playerCar)
dummyCar_group = GetDummyCars()
#collisions with dummy cars
dummyCarCollisions = pygame.sprite.groupcollide(playerCar_group, dummyCar_group)
if dummyCarCollisions != {}:
lose_condition = True
playerCar.src_image = pygame.image.load('images/collision.png')
seconds = 0
playerCar.speed = 0
playerCar.MIN_FORWARD_SPEED = 0
playerCar.MAX_FORWARD_SPEED_ABOVE_MIN = 0
playerCar.k_right = 0
playerCar.k_left = 0
I would like to find a way to detect collision between the sprites in the 2 car groups, or collision between the player sprite and the dummycar_group (each would work out for me) that takes the rotation of the image into account.
What happens now is when I steer the car, the car image rotates but it looks like the collision detection doesn't see that.
Is there a better function i can use that could handle this?
My full source code: dropbox
I found this question very interesting and fun to work on and fix! Thanks for posting!
I have found the problem and it is rather unfortunate. Your code runs perfectly from what I have seen. The problem is that pygame uses rectangles for collision detection which are not precise enough.
You are applying the rotation to the image but that just makes it bigger and less accurate. I have highlighted the problem with the addition of rendiering debug lines in the GameLoop function.
# draw some debug lines
pygame.draw.rect(screen, (255, 0, 0), playerCar.rect, 1)
for dummyCar in dummyCar_group.sprites():
pygame.draw.rect(screen, (0, 0, 255), dummyCar.rect, 1)
Add these lines in and you shall see for yourself.
The only solution that I can think of is to add in the functionality to use polygons for collision detection yourself.
The way I would implement this is to:
Stop using the rect attribute of all Sprites for collision detection and stop using any methods for collision detection that use the underlying Rects, e.g pygame.sprite.spritecollide().
add a pointlist field to all sprites that need it which will store all the points of the polygon
Implement your own function that takes in two lists of points and returns if they overlap
I hope that this answer helped you and if you have any further questions please feel free to post a comment below!
I am writing a relatively complex platformer game in which a player moves using wasd and shoots with the mouse. The goal is to get the bullet to travel to the location of the mouse when it was clicked. I have code that sort of works but as the angle gets farther from 0 or 90 (straight left/right or straight up/down) the the bullets final location gets farther from the cursor. I am fairly certain the issue is simply that since the change in x and y are floating points and x,y location of the bullet cannot be floating point there is a rounding issue occurring. I have tried numerous different methods based on forum searches and all of them have the same problem. I have attached the most relevant file (pay particular attention to the bullets init class). Any advice or help would be appreciated. For the record this is just the player class NOT the main.
import pygame
import level
import platform
##import enemies
import math
pygame.init()
## sets up colors that need to be used in every part of the program
black=(0,0,0)
white=(255,255,255)
red=(255,0,0)
green=(0,255,0)
blue=(0,0,255)
class Player(pygame.sprite.Sprite):
## This class represents the player. It is inhariting from the Sprite class in Pygame
window=None
screen_width=0
screen_height=0
width=None
height=None
x_velocity=0
y_velocity=0
chrono_level=500
ball_damage=0
bomb_damage=0
blast_radius=0
gravity=0
isjumping=False
isducking=False
level=None
direction=None
def __init__(self,argwindow,argsheight,argswidth,argcolor=white,argwidth=40,argheight=60,argx=0,argy=0,argball_damage=5,argbomb_damage=15,argbomb_radius=10,argchrono_level=500):
## Constructor. Pass in the color, width, and height
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([argwidth,argheight])
self.image.fill(argcolor)
self.rect=self.image.get_rect()
## sets up the variables inital variables
self.window=argwindow
self.width=argwidth
self.height=argheight
self.screen_height=argsheight
self.screen_width=argswidth
self.rect.x=argx
self.rect.y=argy
self.x_velocity=0
self.y_velocity=0
self.ball_damage=argball_damage
self.bomb_damage=argbomb_damage
self.bomb_radius=argbomb_radius
self.chrono_level=argchrono_level
self.isjumping=False
self.isducking=False
def update(self):
## check gravity
self.calc_grav()
## move left or right
self.rect.x+=self.x_velocity
## check for any collisions
platform_hit_list=pygame.sprite.spritecollide(self,self.level.platform_list,False)## this is the pygame generatred collision detection builtin to the sprite class
for platform in platform_hit_list:
if self.x_velocity > 0:##i.e sprite was moving right
self.rect.right = platform.rect.left ##puts the right side of the sprite flush with the left side of the platform
elif self.x_velocity < 0:
self.rect.left = platform.rect.right
self.x_velocity=0
## move sprite up or down
self.rect.y+=self.y_velocity
## check for any collisions
platform_hit_list=pygame.sprite.spritecollide(self,self.level.platform_list,False)## this is the pygame generatred collision detection builtin to the sprite class
for platform in platform_hit_list:
if self.y_velocity > 0:## i.e. sprite is falling
self.rect.bottom = platform.rect.top ## puts bottom of player flush with top of platform
self.isjumping=False
elif self.y_velocity < 0:
self.rect.top = platform.rect.bottom
self.y_velocity=0
## check direction
pos = pygame.mouse.get_pos()
if pos[0] > (self.rect.x+(self.width/2)): ##i.e. cursor is farther to the right then the middle of the sprite
self.direction="Right"
else: ##pos[0] < (self.rect.x+(self.width/2))
self.direction="Left"
def jump(self):
if not self.isjumping:
self.y_velocity=-15
self.isjumping=True
def calc_grav(self):
if self.y_velocity ==0:
self.y_velocity=1
else:
self.y_velocity+=self.gravity
if self.rect.y >= self.screen_height - self.rect.height and self.y_velocity >= 0:
self.y_velocity = 0
self.rect.y = self.screen_height - self.rect.height
self.isjumping=False
def move_left(self):## called if the player hits the left arrow key or the a key
self.x_velocity=-5
def move_right(self):## called is the player hits the right arrow key or the d key
self.x_velocity=5
def stop(self):## called if the player lets up on either arrow key or the a or d key
self.x_velocity=0
def shoot(self,argmouse_position):
if self.direction=="Left":
bullet_start_x=self.rect.x
bullet_start_y=(self.rect.y+(self.height/2))
elif self.direction=="Right":
bullet_start_x=(self.rect.x+self.width)
bullet_start_y=(self.rect.y+(self.height/2))
bullet=player_bullet(bullet_start_x,bullet_start_y,argmouse_position)
return (bullet)
class player_bullet(pygame.sprite.Sprite):
bullet_x=None
bullet_y=None
bullet_x_velocity=None
bullet_y_velocity=None
target_x=None
target_y=None
speed=10
def __init__(self,argx,argy,argmouse_positon):
pygame.sprite.Sprite.__init__(self)
print "it inited"
self.image = pygame.Surface([4, 10])
self.image.fill(black)
self.rect=self.image.get_rect()
self.rect.x=argx
self.rect.y=argy
self.bullet_x=argx
self.bullet_y=argy
self.target_x=argmouse_positon[0]
self.target_y=argmouse_positon[1]
dx=self.target_x-self.bullet_x
dy=self.target_y-self.bullet_y
angle=math.atan2(dy,dx)
print angle
self.bullet_x_velocity=self.speed*math.cos(angle)
self.bullet_y_velocity=self.speed*math.sin(angle)
def update(self):
print self.rect.x
print self.bullet_x_velocity
self.rect.x+=self.bullet_x_velocity
print self.rect.x
self.rect.y+=self.bullet_y_velocity
def collide(self,argdisplay_width,argdisplay_height,argplatform_list):
Platform_hit_list=pygame.sprite.spritecollide(self, argplatform_list, False)
if len(Platform_hit_list) > 0:
return True
elif self.rect.x > argdisplay_width or self.rect.x < 0:
return True
elif self.rect.y > argdisplay_height or self.rect.y < 0:
return True
else:
return False
I have written a similar game mechanic, where instead of a bullet I could shoot any projectile, of any size, of any sprite. What I did was add my current position to number of pixels I have to move to (defined as vx, vy below). I found the number of pixels i have to move by dividing the differences in the axis, by distance, and the multiplying a speed ( usually 10). Here is the projectile class below(the mask stuff is so bullets don't go into the buildings):
class projectile:
"""an image goes towards the target from starting location"""
def __init__(self, xorg, yorg, x, y, sprite, speed):
self.x = xorg
self.y = yorg
self.gotox = x
self.gotoy = y
self.sprite = sprite
self.xsize = self.sprite.get_width()
self.ysize = self.sprite.get_height()
self.rect = Rect(self.x, self.y, self.xsize, self.ysize)
# divided by 2, because we want the middle of the projectile to goto the destination, not the edge
self.gotox -= self.xsize / 2
self.gotoy -= self.ysize / 2
self.speed = speed
#differance in the x and y axis of destination to current position
self.dx = self.gotox - self.x
self.dy = self.gotoy - self.y
self.slope = (self.dy / max(1, self.dx))
self.gotox += self.gotox * self.slope
self.gotoy += self.gotoy * self.slope
self.dist = max(1, hypot(self.dx, self.dy))
self.state = "alive"
self.rect = Rect(self.x, self.y, self.xsize, self.ysize)
def move(self):
"""moves based on where the target was during time of shooting
untill it hits targer, or hits a wall"""
global currentMask
dist = max(1, hypot(self.dx, self.dy))
# found the num of pixels I have to move (speed is usually 10)
self.vx = self.speed * (self.dx / self.dist)
self.vy = self.speed * (self.dy / self.dist)
if currentMask.get_at((int(self.x + self.vx), int(self.y + self.vy))) != (0, 0, 0) and currentMask.get_at(
(int(self.x + self.vx + self.xsize), int(self.y + self.vy + self.ysize))) != (0, 0, 0) and int(
self.y + self.vy + self.ysize) <= 800 - self.ysize and int(
self.y + self.vy) >= 0 + self.ysize and int(self.x + self.vx) >= self.xsize and int(
self.x + self.vx + self.xsize) <= 1200 - self.xsize:
# added the num of pixels i have to move, to my current postition
self.x += self.vx
self.y += self.vy
else:
self.state = "dead"
self.rect = Rect(self.x, self.y, self.xsize, self.ysize)
screen.blit(self.sprite, (self.x, self.y))
I have an assignment that asks me to do the following:
Use Google's advanced image search to find a reasonably-sized image of a ball that is free to reuse and that includes transparency. Modify the sample code so that your ball slides back and forth across the bottom of the screen. It should take 2 seconds for the ball to go from the left side to the right.
Improve your animation for question 5 so that the ball rotates, accurately, as if it were rolling back and forth.
Modify your animation for question 6 so that the ball travels counterclockwise around the edge of the screen
I am at the last part. Trying to modify the animation for question 6 to do this: (1:24)
http://www.youtube.com/watch?v=CEiLc_UFNLI&feature=c4-overview&list=UUpbgjjXBL3hdTKDZ0gZvdWg
I'm stumped pretty bad. I just can't seem to understand how I will get the ball to slowly move from one point to another. The ball is an image. This is what I have so far, but it doesn't work.
"""Some simple skeleton code for a pygame game/animation
This skeleton sets up a basic 800x600 window, an event loop, and a
redraw timer to redraw at 30 frames per second.
"""
from __future__ import division
import math
import sys
import pygame
class MyGame(object):
def __init__(self):
"""Initialize a new game"""
pygame.mixer.init()
pygame.mixer.pre_init(44100, -16, 2, 2048)
pygame.init()
# set up a 640 x 480 window
self.width = 800
self.height = 600
self.img = pygame.image.load('ball.png')
self.screen = pygame.display.set_mode((self.width, self.height))
self.x = 0
self.y = 0
self.angle = 0
self.rotate_right=True
self.first = True
#0: Move bottomleft to bottomright 1: Move from bottomright to topright 2:Move from topright to topleft 3:Move from topleft to bottomleft
self.mode = 0
# use a black background
self.bg_color = 0, 0, 0
# Setup a timer to refresh the display FPS times per second
self.FPS = 30
self.REFRESH = pygame.USEREVENT+1
pygame.time.set_timer(self.REFRESH, 1000//self.FPS)
def get_mode(self):
rect = self.img.get_rect()
if self.first == True:
self.first = False
return
if (self.x, self.y) == (0, self.height - rect.height):
#Our starting point, bottom left
self.mode = 0
elif (self.x, self.y) == (self.width-rect.width, self.height-rect.height):
#Bottom right
self.mode = 1
elif (self.x, self.y) == (self.width-rect.width, 0):
#Top Right
self.mode = 2
elif (self.x, self.y) == (0,0):
#Top Left
self.mode = 3
def get_target(self):
rect = self.img.get_rect()
if self.mode == 0:
targetPosition = (0, self.height - rect.height)
elif self.mode == 1:
targetPosition = (self.width-rect.width, self.height-rect.height)
elif self.mode == 2:
targetPosition = (self.width-rect.width, 0)
elif self.mode == 3:
targetPosition = (0,0)
return targetPosition
def get_angle(self):
if self.angle == 360:
self.rotate_right = False
elif self.angle == 0:
self.rotate_right = True
if self.rotate_right == True:
self.angle+=12
else:
self.angle-=12
def run(self):
"""Loop forever processing events"""
running = True
while running:
event = pygame.event.wait()
# player is asking to quit
if event.type == pygame.QUIT:
running = False
# time to draw a new frame
elif event.type == self.REFRESH:
self.draw()
else:
pass # an event type we don't handle
def draw(self):
"""Update the display"""
# everything we draw now is to a buffer that is not displayed
self.screen.fill(self.bg_color)
#Draw img
rect = self.img.get_rect()
#Note: this can be made dynamic, but right now since this is typically a poor structure, we will use static values.
#80 is the padding, so it hits right before.
#0,0 : top left
#self.width-rect.width, 0 : top right
#0, self.height-rect.height : bottom left
#self.width-rect.width, self.height-rect.height : bottom right
targetPosition = ()
#img = pygame.transform.rotate(self.img, self.angle)
img = self.img
self.get_angle()
self.get_mode()
targetPosition = self.get_target()
print targetPosition
print self.x, self.y
if self.x < targetPosition[0]:
self.x+= targetPosition[0]-self.x//self.FPS
elif self.x > targetPosition[0]:
self.x-= targetPosition[0]+self.x//self.FPS
if self.y < targetPosition[1]:
print "s"
self.y+= targetPosition[1]-self.y//self.FPS
elif self.y > targetPosition[1]:
self.y-= targetPosition[1]+self.y//self.FPS
rect = rect.move(self.x, self.y)
self.screen.blit(img, rect)
# flip buffers so that everything we have drawn gets displayed
pygame.display.flip()
MyGame().run()
pygame.quit()
sys.exit()
What's happening is that your ball is starting at (0,0) (top left) with a target of (0,550) (bottom left), discovers that it's at a lower y than its target, and promptly proceeds to increment its position by
targetPosition[1] - (self.y // self.FPS)
which is of course equal to 550, so it immediately snaps to the bottom of the screen.
Then during the next draw loop, get_mode() comes along and says 'okay, I'm at (0, 550), so I'll go ahead and set the mode to 0'. Then get_target() comes along and says 'okay, I'm in mode 0, let's go over to (0, 550).
And then this happens again during the next draw loop, and the next, and the next ... So of course your ball doesn't go anywhere.
You'll need to do a couple of things to fix your example:
Fix your target positions in get_target(). Right now they're targeting the same points where the transitions that trigger those modes happen, so your ball won't go anywhere.
Consider your velocity statements more carefully: right now they'll behave somewhat strangely. One way to do this properly is to determine (dx, dy) - that is, the absolute vector from you to your destination - and then normalize this vector such that it points in the same direction but has a magnitude equal to your desired speed. This approach will work for any target position you want.
To elaborate on the second point:
Suppose we're at (x, y) and we're trying to get to (target_x, target_y).
Let dx = target_x - x, dy = target_y - y. This should be uncontroversial: we're just taking the difference.
Then we remember the Pythagorean theorem: given a right triangle with sides a, b, c and hypotenuse c, we recall that len(c)**2 == len(a)**2 + len(b)**2. It's the same thing with vectors: the length of a vector (x, y) is the hypotenuse of a right triangle with side lengths x and y. You can draw this on a piece of paper if you want to prove this to yourself.
Given that, we can find the length of (dx, dy): it's just L(dx, dy) = sqrt(dx*dx + dy*dy). This lends itself to a curious observation: if we multiply both dx and dy by a scalar k, we also multiply the length by k, since sqrt(dx*k*dx*k + dy*k*dy*k) == sqrt(k*k*(dx*dx + dy*dy)) == k*sqrt(dx*dx + dy*dy).
It follows that we can find a vector parallel to (dx, dy), but of length 1, by dividing both dx and dy by L(dx, dy). Precompute L to avoid some potential issues. Multiply this new vector by whatever you want your speed to be: this is your desired velocity.
bassically im trying to add collision detection to the sprite below, using the following:
self.rect = bounds_rect
collide = pygame.sprite.spritecollide(self, wall_list, False)
if collide:
# yes
print("collide")
However it seems that when the collide is triggered it continuously prints 'collide' over and over when instead i want them to simply not be able to walk through the object, any help?
def update(self, time_passed):
""" Update the creep.
time_passed:
The time passed (in ms) since the previous update.
"""
if self.state == Creep.ALIVE:
# Maybe it's time to change the direction ?
#
self._change_direction(time_passed)
# Make the creep point in the correct direction.
# Since our direction vector is in screen coordinates
# (i.e. right bottom is 1, 1), and rotate() rotates
# counter-clockwise, the angle must be inverted to
# work correctly.
#
self.image = pygame.transform.rotate(
self.base_image, -self.direction.angle)
# Compute and apply the displacement to the position
# vector. The displacement is a vector, having the angle
# of self.direction (which is normalized to not affect
# the magnitude of the displacement)
#
displacement = vec2d(
self.direction.x * self.speed * time_passed,
self.direction.y * self.speed * time_passed)
self.pos += displacement
# When the image is rotated, its size is changed.
# We must take the size into account for detecting
# collisions with the walls.
#
self.image_w, self.image_h = self.image.get_size()
global bounds_rect
bounds_rect = self.field.inflate(
-self.image_w, -self.image_h)
if self.pos.x < bounds_rect.left:
self.pos.x = bounds_rect.left
self.direction.x *= -1
elif self.pos.x > bounds_rect.right:
self.pos.x = bounds_rect.right
self.direction.x *= -1
elif self.pos.y < bounds_rect.top:
self.pos.y = bounds_rect.top
self.direction.y *= -1
elif self.pos.y > bounds_rect.bottom:
self.pos.y = bounds_rect.bottom
self.direction.y *= -1
self.rect = bounds_rect
collide = pygame.sprite.spritecollide(self, wall_list, False)
if collide:
# yes
print("collide")
elif self.state == Creep.EXPLODING:
if self.explode_animation.active:
self.explode_animation.update(time_passed)
else:
self.state = Creep.DEAD
self.kill()
elif self.state == Creep.DEAD:
pass
#------------------ PRIVATE PARTS ------------------#
# States the creep can be in.
#
# ALIVE: The creep is roaming around the screen
# EXPLODING:
# The creep is now exploding, just a moment before dying.
# DEAD: The creep is dead and inactive
#
(ALIVE, EXPLODING, DEAD) = range(3)
_counter = 0
def _change_direction(self, time_passed):
""" Turn by 45 degrees in a random direction once per
0.4 to 0.5 seconds.
"""
self._counter += time_passed
if self._counter > randint(400, 500):
self.direction.rotate(45 * randint(-1, 1))
self._counter = 0
def _point_is_inside(self, point):
""" Is the point (given as a vec2d) inside our creep's
body?
"""
img_point = point - vec2d(
int(self.pos.x - self.image_w / 2),
int(self.pos.y - self.image_h / 2))
try:
pix = self.image.get_at(img_point)
return pix[3] > 0
except IndexError:
return False
def _decrease_health(self, n):
""" Decrease my health by n (or to 0, if it's currently
less than n)
"""
self.health = max(0, self.health - n)
if self.health == 0:
self._explode()
def _explode(self):
""" Starts the explosion animation that ends the Creep's
life.
"""
self.state = Creep.EXPLODING
pos = ( self.pos.x - self.explosion_images[0].get_width() / 2,
self.pos.y - self.explosion_images[0].get_height() / 2)
self.explode_animation = SimpleAnimation(
self.screen, pos, self.explosion_images,
100, 300)
global remainingCreeps
remainingCreeps-=1
if remainingCreeps == 0:
print("all dead")
A check for collision is only a check to see if two rectangular sprites have a common area.
There isn't a built in collision that unables player input during collision. You have to write that yourself.
You should probably want to change the player coordinates when a collision takes place. An example:
Let's say we play mario. When the state of mario is JUMPING check for collision. Somewhere we will store the speed of mario in the y axis. When the collision returns True, with any of the blocks, we now set the speed to 0, and the y to the top/bottom of a block. If it will be the bottom, we still keep JUMPING, so it can fall back to the ground.
My tip for the creeper is to have some oldx and oldy value, to return to when the collision takes place. That way the creeper will never go into a wall. Another approach would be to simply change the direction when a collision takes place, but that may not always work.