Consider the following program:
import pygame, sys
from pygame.locals import *
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
class Player(object):
def __init__(self, world):
self.x = 100
self.y = 100
self.size = 30
self.world = world
def draw(self, windowSurface):
pos = (self.x, self.y, self.size, self.size)
pygame.draw.rect(windowSurface, WHITE, pos)
def moveUp(self):
self.y -= 1
def moveDown(self):
self.y += 1
def moveLeft(self):
self.x -= 1
def moveRight(self):
self.x += 1
class OpenIsaac(object):
def __init__(self):
pygame.init()
self.windowSurface = pygame.display.set_mode((500, 400), 0, 32)
pygame.display.set_caption('OpenIsaac')
pygame.key.set_repeat(1, 2)
self.player = Player(self)
self.objects = [self.player]
def mainLoopStep(self):
self.windowSurface.fill(BLACK)
for object_ in self.objects:
object_.draw(self.windowSurface)
pygame.display.update()
def doQuit(self):
pygame.quit()
sys.exit()
def handleQuit(self, event):
if event.type != QUIT:
return
self.doQuit()
def handleKeyboard(self, event):
if event.type != KEYDOWN:
return
if pygame.key.get_pressed()[K_ESCAPE]:
self.doQuit()
if pygame.key.get_pressed()[K_LEFT]:
self.player.moveLeft()
if pygame.key.get_pressed()[K_RIGHT]:
self.player.moveRight()
if pygame.key.get_pressed()[K_DOWN]:
self.player.moveDown()
if pygame.key.get_pressed()[K_UP]:
self.player.moveUp()
def run(self):
while True:
for event in pygame.event.get():
self.handleQuit(event)
self.handleKeyboard(event)
self.mainLoopStep()
pygame.event.pump()
if __name__ == '__main__':
OpenIsaac().run()
When I move the character pressing one or two keys for some time, the character stops movement at some point. I need to release the key and press it again to restore movement. What could cause that?
It is because when Pygame receives one key press, it will move the sprite once according to the code and only once. You will need to find a way to make the program recognize that the key is being held down, and let the program keep the sprite moving. One way is to use pygame.key.set_repeat. It looks similar to this:
delay = 100
interval = 50
pygame.key.set_repeat(delay, interval)
delay is number of milliseconds the game "waits" before repeating the keys. interval is the amount of milliseconds between each key event. For example, I put delay at 100. The game will wait for 100 milliseconds before repeating the key events. Setting interval at 50 will have each key event be 50 milliseconds apart.
Python 2.x
Place these three lines right before the while loop. The lines should not belong in a function or functions. Holding the key down will repeat the appropriate event, such as continuing moving, jumping, etc.
Python 3.x
I'm new to it, so you might have to find that out through Youtube or Pygame Docs. Sorry!
Setting delay to 1 will terminate the key.set_repeat. Making it higher to like 100 will have no effect and the user won't notice it. Same goes to your interval value. I hope it helps you now!
Related
I'm a beginner programmer who is starting with python and I'm starting out by making a game in pygame.
The game basically spawns circles at random positions and when clicked, it gives you points.
Recently I've hit a roadblock when I want to spawn multiple instances of the same object (in this case circles) at the same time.
I've tried stuff like sleep() and some other code related to counters, but it always results in the next circle spawned overriding the previous one (i.e the program spawns circle 1, but when circle 2 comes in, circle 1 disappears).
Does anyone know a solution to this? I would really appreciate your help!
import pygame
import random
import time
pygame.init()
window = pygame.display.set_mode((800,600))
class circle():
def __init__(self, color, x, y, radius, width,):
self.color = color
self.x = x
self.y = y
self.radius = radius
self.width = width
def draw(self, win, outline=None):
pygame.draw.circle(win, self.color, (self.x, self.y, self.radius, self.width), 0)
run=True
while run:
window.fill((0, 0, 0))
pygame.draw.circle(window, (255, 255, 255), (random.randint(0, 800),random.randint(0, 600)), 20, 20)
time.sleep(1)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
pygame.quit()
quit()
It does not work that way. time.sleep, pygame.time.wait() or pygame.time.delay is not the right way to control time and gameplay within an application loop. The game does not respond while you wait. The application loop runs continuously. You have to measure the time in the loop and spawn the objects according to the elapsed time.
pygame.Surface.fill clears the entire screen. Add the newly created objects to a list. Redraw all of the objects and the entire scene in each frame.
See also Time, timer event and clock
You have 2 options. Use pygame.time.get_ticks() to measure the time. Define a time interval after which a new object should appear. Create an object when the point in time is reached and calculate the point in time for the next object:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
next_object_time = 0
while run:
# [...]
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
Minimal example:
repl.it/#Rabbid76/PyGame-TimerSpawnObjects
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
next_object_time = 0
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
current_time = pygame.time.get_ticks()
if current_time > next_object_time:
next_object_time += time_interval
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
The other option is to use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
object_list = []
time_interval = 500 # 500 milliseconds == 0.1 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.
Receive the event in the event loop:
while run:
for event in pygame.event.get():
if event.type == timer_event:
object_list.append(Object())
The timer event can be stopped by passing 0 to the time argument of pygame.time.set_timer.
Minimal example:
repl.it/#Rabbid76/PyGame-TimerEventSpawn
import pygame, random
pygame.init()
window = pygame.display.set_mode((300, 300))
class Object:
def __init__(self):
self.radius = 50
self.x = random.randrange(self.radius, window.get_width()-self.radius)
self.y = random.randrange(self.radius, window.get_height()-self.radius)
self.color = pygame.Color(0)
self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
object_list = []
time_interval = 200 # 200 milliseconds == 0.2 seconds
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, time_interval)
run = True
clock = pygame.time.Clock()
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
object_list.append(Object())
window.fill(0)
for object in object_list[:]:
pygame.draw.circle(window, object.color, (object.x, object.y), round(object.radius))
object.radius -= 0.2
if object.radius < 1:
object_list.remove(object)
pygame.display.flip()
pygame.quit()
exit()
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()
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 have a problem I'm stuck now for last two days. In file game_function in nested loop I'm creating aliens, when I added randint to create random numbers of aliens in a row I run in to problem. Not always (just re-run the game) but sometimes when I detect sprite edge as a method of Alien class Aliens/sprites won't change the direction, follow x axis to the right and constantly dropping down Aliens each pass of check edge. I don't know what the heck is that. Before when I haven't been using randit to generate random numbers of aliens in a row, everything was just fine.
settings.py
class Settings():
"""A class to store all settings for Alien Invasion"""
def __init__(self):
"""Initialize the game settings"""
#Screen settings
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
#ship settings
self.ship_speed_factor = 1.5
#Bullet settings
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullets_allowed = 3
# Alien settings
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
# fleet_direction of 1 represents right; -1 represents left.
self.fleet_direction = 1
ship.py
import pygame
class Ship():
def __init__(self,ai_settings, screen):
"""Initialize the ship and sets the starting position."""
self.screen = screen
self.ai_settings = ai_settings
#load the ship image and get its rect
self.image = pygame.image.load('images/ship.png')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
# Start each new ship at the bottom center of the screen
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
self.center = float(self.rect.centerx)
#Movement Flag
self.moving_right = False
self.moving_left = False
def update(self):
"""Update the ship's position based on the movement Flag."""
#Update the ship's center value, not the rect
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
#update rect object from self.center
self.rect.centerx = self.center
def blitme(self):
"""Draw the ship at its current location"""
self.screen.blit(self.image, self.rect)
In a function create_fleet(ai_settings, screen, ship, aliens) is nested loop, when instead of generating random number between 3-9 and then placing new instance in row I've used constant calculation which always gives 9 aliens in row everything runned just fine. Aliens changed everytime directions and just one time dropped down until next check_edge event passed the condition. So from one wall to another. Now when the randint is in place, NOT ALWAYS, when check_edge method confirms true, then call to function change_fleet_direction() is made and there I see problem, it just sometimes doesn't change the direction. where for direction is used just simple +1 or -1 and in calling for update in Alien class it should either decrease x axis or increase until edge event.
game_functions.py
import sys
import pygame
from bullet import Bullet
from alien import Alien
from random import randint
def create_fleet(ai_settings, screen, ship, aliens):
"""Create a full fleet of aliens."""
#Create an Alien and find the number of aliens in a row
name= 'First unused'
alien = Alien(ai_settings, screen,name)
number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
number_rows = get_number_rows(ai_settings, ship.rect.height,
alien.rect.height)
# Create the fleet of aliens.
for row_number in range(number_rows):
random_num = randint(3, number_aliens_x)
for alien_number in range(0, random_num):
create_alien(ai_settings, screen, aliens, alien_number,
row_number)
def get_number_aliens_x(ai_settings, alien_width):
"""Determine the number of aliens that fit in a row."""
available_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def get_number_rows(ai_settings, ship_height, alien_height):
"""Determine the number of rows of aliens that fit on the screen."""
available_space_y = (ai_settings.screen_height -
(3 * alien_height) - ship_height)
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(ai_settings, screen, aliens, alien_number, row_number):
"""Create alien and place it in the row"""
name = "Alien number " + str(alien_number) + " in row " + str(row_number)
alien = Alien(ai_settings, screen, name)
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
aliens.add(alien)
def check_keydown_events(event, ai_settings, screen, ship, bullets):
"""Respond to key presses"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_settings, screen, ship, bullets):
# Create a new bullet and add it to the bullets group.
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
def check_keyup_events(event,ship):
"""Respond to key releases"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ai_settings, screen, ship, bullets):
"""Respond to keypress and mouse events"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_screen(ai_settings, screen, ship, aliens, bullets):
"""Update images on the screen and flip to the new screen."""
# Redraw the screen during each pass through the loop.
screen.fill(ai_settings.bg_color)
# Redraw all bullets behind ship and aliens.
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# Make the most recently drawn screen visible.
pygame.display.flip()
def update_bullets(bullets):
"""Update position of bullets and get rid of old bullets."""
# Update bullet positions.
bullets.update()
# Get rid of bullets that have disappeared.
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def check_fleet_edges(ai_settings, aliens):
"""Respond appropriately if any aliens have reached an edge."""
for alien in aliens.sprites():
if alien.check_edges():
print(alien.name)
change_fleet_direction(ai_settings, aliens)
break
def change_fleet_direction(ai_settings, aliens):
"""Drop the entire fleet and change the fleet's direction."""
print("old direction " + str(ai_settings.fleet_direction))
for alien in aliens.sprites():
alien.rect.y += ai_settings.fleet_drop_speed
if ai_settings.fleet_direction == 1:
ai_settings.fleet_direction = -1
else:
ai_settings.fleet_direction = 1
print("new direction" + str(ai_settings.fleet_direction))
def update_aliens(ai_settings, aliens):
"""
Check if the fleet is at an edge,
and then update the positions of all aliens in the fleet.
"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
Main file alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Settings
from ship import Ship
import game_functions as gf
def run_game():
# Initialize game and create a screen object.
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Alien Invasion")
ship = Ship(ai_settings,screen)
#Make a group to store bullets in
bullets = Group()
aliens = Group()
#Create the fleet of aliens
gf.create_fleet(ai_settings, screen, ship, aliens)
# Start the main loop for the game.
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(bullets)
gf.update_aliens(ai_settings, aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
run_game()
bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""A class to manage bullets fired from the ship"""
def __init__(self, ai_settings, screen, ship):
"""create a bullet object at the ship's current position"""
super().__init__()
self.screen = screen
#Create a bullet rect at (0, 0) and then set correct position
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,
ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
#Store the bullet position as a decimal value.
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
"""Move bullet up the scereen"""
#Update the decimal position of the bullet
self.y -= self.speed_factor
#Update the rect position
self.rect.y = self.y
def draw_bullet(self):
"""Draw the bullet to the screen"""
pygame.draw.rect(self.screen, self.color, self.rect)
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""A class to represent a single alien in the fleet"""
def __init__(self, ai_settings, screen, name):
"""Initialize the alien and set its starting position"""
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
#Load the alien image and set its rect attribute.
self.image = pygame.image.load('images/alien.bmp')
self.rect = self.image.get_rect()
#Start each alien near the top left of the screen
self.rect.x = self.rect.width
self.rect.y = self.rect.height
#Store the alien's exact position
self.x = float(self.rect.x)
self.name = name
# def blitme(self):
# """Draw the alien at its current location."""
# self.screen.blit(self.image, self.rect)
#
def check_edges(self):
"""Return True if alien is at edge of screen."""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
print("Right >= right screen" +' direction is' + str(self.ai_settings.fleet_direction))
return True
elif self.rect.left <= 0:
print("Left <= left")
return True
def update(self):
"""Move the alien right or left."""
self.x += (self.ai_settings.alien_speed_factor *
self.ai_settings.fleet_direction)
self.rect.x = self.x
I'm new to pygame and sprites, I don't understand it why randit has such impact. I've placed in the code when crucial changes are about to be made print statements to debug it from console (but I dunno if it is logic problem). Anyone who can shed a light to it I would appreciate so much. Lost 2 days of learning and programming already. Running python 3.4 from Eclipse Neon on win 10 Thanks very much.
Found the mistake, indentation in func change_fleet_direction(), if block: cannot be in for loop.
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()