Python and Pygame Value is updating but update is not taken into account by code? - python

I wanted to ask you for your help. I am following tutorial about creating alien game but I modified it a little bit and I cannot get it to work properly. I have value for alien_speed in settings pyfile. I am modifying it in method increase_speed and I am printing it (and it is actually growing like I want it to). But aliens have still the same speed. I don't understand why it is not working. Can maybe someone point me in right direction ?
settings.py:
import pygame
resolution_width = 1280
resolution_height = 720
class Settings:
"""A class to store all settings for Space Impact."""
def __init__(self):
"""Initialize the game's static settings."""
# Screen settings
# This line is needed to avoid error: No video mode has been set
self.screen = pygame.display.set_mode((0, 0))
self.screen_width = resolution_width
self.screen_height = resolution_height
self.bg_image = pygame.image.load("images/background_1.png").convert()
# Bullet settings
self.bullet_speed = self.screen_width*0.01
self.bullet_width = self.screen_width*0.02
self.bullet_height = self.screen_height*0.02
self.bullet_color = (0, 0, 0)
# How quickly the game speeds up
self.speedup_scale = 999
# Ship Settings
self.ships_limit = 3
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
self.alien_speed = self.screen_width*0.003
def increase_speed(self):
"""Increase speed settings."""
self.alien_speed *= self.speedup_scale
print(self.alien_speed)
alien.py:
import pygame
from pygame.sprite import Sprite
from settings import Settings
import random
class Alien(Sprite):
"""A class to represent a single alien."""
def __init__(self, space_impact):
"""Initialize alien and set it's starting position."""
super().__init__()
self.screen = space_impact.screen
self.settings = Settings()
# Load the alien image and set it's rect attribute.
self.index = 0
self.timer = 0
self.image = []
self.image.append(pygame.image.load('images/alien_1.png'))
self.image.append(pygame.image.load('images/alien_2.png'))
self.image = self.image[self.index]
self.image = pygame.transform.scale(self.image, (80 * int(self.settings.screen_width * 0.0019),
40 * int(self.settings.screen_width*0.0019)))
self.rect = self.image.get_rect()
random_height = random.uniform(0.01, 0.85)
random_width = random.uniform(1.1, 2)
self.rect.x = int(self.settings.screen_width * random_width)
self.rect.y = int(self.settings.screen_height * random_height)
# Store the alien's exact horizontal position.
self.x = float(self.rect.x)
def update(self):
"""Move the alien to left side."""
self.x -= self.settings.alien_speed
self.rect.x = self.x
if self.timer >= 0 and self.timer <= 25:
self.timer += 1
self.index = 0
elif self.timer >= 26 and self.timer < 50:
self.timer += 1
self.index = 1
else:
self.timer = 0
if self.index == 0:
self.image = pygame.image.load("images/alien_1.png")
if self.index == 1:
self.image = pygame.image.load("images/alien_2.png")
self.image = pygame.transform.scale(self.image, (80 * int(self.settings.screen_width * 0.0019),
40 * int(self.settings.screen_width * 0.0019)))
Edit: Of course in my main file I am calling function self.settings.increase_speed()
Edit2:
import pygame
resolution_width = 1280
resolution_height = 720
class Settings:
"""A class to store all settings for Space Impact."""
screen_width = resolution_width
alien_speed = screen_width * 0.003
speedup_scale = 3
alien_speed *= speedup_scale
def __init__(self):
"""Initialize the game's static settings."""
# Screen settings
# This line is needed to avoid error: No video mode has been set
self.screen = pygame.display.set_mode((0, 0))
self.screen_width = resolution_width
self.screen_height = resolution_height
self.bg_image = pygame.image.load("images/background_1.png").convert()
# Bullet settings
self.bullet_speed = self.screen_width*0.01
self.bullet_width = self.screen_width*0.02
self.bullet_height = self.screen_height*0.02
self.bullet_color = (0, 0, 0)
# How quickly the game speeds up
self.speedup_scale = 3
# Ship Settings
self.ships_limit = 3
def increase_speed(self):
"""Increase speed settings."""
global alien_speed
global speedup_scale
alien_speed *= speedup_scale
print(self.alien_speed)
Edit3:
I managed to fix it thanks to your comments. Thank you :)

Settings seems to contain global settings, but each Alien creates its own instance of Settings:
class Alien(Sprite):
"""A class to represent a single alien."""
def __init__(self, space_impact):
# [...]
self.settings = Settings()
That means that each Alien has its own Settings and thus alien_speed.
Either you've to update the settings in each instance of Alien, that means you've to call increase_speed() for each Alien separately.
Or just pass a singleton instance of Settings to Aliens. That makes it sufficient to update the singleton instance:
class Alien(Sprite):
"""A class to represent a single alien."""
def __init__(self, space_impact, settings):
"""Initialize alien and set it's starting position."""
super().__init__()
self.screen = space_impact.screen
self.settings = settings
# [...]
In "main":
alien = Alien(space_impact, self.settings)
Another option would be to turn the attributes of the class Settings to class attributes. Instance attribute are unique to each instance, but class attributes are shared by all instances.

Related

Class inheritance in python with pygame

So I'm working on a hobby game, and i'm not able to get one class to inherit another properly. The file structure is as shown below:
main.py
Enemy
|walker.py
|genericEnemy.py
main.py calls walker.py, whose main class inherits from genericEnemy.py. Both of their contents are below:
walker.py
import pygame
from .genericEnemy import generic
class walker(generic):
pass
genericEnemy.py
'''
This class controls generic capabilities for all enemies.
Specific abilities are in the enemy type's class in the superfolder
'''
import pygame
class generic:
def __init__(self, speed, pos, size):
'''
speed - The entity speed, an int greater than 0
pos - The (x,y) position of the entity, a list of length 2
size - The entity hitbox, a list with length 2
'''
#Movement Variables
self.speed = speed
self.currDir = 1
self.isMoving = True
#Drawing Variables
self.pos = pos
self.size = size
#Gravity Variables
self.isJumping = False
self.fallCounter = 0
self.gravityTimer = 0
#==================================================
def draw(self, surface):
pygame.draw.rect(surface, (255, 0, 0), (self.pos[0], self.pos[1], self.size[0], self.size[1]))
#==================================================
def updateGravity(self):
self.fallCounter += 1
if self.fallCounter == 8:
self.fallCounter = 0
self.gravityTimer += 1
#==================================================
def walk(self):
if self.isMoving:
self.pos[0] += self.speed * self.currDir
The issue I'm having is that in main when I say:
SCREEN = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
ENEMY = walker(6, [120, 1000], [10, 30])
and then later on
ENEMY.draw(SCREEN)
I get the error: AttributeError: 'walker' object has no attribute 'draw'
Any help would be greatly appreciated, like I said this is a hobbyist project, so i'm fairly inexperienced in python/pygame
User Barmar was correct. I, as an absolute walnut, had my functions indented too far.
Use class generic(pygame.sprite.Sprite):
and
all_sprites = pygame.sprite.Group()
all_sprites.add(ENEMY)
all_sprites.draw(SCREEN)
pygame sprite group has
draw function
And maybe it need sprite.image!
It can be surface
self.image = pygame.Surface([self.size[0], self.size[1]])
self.image.fill((255, 0, 0))
or image file
self.image = pygame.transform.scale(pygame.image.load(imgname).convert_alpha(), (self.size[0], self.size[1]))

My sprite instances are not being displayed properly

I'm trying to implement a design where the brick columns are on the left and right sides of the action screen and a data/score screen is on the top. The brick class draws a rect sprite that has the proper size to fill the brick column space. I've made 2 instances for the two sides, but only the left-sided instance is showing and it's being drawn halfway lower than where I want it to be, and if I print the coordinates of the rect instances they are in the right place. Could anyone help me understand what I'm missing/doing wrong? Thank you in advance for your help!
settings.py (control panel class for the game)
class Settings():
def __init__(self):
self.BLACK = (0,0,0)
self.GREEN = (0,255,0)
# Game Screen
self.screen_h = 600
self.screen_w= int(self.screen_h * .6)
self.size = (self.screen_w, self.screen_h)
#Game layout--------------------
# DataScreen specs
self.data_h= int(self.screen_h/4)
self.data_w= self.screen_w
self.data_color = (255,255,0)# yellow
# ActionScreen specs------------
self.action_h = self.screen_h
self.action_w = self.screen_w * .85
self.action_x = int(self.screen_w * .15)
self.action_y = self.data_h
self.action_color = (173,216,230)# light blue
# brick specs--------------
self.brick_fill_h = int(self.screen_h - self.data_h)
self.brick_fill_w = int(self.action_x)
self.brick_inside_color =(252, 106, 3) # Tiger Orange
game_layout.py (display sprite classes file)
import pygame as pg
from settings import Settings
from pygame.sprite import Group,Sprite
BLACK=(0,0,0)
class DataScreen():
def __init__(self,ai_set):
self.width = ai_set.data_w
self.height = ai_set.data_h
self.image = pg.Surface([self.width,self.height])
self.rect = self.image.get_rect()
pg.draw.rect(self.image, ai_set.data_color, [0,0, self.width,self.height],5)
class ActionScreen():
def __init__(self,ai_set):
self.width = ai_set.action_w
self.height = ai_set.action_h
self.x = ai_set.action_x
self.y = ai_set.action_y
self.image = pg.Surface([self.width,self.height])
self.rect = self.image.get_rect()
pg.draw.rect(self.image, ai_set.action_color, [self.x,self.y, self.width,self.height])
class Brick(Sprite):
def __init__(self,ai_set,x,y):
super().__init__()
self.width= ai_set.brick_fill_w
self.height= ai_set.brick_fill_h #int(ai_set.screen_h)
self.x = x
self.y = y
self.image = pg.Surface([self.width,self.height])
self.image.set_colorkey(ai_set.BLACK)
self.rect = self.image.get_rect(topleft=(x,y))
pg.draw.rect(self.image,ai_set.brick_inside_color, [x,y,self.width,self.height])
game_fxn.py(functions necessary to run the game)
import pygame as pg
import sys
def check_events():
for event in pg.event.get():
if event.type == pg.QUIT:
sys.exit()
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
sys.exit()
def update(ai_set,screen,data_s,action_s,brick,brick2):#,bricks
screen.fill(ai_set.BLACK)
screen.blit(action_s.image, action_s.rect)
screen.blit(data_s.image,data_s.rect)
screen.blit(brick.image,brick.rect)
screen.blit(brick2.image,brick2.rect)
pg.display.update()
main.py (main game file)
import pygame as pg
import random
from settings import Settings
from platforms import Tile, Spike
from player import Player
from game_layout import DataScreen, ActionScreen, Brick
import game_fxn as gf
from pygame.sprite import Group
pg.init()
ai_set=Settings()
screen = pg.display.set_mode(ai_set.size)
pg.display.set_caption = "Game 1"
data_s = DataScreen(ai_set)
action_s = ActionScreen(ai_set)
def main():
brick = Brick(ai_set,0,ai_set.data_h)
brick2 = Brick(ai_set,(ai_set.action_x+ai_set.action_w),ai_set.data_h-ai_set.data_h/2)
while True:
gf.check_events()
gf.update(ai_set,screen, data_s, action_s,brick,brick2)
if __name__ == '__main__':
main()
This is what I'm getting:
The top left coordinates of ActionScreen and Brick are saved in the x and y attributes. However, when you draw something on the image, you must use coordinates relative to the Surface, but not relative to the screen. the top left coordinate of an Surface is always (0, 0):
class ActionScreen():
def __init__(self,ai_set):
# [...]
pg.draw.rect(self.image, ai_set.action_color, [0, 0, self.width, self.height])
class Brick(Sprite):
def __init__(self,ai_set,x,y):
# [...]
pg.draw.rect(self.image,ai_set.brick_inside_color, [0, 0, self.width, self.height])
The coordinates of brick2 are wrong:
def main():
brick = Brick(ai_set, 0, ai_set.data_h)
brick2 = Brick(ai_set, ai_set.action_w, ai_set.data_h)
while True:
gf_check_events()
gf_update(ai_set,screen, data_s, action_s, brick, brick2)

Updating multiple items in a class, not just one

In the update section of this code, only the first bat that gets made is affected by update() in class Bat()...
Outside of main loop:
START_BAT_COUNT = 30
BAT_IMAGE_PATH = os.path.join( 'Sprites', 'Bat_enemy', 'Bat-1.png' )
bat_image = pygame.image.load(BAT_IMAGE_PATH).convert_alpha()
bat_image = pygame.transform.scale(bat_image, (80, 70))
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y, bat_image, bat_health):
pygame.sprite.Sprite.__init__(self)
self.bat_health = bat_health
self.image = bat_image
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface(self.image)
self.rect.topleft = (bat_x, bat_y)
def update(self):
self.bat_health -= 1
if self.bat_health < 0:
new_bat.kill()
all_bats = pygame.sprite.Group()
for i in range(START_BAT_COUNT):
bat_x = (random.randint(0, 600))
bat_y = (random.randint(0, 600))
bat_health = 5
new_bat = Bat(bat_x, bat_y, bat_image, bat_health)
all_bats.add(new_bat)
Inside main loop...
all_bats.update()
all_bats.draw(display)
Any help would be great! Thanks.
In Method Objects you must use the instance parameter (self) instead of an object instance in the global namespace. This means you have to call self.kill() instead of new_bat.kill():
class Bat(pygame.sprite.Sprite):
# [...]
def update(self):
self.bat_health -= 1
if self.bat_health < 0:
self.kill()

Pygame returning locking error during blitting

I am trying to make a 2D game in pygame, and have a camera class that has a surface attribute, and each time the cameras get updated their surface is updated. All graphics are blitted to game.worldSurface, and then the main camera take a picture of that and blits it to the display surface. However, when using other cameras, i am unable to blit to the worldsurface and get a locking error. i have tried .unlock(). what could be causing this?
import pygame
import pickle
class Tileset:
def __init__(self, location):
pass
class Tilemap:
def __init__(self):
pass
class Collisionmap(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
class Player(pygame.sprite.Sprite):
def __init__(self, spritesheet):
super().__init__()
self.spritesheet = pygame.image.load(spritesheet)
self.x = 0
self.y = 0
def draw(self, surface):
surface.blit(self.spritesheet, (self.x, self.y))
class Mob(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
class Camera:
def __init__(self):
self.x = 0
self.y = 0
self.width = 100
self.height = 100
self.surface = pygame.Surface((self.width, self.height))
def moveToSprite(self, sprite):
self.x = sprite.rect.centerx - WIDTH // 2
self.y = sprite.rect.centery - HEIGHT // 2
def update(self, world):
self.surface = world.subsurface((self.x, self.y, self.width, self.height))
class Level:
def __init__(self, terrarin, collision, mobs):
self.terrain = terrain
self.collision = collision
self.mobs = mobs
class Game:
def __init__(self):
pygame.init()
self.DISPLAYSURF = pygame.display.set_mode((0, 0))
self.mainCamera = Camera()
self.mainCamera.width = self.DISPLAYSURF.get_width()
self.mainCamera.height = self.DISPLAYSURF.get_height()
self.otherCameras = []
self.worldSurface = pygame.Surface((10000, 10000))
self.player = Player("marioSS.jpg")
self.otherCameras.append(Camera())
self.run()
def run(self):
while True:
for event in pygame.event.get():
pass
self.earlyUpdate()
self.update()
self.lateUpdate()
self.graphicsUpdate()
def update(self):
pass
def earlyUpdate(self):
pass
def lateUpdate(self):
pass
def graphicsUpdate(self):
for each in self.otherCameras:
each.update(self.worldSurface)
self.player.draw(self.worldSurface)
self.otherCameras[0].surface.unlock()
self.worldSurface.unlock()
self.worldSurface.blit(self.otherCameras[0].surface, (100, 100)) ##Error here
self.mainCamera.update(self.worldSurface)
self.DISPLAYSURF.blit(self.mainCamera.surface, (0, 0))
pygame.display.update()
x = Game()
Problem makes world.subsurface() in Camera.update()
It doesn't copy data from world to surface but it assigns access to original world. And later you have: camera.surface keeps access to world, and blit try to copy from camera.surface to world - so finally it tries to copy from world to world. And maybe it locks it.
But if in Camera.update() you use .copy()
self.surface = world.subsurface((self.x, self.y, self.width, self.height)).copy()
or blit it
self.surface.blit(world.subsurface((self.x, self.y, self.width, self.height)), (0,0))
then it works.
DOC: subsurface
subsurface(Rect) -> Surface
Returns a new Surface that shares its pixels with its new parent.

How do I use a .jpg or .png as a background picture?

I found a nice image of space that I'd like sitting in the background of this tiny game I'm working on and can't figure out what and where to write it. It needs to be placed behind all classes to make sure that it doesn't block the screen. I thought it might be in class Window, but I'm not sure. I am brand new to python so any help is much appreciated! This is the entire project so far.
import sys, logging, os, random, math, open_color, arcade
#check to make sure we are running the right version of Python
version = (3,7)
assert sys.version_info >= version, "This script requires at least Python {0}.{1}".format(version[0],version[1])
#turn on logging, in case we have to leave ourselves debugging messages
logging.basicConfig(format='[%(filename)s:%(lineno)d] %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
MARGIN = 30
SCREEN_TITLE = "Intergalactic slam"
NUM_ENEMIES = 5
STARTING_LOCATION = (400,100)
BULLET_DAMAGE = 10
ENEMY_HP = 10
HIT_SCORE = 10
KILL_SCORE = 100
PLAYER_HP = 100
class Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
'''
initializes the bullet
Parameters: position: (x,y) tuple
velocity: (dx, dy) tuple
damage: int (or float)
'''
super().__init__("PNG/laserPink3.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
'''
Moves the bullet
'''
self.center_x += self.dx
self.center_y += self.dy
class Enemy_Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
super().__init__("PNG/laserGreen1.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
self.center_x += self.dx
self.center_y += self.dy
class Player(arcade.Sprite):
def __init__(self):
super().__init__("PNG/shipYellow_manned.png", 0.5)
(self.center_x, self.center_y) = STARTING_LOCATION
self.hp = PLAYER_HP
class Enemy(arcade.Sprite):
def __init__(self, position):
'''
initializes an alien enemy
Parameter: position: (x,y) tuple
'''
super().__init__("PNG/shipGreen_manned.png", 0.5)
self.hp = ENEMY_HP
(self.center_x, self.center_y) = position
class Window(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
def setup(self):
'''
Set up enemies
'''
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
def update(self, delta_time):
self.bullet_list.update()
self.enemy_bullet_list.update()
if (not (self.win or self.lose)):
for e in self.enemy_list:
for b in self.bullet_list:
if (abs(b.center_x - e.center_x) <= e.width / 2 and abs(b.center_y - e.center_y) <= e.height / 2):
self.score += HIT_SCORE
e.hp -= b.damage
b.kill()
if (e.hp <= 0):
e.kill()
self.score += KILL_SCORE
if (len(self.enemy_list) == 0):
self.win = True
if (random.randint(1, 75) == 1):
self.enemy_bullet_list.append(Enemy_Bullet((e.center_x, e.center_y - 15), (0, -10), BULLET_DAMAGE))
for b in self.enemy_bullet_list:
if (abs(b.center_x - self.player.center_x) <= self.player.width / 2 and abs(b.center_y - self.player.center_y) <= self.player.height / 2):
self.player.hp -= b.damage
b.kill()
if (self.player.hp <= 0):
self.lose = True
def on_draw(self):
arcade.start_render()
arcade.draw_text(str(self.score), 20, SCREEN_HEIGHT - 40, open_color.white, 16)
arcade.draw_text("HP: {}".format(self.player.hp), 20, 40, open_color.white, 16)
if (self.player.hp > 0):
self.player.draw()
self.bullet_list.draw()
self.enemy_bullet_list.draw()
self.enemy_list.draw()
if (self.lose):
self.draw_game_loss()
elif (self.win):
self.draw_game_won()
def draw_game_loss(self):
arcade.draw_text(str("LOSER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def draw_game_won(self):
arcade.draw_text(str("WINNER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def on_mouse_motion(self, x, y, dx, dy):
'''
The player moves left and right with the mouse
'''
self.player.center_x = x
def on_mouse_press(self, x, y, button, modifiers):
if button == arcade.MOUSE_BUTTON_LEFT:
x = self.player.center_x
y = self.player.center_y + 15
bullet = Bullet((x,y),(0,10),BULLET_DAMAGE)
self.bullet_list.append(bullet)
def main():
window = Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
One way to do it would be to load the .jpg or .png as a texture, and draw that texture each frame, as big as the screen is (or bigger!).
I haven't tested this, but as an example, loading the texture could be done in Window.__init__, like so (reference):
self.background = arcade.load_texture('PNG/background.png')
And then in on_draw, just after you call start_render, you would draw it (reference), passing the required center coordinates, as well as width and height:
self.background.draw(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH, SCREEN_HEIGHT)
The reason it needs to be the first thing is because everything is drawn back-to-front, like you would do in a painting.
If the image is not the exact same size as your screen/window, your background will probably be stretched/squished. If that's not what you want, the easiest fix would be to change the image so that it's the right size.
Yes, you should be able to add it to class window...
You could do something like this to add it:
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
self.background = None
def setup(self):
'''
Set up enemies
'''
self.background = arcade.load_texture("images/background.jpg")
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)

Categories