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.
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 wrote this physics simulation in pygame and the collision mechanism is not working properly. It seems to work when I collide the player character with the wall from above the wall or from the left and not work for collisions from the bottom or from the right
I have been trying to find this bug for some time but I just have no clue as to what might cause this. I am using python 3.7.3 and pygame 1.9.5 (latest versions as of date)
I am sorry for pasting an entire file but I just have no Idea where the problem is
import pygame # import the pygame library to have access to game building tools
import math
# these variables will be used to hold game objects and draw them
rigid_bodies = []
g = 100
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PURPLE = (127, 0, 255)
RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)
class RigidBody(pygame.Rect):
"""
represents a rectangular object that acts according to newton's laws of motion
"""
def __init__(self, canvas, color, m, u, x, y, w, h):
"""
called automatically when a new object is created to initialize the object
:param canvas: the canvas on which to draw the object
:param color: the color of the object
:param m: the mass of the object
:param u: Coefficient of friction
:param x: the starting position of the object on the x axis
:param y: the starting position of the object on the y axis
:param w: the width of the object
:param h: the height of the object
"""
super().__init__(x, y, w, h) # initialize the parent Rect object
self.canvas = canvas
self.color = color
self.m = m
self.u = u
self.x_speed = 0 # the speed of the object on the x axis
self.y_speed = 0 # the speed of the object on the y axis
def apply_force(self, axis, F, initiator=None):
"""
used to apply force on the object
:param axis: the axis of the force
:param F: the amount of force to apply
:param initiator: the object that is applying the force
"""
a = F / self.m # calculate the acceleration the object should have
if axis == 'y':
self.y_speed += a
elif axis == 'x':
self.x_speed += a
if initiator:
initiator.apply_force(axis, -1 * F) # apply normal force
print('colliding')
def inertia(self):
"""
shall be run every frame to make the object move according to its speed
if possible and take the necessary steps if not
"""
# go:
self.x += self.x_speed
self.y += self.y_speed
for body in rigid_bodies:
if self.colliderect(body): # if collide with another object:
self.x -= self.x_speed # go back
self.y -= self.y_speed
body.apply_force('x', self.m * self.x_speed, self) # and apply force on that object
body.apply_force('y', self.m * self.y_speed, self)
break
def draw(self):
"""
shall be run every frame to draw the object on the canvas
"""
pygame.draw.rect(self.canvas, self.color, (self.x, self.y, self.w, self.h))
class Controller:
def __init__(self, character, F):
"""
initialize the controller object
:param character: the character to control
:param F: the force to apply to the character for every frame a button is being pressed
"""
self.character = character
self.up = 0 # whether to move up or not
self.down = 0 # whether to move down or not
self.left = 0 # whether to move left or not
self.right = 0 # whether to move right or not
self.F = F
def stop(self):
"""
stops applying force on the object
"""
self.up = 0
self.down = 0
self.left = 0
self.right = 0
def run(self):
"""
shall be run every frame to apply force on the character according to user input
"""
self.character.apply_force('y', -self.F * self.up)
self.character.apply_force('y', self.F * self.down)
self.character.apply_force('x', -self.F * self.left)
self.character.apply_force('x', self.F * self.right)
def main():
"""
the main function contains the main loop
that runs repeatedly while the game is running
"""
crashed = False # tells if the program crashed or if the window was closed
pygame.init() # required to use pygame
canvas = pygame.display.set_mode((1000, 700)) # define the canvas
clock = pygame.time.Clock() # will be used to limit the number of times a loop runs per second
pygame.display.set_caption('the dot game V2')
character = RigidBody(canvas, WHITE, 1000, 0.3, 500, 500, 20, 50) # initialize the character
player = Controller(character, 500) # initialize the controller
rigid_bodies.append(RigidBody(canvas, WHITE, math.inf, 0, 300, 300, 300, 20)) # initialize the wall
while not crashed:
# handle inputs:
for event in pygame.event.get():
if event.type == pygame.QUIT:
crashed = True
elif event.type == pygame.MOUSEBUTTONUP:
pass
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
player.up = 1
elif event.key == pygame.K_DOWN:
player.down = 1
elif event.key == pygame.K_LEFT:
player.left = 1
elif event.key == pygame.K_RIGHT:
player.right = 1
elif event.type == pygame.KEYUP:
player.stop()
player.run()
character.inertia()
canvas.fill(BLACK)
character.draw()
for body in rigid_bodies:
body.draw()
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
I suspect that the problem is with either the "inertia" or the "apply_force" functions but I just cant figure out WHAT is the problem with those functions
The character should stop moving every time it hits the wall, but when it hits the wall from below or from the right it gets stuck and can only move up or to the left
The issue is caused by casting a floating point value to int and can be solved by:
stored_pos = (self.x, self.y)
self.x += self.x_speed
self.y += self.y_speed
for body in rigid_bodies:
if self.colliderect(body): # if collide with another object:
self.x, self.y = stored_pos
Note, that
self.x -= self.x_speed
self.y -= self.y_speed
is not the inverse operation of
self.x += self.x_speed
self.y += self.y_speed
e.g: a = 2 and b = 0.5
int(a + b) == int(2 + 0.5) == 2
int(a - b) == int(2 - 0.5) == 1
The solution is to store the original values of self.x and self.y
stored_pos = (self.x, self.y)
and to restore it in the case of a collision:
self.x, self.y = stored_pos
I have a ship that shoots lasers. It draws a line from itself to (mouseX, mouseY). How can I have the ship accelerate in the exact opposite direction as if being pushed backwards?
I have been working through some trig and ratios but am not making much progress. Acceleration is constant so how far the mouse is doesn't matter, just the direction.
I've been primarily working on trying to create a vector that only works with integers in the range (-4,4), as I cannot have the ship move across the screen at a Float speed, and the (-4,4) range would at least give me... 16 different directions for the ship to move, though I'd like that not to be a limitation.
There's no trigonometry involved, just vectors. If you take the subtract the position of the mouse from the position of the ship
(shipX-mouseX, shipY-mouseY)
that gives you the direction in which to move the ship. Multiply it by some factor, round it to integers, and add it to the ship's current position. You may want to do this a few times, to give some continuous motion. Also, you may want the factor to vary: increase the first few ticks, and then decrease to zero.
The vector of acceleration will be in the direction from the mouse cursor to the ship, away from the mouse position - so if the line from mouse to ship is at angle theta to horizontal (where anticlockwise theta is +ve, clockwise is -ve), so will the force F acting on the ship to accelerate it. If ship has mass M then using f=ma, acceleration A will be F/M. Acceleration in x axis will be A * cos(theta), in y A * sin(theta) - then use v=u+at after every time interval for your simulation. You will have to use floating point maths for this calculation.
Here's a full example using simple vector math. Note the comments.
import pygame
import math
# some simple vector helper functions, stolen from http://stackoverflow.com/a/4114962/142637
def magnitude(v):
return math.sqrt(sum(v[i]*v[i] for i in range(len(v))))
def add(u, v):
return [ u[i]+v[i] for i in range(len(u)) ]
def sub(u, v):
return [ u[i]-v[i] for i in range(len(u)) ]
def dot(u, v):
return sum(u[i]*v[i] for i in range(len(u)))
def normalize(v):
vmag = magnitude(v)
return [ v[i]/vmag for i in range(len(v)) ]
class Ship(object):
def __init__(self):
self.x, self.y = (0, 0)
self.set_target((0, 0))
self.org_speed = 0.9
self.speed = self.org_speed
self.laser = (None, 0)
#property
def pos(self):
return self.x, self.y
# for drawing, we need the position as tuple of ints
# so lets create a helper property
#property
def int_pos(self):
return map(int, self.pos)
#property
def target(self):
return self.t_x, self.t_y
#property
def int_target(self):
return map(int, self.target)
def set_target(self, pos):
self.t_x, self.t_y = pos
def update(self):
if self.speed < self.org_speed:
self.speed += min(self.org_speed, 0.3)
# if we won't move, don't calculate new vectors
if self.int_pos == self.int_target:
return
target_vector = sub(self.target, self.pos)
# a threshold to stop moving if the distance is to small.
# it prevents a 'flickering' between two points
if magnitude(target_vector) < 2:
return
# apply the ship's speed to the vector
move_vector = [c * self.speed for c in normalize(target_vector)]
# update position
self.x, self.y = add(self.pos, move_vector)
def draw(self, s):
pygame.draw.circle(s, (255, 0 ,0), self.int_pos, 5)
end, state = self.laser
if state > 0:
pygame.draw.line(s, (255, 255, 0 if state % 2 else 255), self.pos, end, 2)
self.laser = end, state - 1
def fire(self, pos):
self.speed = -min(5, magnitude(sub(pos, self.pos)) / 10.0)
self.laser = pos, 5
pygame.init()
quit = False
s = pygame.display.set_mode((300, 300))
c = pygame.time.Clock()
ship = Ship()
FIRE = pygame.USEREVENT
pygame.time.set_timer(FIRE, 2000)
while not quit:
quit = pygame.event.get(pygame.QUIT)
if pygame.event.get(FIRE):
ship.fire(pygame.mouse.get_pos())
ship.set_target(pygame.mouse.get_pos())
pygame.event.poll()
ship.update()
s.fill((0, 0, 0))
ship.draw(s)
pygame.display.flip()
c.tick(60)
I have to move the player with the mouse. I'll explain better:
When I click into the screen , I have a target and my hero moves from his actual position to the new one.
I have to put the boundaries , and I'm having some problems.
I tried the solution that I'm posting here, but when the player hits the boundaries he gets stuck to these boundaries.
Hero(player) class:
def get_direction(self, target):
'''
Function:
takes total distance from sprite.center
to the sprites target
(gets direction to move)
Returns:
a normalized vector
Parameters:
- self
- target
x,y coordinates of the sprites target
can be any x,y coorinate pair in
brackets [x,y]
or parentheses (x,y)
'''
if self.target: # if the square has a target
position = Vector(self.rect.centerx, self.rect.centery) # create a vector from center x,y value
target = Vector(target[0], target[1]) # and one from the target x,y
self.dist = target - position # get total distance between target and position
direction = self.dist.normalize() # normalize so its constant in all directions
return direction
def distance_check(self, dist):
'''
Function:
tests if the total distance from the
sprite to the target is smaller than the
ammount of distance that would be normal
for the sprite to travel
(this lets the sprite know if it needs
to slow down. we want it to slow
down before it gets to it's target)
Returns:
bool
Parameters:
- self
- dist
this is the total distance from the
sprite to the target
can be any x,y value pair in
brackets [x,y]
or parentheses (x,y)
'''
dist_x = dist[0] ** 2 # gets absolute value of the x distance
dist_y = dist[1] ** 2 # gets absolute value of the y distance
t_dist = dist_x + dist_y # gets total absolute value distance
speed = self.speed ** 2 # gets aboslute value of the speed
if t_dist < (speed): # read function description above
return True
def Walking(self):
'''
Function:
gets direction to move then applies
the distance to the sprite.center
()
Parameters:
- self
'''
self.dir = self.get_direction(self.target) # get direction
if self.dir: # if there is a direction to move
if self.distance_check(self.dist): # if we need to stop
self.rect.center = self.target # center the sprite on the target
else: # if we need to move normal
self.x += (self.dir[0] * self.speed) # calculate speed from direction to move and speed constant
self.y += (self.dir[1] * self.speed)
self.rect.center = (round(self.x),round(self.y)) # apply values to sprite.center
Level 1:
def ProcessInput(self, events):
for event in events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.__myHero.moveLeft()
if event.key == pygame.K_RIGHT:
self.__myHero.moveRight()
if event.type == pygame.MOUSEBUTTONDOWN:
self.__myHero.target = event.pos
#if event.type == pygame.MOUSEBUTTONDOWN:
#print(pygame.mouse.get_pos())
#self.__myHero.target = pygame.mouse.get_pos() # set the sprite.target to the mouse click position
def Update(self):
#Put your game logic in here for the scene.
#self.SwitchToScene(SecondScene())
if self.__myHero.x >= 740:
self.__myHero.target = None
else:
self.__myHero.Walking()
def Render(self, display_game):
display_game.blit(self.image_background, (0, 0))
display_game.blit(self.__myHero.getImage(), self.__myHero.rect.topleft)
pygame.display.update()
Main:
if not quit_attempt:
active_scene.ProcessInput(filtered_events)
active_scene.Update()
active_scene.Render(display_game)
active_scene = active_scene.next
In the Update() function I used the following code:
if self.__myHero.x >= 740:
self.__myHero.target = None
If my hero go over the boundaries , I change his target and I set it to None
because I want that he stops his walking. He stops himself , but remains completely blocked. I don't know why.
Can you help?
If your hero goes over the boundaries, you change it's target to None to stop walking. The problem here lies in the if statement that performs this action.
Once you enter this statment:
if self.__myHero.x >= 740:
self.__myHero.target = None
The value of self.__myHero.x remains bigger or equal to 740. Therefore you should do something to avoid that the value of self.__myHero.x is bigger or equal to 740 within the if statement otherwise you will keep setting None into self.__myHero.target
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.