I am making a soccer game. Where pygame should detect a collision between the ball and the player. I am not sure if I am using the right function or I have to use some logic to detect it. If you do need to use some logic. Could you help me understand the logic.
Unless your ball is a single pixel, you probably want to use colliderect().
Create a PyGame Rect for you player, and use the (x,y) of that rect to move it around, and to blit it. Similarly for the ball.
This might give you some code like:
player_img = pygame.image.load( "player.png" ).convert_alpha()
player_rect= player_img.get_rect()
player_rect.topleft = ( player_start_x, player_start_y )
ball_img = pygame.image.load( "ball.png" ).convert_alpha()
ball_rect = ball_img.get_rect()
ball_rect.topleft = ( ball_start_x, ball_start_y )
And a very simple ball-collision function. The colliderect() simply returns True if the parameter-rect overlaps geometrically with the base rect. Since balls are typically round(ish), eventually you could investigate using "masked" collisions, which will only collide where the actual ball is in the image (e.g. not the corners) - but for now don't worry about it.
def playerAtBall( player_rect, ball_rect ):
result = False
if ( player_rect.colliderect( ball_rect ) ):
result = True
return result
Then later put it all together in your main loop:
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_LEFT] ):
print("Move left")
player_rect.x -= 1
if ( keys[pygame.K_RIGHT] ):
print("Move right")
player_rect.x += 1
# Is the player at the ball?
if ( playerAtBall( player_rect, ball_rect ) ):
print( "PLAYER AT BALL - DO SOMETHING!" )
# redraw the screen
window.fill( GREEN )
window.blit( player_img, player_rect )
window.blit( ball_img, ball_rect )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
NOTE: This is not tested, debugged code, just written off the top of my head. Expect typos, errors, etc.
Related
I've been searching for some good tutorial about making simple sprite animation from few images in Python using Pygame. I still haven't found what I'm looking for.
My question is simple: how to make an animated sprite from few images (for an example: making few images of explosion with dimensions 20x20px to be as one but animated)
Any good ideas?
There are two types of animation: frame-dependent and time-dependent. Both work in similar fashion.
Before the main loop
Load all images into a list.
Create three variable:
index, that keeps track on the current index of the image list.
current_time or current_frame that keeps track on the current time or current frame since last the index switched.
animation_time or animation_frames that define how many seconds or frames should pass before switching image.
During the main loop
Increment current_time by the amount of seconds that has passed since we last incremented it, or increment current_frame by 1.
Check if current_time >= animation_time or current_frame >= animation_frame. If true continue with 3-5.
Reset the current_time = 0 or current_frame = 0.
Increment the index, unless if it'll be equal or greater than the amount of images. In that case, reset index = 0.
Change the sprite's image accordingly.
A full working example
import os
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 720, 480
BACKGROUND_COLOR = pygame.Color('black')
FPS = 60
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
def load_images(path):
"""
Loads all images in directory. The directory must only contain images.
Args:
path: The relative or absolute path to the directory to load images from.
Returns:
List of images.
"""
images = []
for file_name in os.listdir(path):
image = pygame.image.load(path + os.sep + file_name).convert()
images.append(image)
return images
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, position, images):
"""
Animated sprite object.
Args:
position: x, y coordinate on the screen to place the AnimatedSprite.
images: Images to use in the animation.
"""
super(AnimatedSprite, self).__init__()
size = (32, 32) # This should match the size of the images.
self.rect = pygame.Rect(position, size)
self.images = images
self.images_right = images
self.images_left = [pygame.transform.flip(image, True, False) for image in images] # Flipping every image.
self.index = 0
self.image = images[self.index] # 'image' is the current image of the animation.
self.velocity = pygame.math.Vector2(0, 0)
self.animation_time = 0.1
self.current_time = 0
self.animation_frames = 6
self.current_frame = 0
def update_time_dependent(self, dt):
"""
Updates the image of Sprite approximately every 0.1 second.
Args:
dt: Time elapsed between each frame.
"""
if self.velocity.x > 0: # Use the right images if sprite is moving right.
self.images = self.images_right
elif self.velocity.x < 0:
self.images = self.images_left
self.current_time += dt
if self.current_time >= self.animation_time:
self.current_time = 0
self.index = (self.index + 1) % len(self.images)
self.image = self.images[self.index]
self.rect.move_ip(*self.velocity)
def update_frame_dependent(self):
"""
Updates the image of Sprite every 6 frame (approximately every 0.1 second if frame rate is 60).
"""
if self.velocity.x > 0: # Use the right images if sprite is moving right.
self.images = self.images_right
elif self.velocity.x < 0:
self.images = self.images_left
self.current_frame += 1
if self.current_frame >= self.animation_frames:
self.current_frame = 0
self.index = (self.index + 1) % len(self.images)
self.image = self.images[self.index]
self.rect.move_ip(*self.velocity)
def update(self, dt):
"""This is the method that's being called when 'all_sprites.update(dt)' is called."""
# Switch between the two update methods by commenting/uncommenting.
self.update_time_dependent(dt)
# self.update_frame_dependent()
def main():
images = load_images(path='temp') # Make sure to provide the relative or full path to the images directory.
player = AnimatedSprite(position=(100, 100), images=images)
all_sprites = pygame.sprite.Group(player) # Creates a sprite group and adds 'player' to it.
running = True
while running:
dt = clock.tick(FPS) / 1000 # Amount of seconds between each loop.
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
player.velocity.x = 4
elif event.key == pygame.K_LEFT:
player.velocity.x = -4
elif event.key == pygame.K_DOWN:
player.velocity.y = 4
elif event.key == pygame.K_UP:
player.velocity.y = -4
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
player.velocity.x = 0
elif event.key == pygame.K_DOWN or event.key == pygame.K_UP:
player.velocity.y = 0
all_sprites.update(dt) # Calls the 'update' method on all sprites in the list (currently just the player).
screen.fill(BACKGROUND_COLOR)
all_sprites.draw(screen)
pygame.display.update()
if __name__ == '__main__':
main()
When to chose which
Time-dependent animation allows you to play the animation at the same speed, no matter how slow/fast the frame-rate is or slow/fast your computer is. This allows your program to freely change the framerate without affecting the animation and it'll also be consistent even if the computer cannot keep up with the framerate. If the program lags the animation will catch up to the state it should've been as if no lag had happened.
Although, it might happen that the animation cycle don't synch up with the framerate, making the animation cycle seem irregular. For example, say that we have the frames updating every 0.05 second and the animation switch image every 0.075 second, then the cycle would be:
Frame 1; 0.00 seconds; image 1
Frame 2; 0.05 seconds; image 1
Frame 3; 0.10 seconds; image 2
Frame 4; 0.15 seconds; image 1
Frame 5; 0.20 seconds; image 1
Frame 6; 0.25 seconds; image 2
And so on...
Frame-dependent can look smoother if your computer can handle the framerate consistently. If lag happens it'll pause in its current state and restart when the lag stops, which makes the lag more noticeable. This alternative is slightly easier to implement since you just need to increment current_frame with 1 on each call, instead of dealing with the delta time (dt) and passing it to every object.
Sprites
Result
You could try modifying your sprite so that it swaps out its image for a different one inside update. That way, when the sprite is rendered, it'll look animated.
Edit:
Here's a quick example I drew up:
import pygame
import sys
def load_image(name):
image = pygame.image.load(name)
return image
class TestSprite(pygame.sprite.Sprite):
def __init__(self):
super(TestSprite, self).__init__()
self.images = []
self.images.append(load_image('image1.png'))
self.images.append(load_image('image2.png'))
# assuming both images are 64x64 pixels
self.index = 0
self.image = self.images[self.index]
self.rect = pygame.Rect(5, 5, 64, 64)
def update(self):
'''This method iterates through the elements inside self.images and
displays the next one each tick. For a slower animation, you may want to
consider using a timer of some sort so it updates slower.'''
self.index += 1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
def main():
pygame.init()
screen = pygame.display.set_mode((250, 250))
my_sprite = TestSprite()
my_group = pygame.sprite.Group(my_sprite)
while True:
event = pygame.event.poll()
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
# Calling the 'my_group.update' function calls the 'update' function of all
# its member sprites. Calling the 'my_group.draw' function uses the 'image'
# and 'rect' attributes of its member sprites to draw the sprite.
my_group.update()
my_group.draw(screen)
pygame.display.flip()
if __name__ == '__main__':
main()
It assumes that you have two images called image1.png and image2.png inside the same folder the code is in.
You should have all your sprite animations on one big "canvas", so for 3 20x20 explosion sprite frames you will have 60x20 image. Now you can get right frames by loading an area of the image.
Inside your sprite class, most likely in update method you should have something like this (hardcoded for simplicity, I prefer to have separate class to be responsible for picking the right animation frame). self.f = 0 on __init__.
def update(self):
images = [[0, 0], [20, 0], [40, 0]]
self.f += 1 if self.f < len(images) else 0
self.image = your_function_to_get_image_by_coordinates(images[i])
For an animated Sprite a list of images (pygame.Surface objects) must be generated. A different picture of the list is displayed in each frame, just like in the pictures of a movie. This gives the appearance of an animated object.
One way to get a list of images is to load an animated GIF (Graphics Interchange Format). Unfortunately, PyGame doesn't offer a function to load the frames of an animated GIF. However, there are several Stack Overflow answers that address this issue:
How can I load an animated GIF and get all of the individual frames in PyGame?
How do I make a sprite as a gif in pygame?
Pygame and Numpy Animations
One way is to use the popular Pillow library (pip install Pillow). The following function loads the frames of an animated GIF and generates a list of pygame.Surface objects:
from PIL import Image, ImageSequence
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
for frame in ImageSequence.Iterator(pilImage):
frame = frame.convert('RGBA')
pygameImage = pygame.image.fromstring(
frame.tobytes(), frame.size, frame.mode).convert_alpha()
frames.append(pygameImage)
return frames
Create a pygame.sprite.Sprite class that maintains a list of images. Implement an update method that selects a different image in each frame.
Pass the list of images to the class constructor. Add an index attribute that indicates the index of the current image in the list. Increase the index in the Update method. Reset the index if it is greater than or equal to the length of the image list (or use the modulo (%) operator). Get the current image from the list by subscription:
class AnimatedSpriteObject(pygame.sprite.Sprite):
def __init__(self, x, bottom, images):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom = (x, bottom))
self.image_index = 0
def update(self):
self.image_index += 1
if self.image_index >= len(self.images):
self.image_index = 0
self.image = self.images[self.image_index]
See also Load animated GIF and Sprite
Example GIF (from Animated Gifs, Animated Image):
Minimal example: repl.it/#Rabbid76/PyGame-SpriteAnimation
import pygame
from PIL import Image, ImageSequence
def loadGIF(filename):
pilImage = Image.open(filename)
frames = []
for frame in ImageSequence.Iterator(pilImage):
frame = frame.convert('RGBA')
pygameImage = pygame.image.fromstring(
frame.tobytes(), frame.size, frame.mode).convert_alpha()
frames.append(pygameImage)
return frames
class AnimatedSpriteObject(pygame.sprite.Sprite):
def __init__(self, x, bottom, images):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom = (x, bottom))
self.image_index = 0
def update(self):
self.image_index += 1
self.image = self.images[self.image_index % len(self.images)]
self.rect.x -= 5
if self.rect.right < 0:
self.rect.left = pygame.display.get_surface().get_width()
pygame.init()
window = pygame.display.set_mode((300, 200))
clock = pygame.time.Clock()
ground = window.get_height() * 3 // 4
gifFrameList = loadGIF('stone_age.gif')
animated_sprite = AnimatedSpriteObject(window.get_width() // 2, ground, gifFrameList)
all_sprites = pygame.sprite.Group(animated_sprite)
run = True
while run:
clock.tick(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_sprites.update()
window.fill((127, 192, 255), (0, 0, window.get_width(), ground))
window.fill((255, 127, 64), (0, ground, window.get_width(), window.get_height() - ground))
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()
Hey I'm working on pong and regardless of how hard I try, I keep facing the same problem; the scoring system doesn't appear on the window when I run the code.
I was told I need to initialize attributes such as scoring system, ball and paddles and I guess I did so for the ball and paddle even though I don't really get what that means since this course is the farthest thing from being kind to students who are new to python. But I guess I didn't do so for the scoring system since it's not appearing on the screen.
I've been struggling with these past few weeks and still haven't found a way to fix this and I need to hand in this next week which is making me freak out.
I would appreciate it if you can explain me what did I do wrong and what initializing attribute means and also check my codes for collision detection!
Thank you all SO MUCH!! (By the way, this always happens and confuses me a lot since I get no error message but the scoring system is just not working as I expected)
# Pong V2
# Pong game needs two players and players try to prevent the ball from hitting
# the edge of the screen of their side. If the ball hits the edge of the window,
# the counterpart of the player who couldn't defence the wall gains a point.
# When one of the players reach 11 points, game ends and the player with higher
# points win the game. Players and move the paddles of their own by pressing
# some keys and ball bounces off when the ball has hit the front part of the
# paddle but passes through when they hit the back part of the paddle.
# wall, game continuing until the player presses 'x' button to close the window,
# paddles on the screen but cannot be moved.
# Version 2 of Pong implies the scoring system and game ending when someone hits
# 11 scores. Also, now collision detection for the paddles and the ball has been
# added as a new feature even though still player cannot move the paddles.
import pygame
from random import randint
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((500, 400))
# set the title of the display window
pygame.display.set_caption('Pong')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
# start the main game loop by calling the play method on the game object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
class Game:
def __init__(self, surface):
# Initialize a Game.
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
#iniotialize the ball
self.ball = Ball('white', 5, [250, 200], [1, 2], self.surface)
self.max_frames = 150
self.frame_counter = 0
# initialize paddles
self.paddleA = pygame.Rect((70, 170), (10, 60))
self.paddleB = pygame.Rect((415, 170), (10, 60))
# initialize paddles
self.scoreA = 0
self.scoreB = 0
def draw_score_A(self):
# this method draws the player's score in the top-left corner of the
# game window.
strscoreA = str(self.scoreA)
font_color = pygame.Color('white')
font_bg = pygame.Color('black')
font = pygame.font.SysFont("arial", 72)
text_img = font.render(strscoreA, True, font_color, font_bg)
text_pos = (0, 0)
self.surface.blit(text_img, text_pos)
def draw_score_B(self):
# this method draws the player's score in the top-right corner of the
# game window.
strscoreB = str(self.scoreB)
font_color = pygame.Color('white')
font_bg = pygame.Color('black')
font = pygame.font.SysFont("arial", 72)
text_img = font.render(strscoreB, True, font_color, font_bg)
text_pos = (425,0)
self.surface.blit(text_img, text_pos)
def update_score(self):
# check if the ball has hit the left or right side edge and update score
# if the ball hit left side, scoreB goes up by 1 and if the ball hit the right
# side the scoreA goes up by 1.
if self.ball.center[0] < self.ball.radius:
self.scoreB += 1
if self.ball.center[0] + self.ball.radius > self.surface.get_width():
self.scoreA += 1
return self.scoreA, self.scoreB
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
self.update_score()
self.draw_score_A()
self.draw_score_B()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames Per Second
def handle_events(self):
# Handle each user event by changing the game state appropriately.
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
def draw(self):
# Draw all game objects.
self.surface.fill(self.bg_color) # clear the display surface first
# draw ball on the surface
self.ball.draw()
# draw both paddles on the surface
paddleA = pygame.Rect((70, 170), (10, 60))
self.paddleA = pygame.draw.rect(self.surface, (255, 255, 255), paddleA)
paddleB = pygame.Rect((415, 170), (10, 60))
self.paddleB = pygame.draw.rect(self.surface, (255, 255, 255), paddleB)
pygame.display.update() # make the updated surface appear on the display
def collision_detection(self):
# figure out if the paddle and the ball has collide or not.
# If the ball has hit the front of the paddle, it is recognizes as
# a collision and bounces off to the opposite direction. However
# if the ball has hit the backside of the paddle, this is not
# considered as a collision and this will not make the ball bounce of but
# go through the paddle.We need to check if the center of the ball has passed
# the center of the paddle or not and for this we will use rect.collidepoint
if self.paddleA.collidepoint(self.center) and self.velocity[index] > 0 is True:
self.velocity[index] = self.velocity[index]
elif self.paddleA.collidepoint(self.center) and self.velocity[index] < 0 is True:
self.velocity[index] = - self.velocity[index]
elif self.paddleB.collidepoint(self.center) and self.velocity[index] > 0 is True:
self.velocity[index] = - self.velocity[index]
elif self.paddleB.collidepoint(self.center) and self.velocity[index] < 0 is True:
self.velocity[index] = self.velocity[index]
def update(self):
# Update the game objects for the next frame.
self.ball.move()
self.frame_counter += self.frame_counter
def decide_continue(self):
# Check and remember if the game should continue
# if the score of one of the players reaches 11, the game should end.
# thus we need to check if anyone's score has reached 11 or not and
# decide if we'll continue the game or not.
if self.scoreA == 11 or self.scoreB == 11 :
self.continue_game = False
class Ball:
# An object in this class represents a ball that moves
def __init__(self, ball_color, ball_radius, ball_center, ball_velocity,
surface):
# Initialize a ball.
# - self is the ball to initialize
# - color is the pygame.Color of the ball
# - center is a list containing the x and y int
# coords of the center of the dot
# - radius is the int pixel radius of the ball
# - velocity is a list containing the x and y components
# - surface is the window's pygame.Surface object
self.color = pygame.Color(ball_color)
self.radius = ball_radius
self.center = ball_center
self.velocity = ball_velocity
self.surface = surface
def move(self):
# Change the location of the ball by adding the corresponding
# speed values to the x and y coordinate of its center
# we need the height and width of the screen to determine when ball
# should change their direction of motion (bounce)
screen_width = self.surface.get_width()
screen_height = self.surface.get_height()
# updates the ball's position based on its velocity and also bounces the
# ball off a wall if it gets too close
screen_size = (screen_width, screen_height)
for index in range(0, len(self.center)):
self.center[index] += self.velocity[index]
if (self.center[index] <= 0 + self.radius or self.center[index] >=
screen_size[index] - self.radius):
self.velocity[index] = -self.velocity[index]
def draw(self):
# Draw the ball on the surface
pygame.draw.circle(self.surface, self.color, self.center, self.radius)
main()
The code is calling draw() (to repaint and flush the screen), and then calling draw_score_A() and draw_score_B() without flushing. So the changes made by the draw_score_ functions are never seen by the user.
If you have a function draw() - make it draw everything. This keeps all the drawing and flushing operations in a single place. Sure the draw() function calls other functions to do the painting, but it's all starts from one place.
def draw( self ):
# Draw all game objects.
# clear the display surface first
self.surface.fill( self.bg_color )
# draw ball on the surface
self.ball.draw()
# draw both paddles on the surface
pygame.draw.rect( self.surface, ( 255, 255, 255 ), self.paddleA )
pygame.draw.rect( self.surface, ( 255, 255, 255 ), self.paddleB )
# Paint the scores
self.draw_score_A()
self.draw_score_B()
# make the updated surface appear on the display
pygame.display.update()
Also, your draw() function seems to be re-defining paddleA and paddleB as part of the drawing. I corrected this in the version above.
Currently, I am working on a game in which you are asked a mathematical question such as 2 + 1 and numbers will fall from the top of the screen. The player has to click the right answer before it falls below the screen. However, I don't want to download images of the numbers because it would waste lots of time. Is there a way to draw or blit numbers instead? If there is, can the numbers be in a circle shape instead of a rectangle?
EDIT: I could create the numbers by displaying them as text. However, that would mean I have to create hundreds of different rectangles for each possible answer. My question then is, is there a way for me to tell Python to generate numbers and place them on a rectangle without me having to do it manually?
One solution would be to create a class in which you store the actual answer, the text surface and the rect for the collision detection. I use a pygame.sprite.Sprite subclass here, but you can do the same with a normal class. When a new question gets asked, you can just create a bunch of instances and add them to a sprite group, so that you can update them altogether in the main loop. You can use the pygame.Rect.collidepoint method for the collision detection.
import random
import pygame as pg
from pygame.math import Vector2
pg.init()
BLUE = pg.Color('dodgerblue1')
FONT = pg.font.Font(None, 42)
class Answer(pg.sprite.Sprite):
def __init__(self, pos, number):
super().__init__()
# Store the actual number, so that we can compare it
# when the user clicks on this object.
self.number = number
# Render the new text image/surface.
self.image = FONT.render(str(number), True, BLUE)
# A rect with the size of the surface, used for collision
# detection and rendering.
self.rect = self.image.get_rect(topleft=pos)
self.vel = Vector2(0, random.uniform(1, 4))
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if self.rect.top > 480: # Screen bottom.
self.kill() # Remove the sprite from all groups.
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 3: # Right mouse button.
# Add 20 numbers (Answer objects).
for _ in range(20):
number = random.randrange(100)
all_sprites.add(Answer((random.randrange(620), -20), number))
elif event.button == 1: # Left mouse button.
# See if the user clicked on a number.
for answer in all_sprites:
# event.pos is the mouse position.
if answer.rect.collidepoint(event.pos):
print(answer.number)
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
(Right click to spawn the numbers, left click to touch single numbers.)
I am currently working on a game called Table Wars, a turn based strategy game for two players. Progress has been going smoothly, until I ran into a problem with spawning units.
The program won't spawn multiple of the same unit, or respawn new ones after the old ones die.
Here is some information that may help:
Each class is stored in a variable: (redI = Red_Infantry())
All functions are stored in the main loop.
The sprite classes have hard-coded X and Y values, used when spawning units and moving units.
What should I do?
As requested, here is the class for the Red Infantry:
class Red_Infantry(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_image('Soldier_red.png', -1)
self.selected = 0
self.area = screen.get_rect()
self.rect.topleft = (100, 300)
self.health = 100 #Soldiers are have mediocre toughness.
self.attack_damage = 25
self.range_maximum = 20 #in pixels, this is melee range
self.range_minimum = 0
self.update()
def update(self):
if self.health <= 0:
self.kill()
and the code to spawn this unit:
if spawned_units < 3 and PHASE == 1 and TURN == 'Red':
if REDGOLD < 10:
print "Out of money! Moving to Phase 2!"
PHASE = 2
spawned_units = 0
elif event.type == KEYDOWN and event.key == K_1:
if REDGOLD >= 10 and REDCOMMAND >= 5:
Sprites.append(redI)
REDGOLD -= 10
REDCOMMAND -= 5
spawned_units = spawned_units + 1
else:
print "Not enough gold!"
This is similar style with all units. It performs correctly the first time, but not in the second, third, and so on, meaning I can only have one Soldier. Also, when that soldier dies via self.kill, it won't come back if I try to spawn it.
The part of the spawn procedure you posted doesn't create any new instances. Unless redI is declared as a new Red_Infantry somewhere else, you need to modify the code to create a new instance of Red_Infantry every time you want to spawn a soldier.
sprites.append(Red_Infantry())
To update the sprites:
for sprite in sprites:
sprite.update()
Put movement and other state changes in the update method. This is what pygame expects, but you can use a different style if you want. The main point is you must have multiple instances of Red_Infantry in order to see multiple sprites.
You could also use pygame's Group class instead of a simple list to hold the sprites.
Here's a full example that uses Group instead of list. In the example, an Enemy is spawned each time a key is pressed. Each Enemy prints its unique ID to stdout.
import sys
import random
import pygame
from pygame.locals import *
def main():
pygame.init()
screen = pygame.display.set_mode((480, 320))
enemies = pygame.sprite.Group()
while True:
for event in pygame.event.get():
if event.type == KEYDOWN:
enemies.add(Enemy(screen))
elif event.type == QUIT:
sys.exit()
enemies.update()
screen.fill(pygame.Color("black"))
enemies.draw(screen)
pygame.display.update()
class Enemy(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
print "created a new sprite:", id(self)
self.image = pygame.image.load("sprite.png")
self.rect = self.image.get_rect()
self.rect.move_ip(random.randint(0, screen.get_width()),
random.randint(0, screen.get_height()))
def update(self):
self.rect.move_ip(random.randint(-3, 3), 0)
if __name__ == "__main__":
main()
I'm trying to write this tutorial in Pygame(Python) and having problems about holding points between planes.
My code is: fiz2.py
Vector class: vector.py
If you move mouse on the Pygame screen, the planes will rotate. And when the planes are rotating, points are passing through planes and going outside.
I tried to fix points' positions on every iteration but they still passed the planes. I have no idea about where should I fix their positions.
NOTE: I know my code is a little bit messy, this is my first 2d program and I had really hard times getting used to Pygame's coordinate plane and vectors. I will re-write when I solve this.
NOTE2: Yes, I wrote the comment about how to hold points between planes on the tutorial, I understand the way he fixes positions but have no idea about how(and where, in code) to implement it.
Thanks.
I can't tell looking at the code. My guess is a variable-timestep, causing instability. But I can't verify if the math is right. Although, I have useful information :
Vectors
You can simplify code, by using vectors as a class vs list/tuple. (velocity, acceleration, location) are treated as one object, verses separate .x and .y values.
# example:
pos[0] += vel[0]
pos[1] += vel[1]
# vs
pos += vel
There is a python-only implementation: euclid.py You can use to compare with your vector.py.
Or use NumPy [ used for 3d graphics, in openGL. ] Is a popular, mature lib.
physics
(It looks like you want to learn by writing your own physics), but check out PyMunk
colors
You can use: pygame.Color
import pygame
from pygame import Color
color = Color('white')
color2 = Color('lightgray')
color3 = Color(0,128,128)
collisions
Look at pygame.sprite.*collide , and pygame.Rect.*collide
pygame Game loop with numpy vector's
Boilerplate I wrote
""" Pygame boilerplate. <ninmonkey>2011/04
pygame main Game() loop, and numpy for vector math.
note:
this might not be the most effecient way to use numpy as vectors, but it's an intro.
And this does not force fixed-timesteps. If you want a stable simulation, you need to use a fixed timestep.
see: http://gafferongames.com/game-physics/fix-your-timestep/
Keys:
ESC : exit
Space : game_init()
"""
import pygame
from pygame.locals import *
from pygame import Color, Rect
import numpy as np
def get_screen_size():
"""return screen (width, height) tuple"""
screen = pygame.display.get_surface()
return screen.get_size()
class Actor():
"""basic actor, moves randomly.
members:
loc = position vector
velocity = velocity vector
width, height
"""
def __init__(self, loc=None, velocity=None):
"""optional initial loc and velocity vectors"""
self.width = 50
self.height = 50
# if loc or velocity are not set: use random
if loc is None: self.rand_loc()
else: self.loc = loc
if velocity is None: self.rand_velocity()
else: self.velocity = velocity
def update(self):
"""update movement"""
self.loc += self.velocity
def rand_velocity(self):
"""set a random vector , based on random direction. Using unit circle:
x = cos(deg) * speed
"""
rad = np.radians( np.random.randint(0,360) )
speed = np.random.randint(1,15)
x = np.cos(rad)
y = np.sin(rad)
velocity = np.array( [x,y])
velocity *= speed
self.velocity = velocity
def rand_loc(self):
"""random location onscreen"""
width,height = get_screen_size()
x = np.random.randint(0,width)
y = np.random.randint(0,height)
self.loc = np.array([x,y])
def is_onscreen(self):
"""test is screen.colliderect(actor) true?"""
x,y = self.loc
w,h = get_screen_size()
screen = Rect(0, 0, w, h)
actor = Rect(x, y, self.width, self.height)
if screen.colliderect(actor): return True
else: return False
class GameMain():
"""game Main entry point. handles intialization of game and graphics."""
done = False
debug = False
color_gray = Color('lightgray')
def __init__(self, width=800, height=600, color_bg=None):
"""Initialize PyGame"""
pygame.init()
self.width, self.height = width, height
self.screen = pygame.display.set_mode(( self.width, self.height ))
pygame.display.set_caption( "boilerplate : pygame" )
self.clock = pygame.time.Clock()
self.limit_fps = True
self.limit_fps_max = 60
if color_bg is None: color_bg = Color(50,50,50)
self.color_bg = color_bg
self.game_init()
def game_init(self):
"""new game/round"""
self.actors = [Actor() for x in range(10)]
def loop(self):
"""Game() main loop"""
while not self.done:
self.handle_events()
self.update()
self.draw()
if self.limit_fps: self.clock.tick( self.limit_fps_max )
else: self.clock.tick()
def update(self):
"""update actors, handle physics"""
for a in self.actors:
a.update()
if not a.is_onscreen():
a.rand_loc()
def handle_events(self):
"""handle regular events. """
events = pygame.event.get()
# kmods = pygame.key.get_mods() # key modifiers
for event in events:
if event.type == pygame.QUIT: sys.exit()
elif event.type == KEYDOWN:
if (event.key == K_ESCAPE): self.done = True
elif (event.key == K_SPACE): self.game_init()
def draw(self):
"""render screen"""
# clear screen
self.screen.fill( self.color_bg )
# Actor: draw
for a in self.actors:
x,y = a.loc
w,h = a.width, a.height
r = Rect(x, y, w, h)
self.screen.fill(self.color_gray, r)
# will call update on whole screen Or flip buffer.
pygame.display.flip()
if __name__ == '__main__':
g = GameMain()
g.loop()