Animated objects launch at a precise time with pytmx, pygame - python

I am in troubles, I am trying to make doors opening in my game.
I am using pygame, and pytmx, I have built a Level made with Rooms, and in each Room I have a Renderer using pytmx, what I want to achieve is for exemple on level 0 the player has to move to a door to open it and enter level 1, my goal is to launch and draw the animation when player hit the door.
I tried to make my Door object (his parent is pygame Sprite class) update, it has kind of worked but of course the object was modified and displayed, but the tiled map object also and was staying in the back of the map, so I tried to just not initialize my objects in the tile map but if so the door doesn't bit at all. Then I tried to modify the tile object image and to reload my rendering but still doesn't work, does anyone of you has any idea ?
Here is some parts of the code to let you have better understanding
"""
This is a test of using the pytmx library with Tiled.
"""
import pygame
import pytmx
class Renderer(object):
"""
This object renders tile maps from Tiled
"""
def __init__(self, filename):
tm = pytmx.load_pygame(filename, pixelalpha=True)
self.object_images = []
self.size = tm.width * tm.tilewidth, tm.height * tm.tileheight
self.tmx_data = tm
self.map_surface = self.make_map()
self.current_frame = 0
def render(self, surface):
tw = self.tmx_data.tilewidth
th = self.tmx_data.tileheight
print(self.tmx_data.tile_properties.items())
if self.tmx_data.background_color:
surface.fill(self.tmx_data.background_color)
for layer in self.tmx_data.layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, image in layer.tiles():
if image:
surface.blit(image.convert_alpha() , (x * tw, y * th))
elif isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:
if object.image:
surface.blit(object.image.convert_alpha() , (object.x, object.y))
elif isinstance(layer, pytmx.TiledImageLayer):
if image:
surface.blit(image , (0, 0))
def make_map(self):
temp_surface = pygame.Surface(self.size)
self.render(temp_surface)
return temp_surface
def reload(self):
self.map_surface = self.make_map()
def update_object_image(self, object_id, surface):
pass
def get_layer(self, layer_name):
return self.tmx_data.get_layer_by_name(layer_name)
class Room:
def __init__(self, room_image, level_options):
self.renderer = Renderer(room_image)
self.surface = self.renderer.map_surface
self.rect = self.surface.get_rect()
self.level_options = level_options
self.obstacles = pygame.sprite.Group()
self.doors = pygame.sprite.Group()
self.load_obstacles()
self.load_doors()
def load_obstacles(self):
obstacles = self.level_options['obstacle_layers']
for obstacle in obstacles:
try:
items = self.renderer.get_layer(obstacle)
for x, y, image in items.tiles():
self.obstacles.add(Obstacle(x, y, image, SPRITE_SIZE))
except ValueError:
return
def load_doors(self):
doors = self.renderer.get_layer(self.level_options['door_layer'])
if isinstance(doors, pytmx.TiledTileLayer):
for x, y, image in doors.tiles():
if image:
self.doors.add(Door(self, x, y, image, SPRITE_SIZE, self.level_options['open_door_actions']))
elif isinstance(doors, pytmx.TiledObjectGroup):
for object in doors:
self.doors.add(Door(self, object.x, object.y, object.image, SPRITE_SIZE, self.level_options['open_door_actions'], object))
def update_doors(self):
for door in self.doors:
if door.object:
door.update()
#self.renderer.update_object_image(door.object.id, door.image)
class Wall(pygame.sprite.Sprite):
def __init__(self, x, y, image, sprite_size):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
self.image = image
class Obstacle(pygame.sprite.Sprite):
def __init__(self, x, y, image, sprite_size):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
self.image = image
class Door(pygame.sprite.Sprite):
def __init__(self, parent, x, y, image, sprite_size, open_actions, object = None):
self.parent = parent
pygame.sprite.Sprite.__init__(self)
self.object = object
if self.object:
self.rect = pygame.Rect(x ,y, self.object.width, self.object.height)
self.current_index = 0
self.load_animations()
else:
self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
self.image = image
self.open_actions = open_actions
self.last_updated = 0
self.is_open = False
self.is_activated = False
def load_animations(self):
self.animations = []
filename = os.path.join(os.getcwd(), IMAGES_FOLDER, 'maps', 'animations', self.object.animated_image)
spritesheet = Spritesheet(filename, self.object.width, self.object.height)
for i in range(spritesheet.data['columns']):
self.animations.append(spritesheet.parse_sprite(i))
self.image = self.animations[self.current_index]
def open_animation(self):
if self.is_open:
return
if not self.object or not self.is_activated or not self.animations:
return
self.image = self.animations[self.current_index]
if self.current_index == len(self.animations) - 1:
self.is_open = True
def update(self):
if self.is_open:
if self.object:
self.image = self.animations[-1]
if self.is_activated:
if self.object:
now = pygame.time.get_ticks()
if now - self.last_updated > 200:
self.last_updated = now
self.current_index = (self.current_index + 1) % len(self.animations)
self.image = self.animations[self.current_index]
if not self.is_open:
self.image = self.animations[0]
def check_doors_state(self, player):
if 'any' in self.open_actions:
self.is_activated = True
for action in player.actions:
if action in self.open_actions:
self.is_activated = True
So right now what I am trying to do is to use an animated tile object and launch the animation from tmx data, but I can't even understand how to launch the animation at the first rendering.
Thank you in advance for you answers.

So I found a solution, I am not trying to change and reload the object tiles, I simply made my game sprites in groups, modified my generating and rendering of tiles.
Classes modified :
class Room:
def __init__(self, level, room_image, level_options):
self.level = level
self.renderer = Renderer(room_image)
self.surface = self.renderer.map_surface
self.tmx_data = self.renderer.tmx_data
self.rect = self.surface.get_rect()
self.level_options = level_options
self.walls = pygame.sprite.Group()
self.doors = pygame.sprite.Group()
#self.load_walls()
#self.load_doors()
def load_walls(self):
walls = self.renderer.get_layer(self.level_options['wall_layer'])
if isinstance(walls, pytmx.TiledObjectGroup):
for object in walls:
self.walls.add(Wall(object.x, object.y, object, SPRITE_SIZE))
def load_doors(self):
doors = self.renderer.get_layer(self.level_options['door_layer'])
if isinstance(doors, pytmx.TiledTileLayer):
for x, y, image in doors.tiles():
if image:
self.doors.add(Door(self, x, y, image, SPRITE_SIZE, self.level_options['open_door_actions']))
elif isinstance(doors, pytmx.TiledObjectGroup):
for object in doors:
self.doors.add(Door(self, object.x, object.y, object.image, SPRITE_SIZE, self.level_options['open_door_actions'], object))
def update(self):
self.doors.update()
def draw(self, display, camera = None):
self.doors.draw(self.surface)
if not camera:
display.blit(self.surface, self.rect)
else:
display.blit(self.surface, (self.rect.x - camera.offset.x, self.rect.y - camera.offset.y))
def get_opening_actions(self):
return self.level_options['open_door_actions']
class Wall(pygame.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.walls
pygame.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = game.wall_img
self.rect = self.image.get_rect()
self.x = x
self.y = y
self.rect.x = x * SPRITE_SIZE
self.rect.y = y * SPRITE_SIZE
class Obstacle(pygame.sprite.Sprite):
def __init__(self, game, x, y, w, h):
self.groups = game.walls
pygame.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.rect = pygame.Rect(x, y, w, h)
self.hit_rect = self.rect
self.x = x
self.y = y
self.rect.x = x
self.rect.y = y
class Door(pygame.sprite.Sprite):
def __init__(self, game, x, y, w, h, open_actions, animated_image):
self.groups = game.all_sprites, game.doors
pygame.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.rect = pygame.Rect(x ,y, w, h)
self.current_index = 0
self.animated_image = animated_image
self.load_animations()
self.open_actions = open_actions
self.last_updated = 0
self.is_open = False
self.is_activated = False
def load_animations(self):
self.animations = []
filename = os.path.join(os.getcwd(), IMAGES_FOLDER, 'maps', 'animations', self.animated_image)
spritesheet = Spritesheet(filename, self.rect.width, self.rect.height)
for i in range(spritesheet.data['columns']):
self.animations.append(spritesheet.parse_sprite(i))
self.image = self.animations[self.current_index]
def open_animation(self):
if self.is_open:
return
if not self.is_activated or not self.animations:
return
now = pygame.time.get_ticks()
if now - self.last_updated > 15:
self.last_updated = now
self.current_index = (self.current_index + 1) % len(self.animations)
self.image = self.animations[self.current_index]
if self.current_index == len(self.animations) - 1:
self.current_index = -1
self.is_open = True
def update(self):
if not self.is_open:
self.image = self.animations[0]
if self.is_open:
self.image = self.animations[-1]
def check_doors_state(self, player):
if 'any' in self.open_actions:
self.is_activated = True
for action in player.actions:
if action in self.open_actions:
self.is_activated = True
Sprites groups generating, updating and rendering added (in my game class) :
def new(self, level = 0):
# initialize all variables and do all the setup for a new game
self.all_sprites = pygame.sprite.Group()
self.walls = pygame.sprite.Group()
self.doors = pygame.sprite.Group()
self.mobs = pygame.sprite.Group()
## LOAD LEVEL
self.level = Level(level)
self.room = self.level.current_room
for tile_object in self.room.tmx_data.objects:
if tile_object.name == 'player':
self.player = Player(self, tile_object.x, tile_object.y)
if tile_object.name == 'obstacle':
Obstacle(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height)
if tile_object.name == 'wall':
Wall(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height)
if tile_object.name == 'door':
Door(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height, self.room.get_opening_actions(), tile_object.animated_image)
def update(self):
self.all_sprites.update()
def draw(self):
self.window.fill((0, 0, 0))
## DISPLAY MAP
self.level.draw_room(self.window, self.camera)
## DISPLAY SPRITES
for sprite in self.all_sprites:
self.window.blit(sprite.image, (sprite.rect.x - self.camera.offset.x, sprite.rect.y - self.camera.offset.y))
## REFRESH SCREEN
pygame.display.flip()
And here is the result (I am working on better hitbox for the player ^^)
door opening when player getting close to it

Related

Pygame sprite group won't draw because of error: Source objects must be a suface

im having a problem with my 2D Platformer game and when trying to display sprite group, an error message appears saying: Pygame: TypeError: Source objects must be a surface, The problem seems to be around the coins sprite group when trying to draw it to the self.display_surface in my code here.
I would highly appreciate any help.
Here is my code for my level.py:
import pygame
from support import import_csv_layout, import_cut_graphics
from settings import tile_size
from tiles import Tile, StaticTile, Crate, AnimatedTile
class Level:
def __init__(self, level_data, surface):
# general setup
self.display_surface = surface
self.world_shift = -5
# terrain setup
terrain_layout = import_csv_layout(level_data['terrain'])
self.terrain_sprites = self.create_tile_group(terrain_layout, 'terrain')
# grass setup
grass_layout = import_csv_layout(level_data['grass'])
self.grass_sprites = self.create_tile_group(grass_layout, 'grass')
# crates
crate_layout = import_csv_layout(level_data['crates'])
self.crate_sprites = self.create_tile_group(crate_layout, 'crates')
# coins
coins_layout = import_csv_layout(level_data['coins'])
self.coin_sprites = self.create_tile_group(coins_layout, 'coins')
def create_tile_group(self, layout, type):
sprite_group = pygame.sprite.Group()
for row_index, row in enumerate(layout):
for col_index, val in enumerate(row):
if val != '-1':
x = col_index * tile_size
y = row_index * tile_size
if type == 'terrain':
terrain_tile_list = import_cut_graphics('../gfx/terrain/terrain_tiles.png')
tile_surface = terrain_tile_list[int(val)]
sprite = StaticTile(tile_size, x, y, tile_surface)
if type == 'grass':
grass_tile_list = import_cut_graphics('../gfx/decoration/grass/grass.png')
tile_surface = grass_tile_list[int(val)]
sprite = StaticTile(tile_size, x, y, tile_surface)
if type == 'crates':
sprite = Crate(tile_size, x, y)
if type == 'coins':
sprite = AnimatedTile(tile_size, x, y, '../gfx/coins/gold')
sprite_group.add(sprite)
return sprite_group
def run(self):
# run the entire game / level
# terrain
self.terrain_sprites.update(self.world_shift)
self.terrain_sprites.draw(self.display_surface)
# grass
self.grass_sprites.update(self.world_shift)
self.grass_sprites.draw(self.display_surface)
# crate
self.crate_sprites.update(self.world_shift)
self.crate_sprites.draw(self.display_surface)
# coins
self.coin_sprites.update(self.world_shift)
self.coin_sprites.draw(self.display_surface)
And Here is the tile.py file: AnimatedTile and SaticTile class:
class AnimatedTile(Tile):
def __init__(self, size, x, y, path):
super().__init__(size, x, y)
self.frames = import_folder(path)
self.frame_index = 0
self.image = self.frames[self.frame_index]
class StaticTile(Tile):
def __init__(self, size, x, y, surface):
super().__init__(size, x, y)
self.image = surface
Also import folder has a surface variable, so i can't tell if its in here:
surface_list = []
for _,__, image_files in walk(path):
for image in image_files:
full_path = path + '/' + image
image_surf = pygame.image.load(full_path).convert_alpha()
surface_list.append(image)
return surface_list
Crate:
class Crate(StaticTile):
def __init__(self, size, x, y):
super().__init__(size, x, y, pygame.image.load('../gfx/terrain/crate.png').convert_alpha())
offset_y = y + size
self.rect = self.image.get_rect(bottomleft = (x,offset_y))
The bug is in the code that creates the surface_list. You have to append image_surf to the surface_list, but not image:
surface_list.append(image)
surface_list.append(image_surf)

Black BG during render under tiles, looks fine in Tiled map editor

I started learning Pygame and Tiled map editor. I have the following test:
Example
It looks fine in the editor, the objects that have the black BG are currently on layer_4 (but they do it regardless of layer count).
I already tried using convert_alpha on it, ticking and unticking the transparency color in Tiled when loading in the tile map.
This is the original picture:
Full picture
This is the part where I load in the picture:
def import_cut_graphics(path):
surface = pygame.image.load(path).convert_alpha()
tile_num_x = int(surface.get_size()[0] / TILESIZE)
tile_num_y = int(surface.get_size()[1] / TILESIZE)
cut_tiles = []
for row in range(tile_num_y):
for col in range(tile_num_x):
x = col * TILESIZE
y = row * TILESIZE
new_surface = pygame.Surface((TILESIZE, TILESIZE))
new_surface.blit(surface, (0, 0), pygame.Rect(x, y, TILESIZE, TILESIZE))
cut_tiles.append(new_surface)
return cut_tiles
And this is where I work with it:
class Level:
def __init__(self, level_data, surface):
# general setup
self.all_layers = import_cut_graphics("img\ProjectUtumno_full.png")
self.display_surface = surface
self.world_shift = 0
# terrain setup
layer1_layout = import_csv_layout(level_data["layer_1"])
self.layer1_sprites = self.create_tile_group(layer1_layout, 'layer_1')
# grass setup
layer2_layout = import_csv_layout(level_data["layer_2"])
self.layer2_sprites = self.create_tile_group(layer2_layout, "layer_2")
# crates
layer3_layout = import_csv_layout(level_data["layer_3"])
self.layer3_sprites = self.create_tile_group(layer3_layout, "layer_3")
# layer 4
layer4_layout = import_csv_layout(level_data["layer_4"])
self.layer4_sprites = self.create_tile_group(layer4_layout, "layer_4")
def create_tile_group(self, layout, type):
sprite_group = pygame.sprite.Group()
for row_index, row in enumerate(layout):
for col_index, val in enumerate(row):
if val != '-1':
x = col_index * TILESIZE
y = row_index * TILESIZE
if type == 'layer_1':
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_2" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_3" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_4" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
return sprite_group
def run(self):
# run the entire game / level
self.camera()
# layer1
self.layer1_sprites.update(self.world_shift)
self.layer1_sprites.draw(self.display_surface)
# layer2
self.layer2_sprites.update(self.world_shift)
self.layer2_sprites.draw(self.display_surface)
# layer3
self.layer3_sprites.update(self.world_shift)
self.layer3_sprites.draw(self.display_surface)
# layer4
self.layer4_sprites.update(self.world_shift)
self.layer4_sprites.draw(self.display_surface)
This is my main game loop:
class Game:
def __init__(self):
pygame.init()
pygame.font.init()
self.screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
self.clock = pygame.time.Clock()
self.screen_name = pygame.display.set_caption("New Game")
self.running = True
self.playing = False
self.level = Level(level_0, self.screen)
self.character_spritesheet = Spritesheet('img\MainCharacter\B_witch_idle.png')
def main(self):
# game loop
while self.running:
self.events()
self.update()
self.draw()
self.running = False
def draw(self):
self.screen.fill(BLACK)
self.level.run()
self.all_sprites.draw(self.screen)
self.clock.tick(FPS)
pygame.display.update()
I did try commenting the screen fill black and got the same result. I tried out set_colorkey with white/black colors but didn't work out.
I add my tile create class too:
class Tile(pygame.sprite.Sprite):
def __init__(self, size, x, y):
super().__init__()
self.image = pygame.Surface((size, size))
self.rect = self.image.get_rect(topleft=(x, y))
def update(self, shift):
self.rect.x += shift
class StaticTile(Tile):
def __init__(self, size, x, y, surface):
super().__init__(size, x, y)
self.image = surface
Thanks in advance if you can figure out why i have this problem.
So for anyone who's having this problem, I managed to solve it by this:
Inside my import_cut_graphics methode I added a set_colorkey(BLACK), turns out the problem occured when I was originally cutting the picture, therefore it didnt matter how much I change it at the latter phases since the problem was in an earlier stage.
def import_cut_graphics(path):
surface = pygame.image.load(path).convert_alpha()
tile_num_x = int(surface.get_size()[0] / TILESIZE)
tile_num_y = int(surface.get_size()[1] / TILESIZE)
cut_tiles = []
for row in range(tile_num_y):
for col in range(tile_num_x):
x = col * TILESIZE
y = row * TILESIZE
new_surface = pygame.Surface((TILESIZE, TILESIZE))
new_surface.blit(surface, (0, 0), pygame.Rect(x, y, TILESIZE, TILESIZE))
new_surface.set_colorkey(BLACK)
cut_tiles.append(new_surface)
return cut_tiles

How do I make my player image bigger on collision while keeping its proportions?

I am making a game in pygame where you now swim around and eat small squares, with an animation to the jellyfish. I've made it so you get bigger when eating, but when adding a number between 1-9 to the scale the image sort of gets wider than I want it to become. When I have the scale go up by 10 or more when eating this problem does not occur as badly.
This is the code for the jellyfish/player:
import pygame, math, time
pt = time.time()
speed = 40
pygame.display.set_mode((800, 500))
img0 = pygame.transform.scale(pygame.image.load("assets/glow0.png"), (30,30)).convert_alpha()
img1 = pygame.transform.scale(pygame.image.load("assets/glow1.png"), (30,30)).convert_alpha()
img2 = pygame.transform.scale(pygame.image.load("assets/glow2.png"), (30,30)).convert_alpha()
img3 = pygame.transform.scale(pygame.image.load("assets/glow3.png"), (30,30)).convert_alpha()
img4 = pygame.transform.scale(pygame.image.load("assets/glow4.png"), (30,30)).convert_alpha()
img5 = pygame.transform.scale(pygame.image.load("assets/glow5.png"), (30,30)).convert_alpha()
img6 = pygame.transform.scale(pygame.image.load("assets/glow6.png"), (30,30)).convert_alpha()
img7 = pygame.transform.scale(pygame.image.load("assets/glow7.png"), (30,30)).convert_alpha()
img8 = pygame.transform.scale(pygame.image.load("assets/glow8.png"), (30,30)).convert_alpha()
class Glow():
rot = 0
rotp = 1
xsp = 0
ysp = 0
def __init__(self, x, y, scale):
self.x, self.y = x, y
self.scale = scale
self.list = [img0, img1, img2, img3, img4, img5, img6, img7, img8]
self.current = 0
self.image = self.list[int(self.current)]
self.rect = self.image.get_rect(center = (x, y))
self.colRect = pygame.rect.Rect((0, 0), (self.rect.width/3, self.rect.height/3))
self.colRect.center = self.rect.center
def update(self, x, y, accex):
global pt, speed
now = time.time()
dt = now - pt
pt = now
self.rect = self.image.get_rect(center = (x, y))
if pygame.key.get_pressed()[pygame.K_UP] or accex:
# animation
self.current += dt*5
if self.current >= len(self.list):
self.current = 0
self.image = pygame.transform.rotate(self.list[int(self.current)], self.rot)
self.rect = self.image.get_rect(center = (x, y))
self.colRect.center = self.rect.center
# go in direction of rotation
self.rotr = math.radians(self.rot)
self.ysp = math.cos(self.rotr)
self.xsp = math.sin(self.rotr)
self.x -= self.xsp*dt*speed
self.y -= self.ysp*dt*speed
if not accex:
if pygame.key.get_pressed()[pygame.K_LEFT]:
self.rot += math.pi*dt*7
if pygame.key.get_pressed()[pygame.K_RIGHT]:
self.rot -= math.pi*dt*7
if accex:
speed += dt*10
def scaleup(self):
self.scale += 2
i = 0
for img in self.list:
self.list.remove(img)
img = pygame.transform.scale(img, (self.scale, self.scale))
self.list.insert(i, img)
i += 1
This is the main code in the game loop that has to do with this:
W, H = 800, 500
sc = pygame.display.set_mode((W, H))
score = 0
score_x, score_y = 10, 10
font = pygame.font.SysFont("calibri", 30)
score_text = font.render("0", True, (255,255,255))
score _rect = score_text.get_rect(topleft = (score_x, score_y))
if lvl0:
glow.update(glow.x, glow.y, accex)
sc.fill((0,0,0))
for food in Food.food_list:
sc.blit(food.image, (food.x, food.y))
if pygame.Rect.colliderect(glow.colRect, food.rect):
Food.food_list.remove(food)
glow.scaleup()
score += 3
score_text = font.render(str(score), True, (255,255,255))
score_rect = score_text.get_rect(topleft = (score_x, score_y))
sc.blit(glow.image, glow.rect)
sc.blit(label, label_rect)
pygame.display.update()
Pygame behaves weird when using the transformed image repetitively as in case of rotation...
I even have faced crashes due to it
So try using the the same image which is initially loaded as img0,img1,etc. and scale it to the desired size. As of now you were using the same scaled image again and again .
This might help
I know there is an already accepted answer, but here is how I do it:
class Wabbit:
# this store the main, unaltered image loaded from disk
_main_image = None
def __init__(self, pos, scale=1):
# if the class._main_image variable is not set
# we do this so we only have to load the image once
# when the first instance is created
if not self._main_image:
# load the image from disk
self._main_image = pygame.image.load('wabbit_alpha.png')
self.pos = Vector2(pos)
self.vel = Vector2()
self.scale = scale
# some variables to store the "previous" values
self._scale = None
self._image = None
#property
def size(self):
# returns the size of our image, as a Vector2
return Vector2(self.image.get_size())
#property
def image(self):
# this is where we look at our current scale
# compare it to our previous _scale
# and update our _image if the two arent the same
if self.scale != self._scale:
# update the previous _scale value
self._scale = self.scale
# get the size of the original image and scale it
# according to our scale value
size = Vector2(self._main_image.get_size()).elementwise() * self.scale
# set our _image to be the scaled version of the main, original image
self._image = pygame.transform.scale(self._main_image, size)
# return our scaled image
return self._image
def update(self, dt):
self.pos += self.vel * dt
def draw(self, surface):
surface.blit(self.image, self.pos-self.size/2)

Why is the tracer making another turtle in Python

So, I am making a tower defense game using Python's turtle module and I just started off. For some reason though, the .tracer at the very beginning is creating an extra turtle for me for some reason. I don't know why. I have 3 classes, and so far, I have only defined 4 sprites: Helper, Enemy, Enemy, Enemy. When I remove the tracer, the turtle suddenly disappears. I accessed my turtle list and started to delete them. That didn't work. Here is the code:
import turtle as t
import random as r
wn = t.Screen()
wn.tracer(0)
class Sprite():
def __init__(self, color, speed, shape, ento, x, y):
self.sprite = t.Turtle()
self.color = color
self.speed = speed
self.shape = shape
self.ento = ento
self.x = x
self.y = y
def render(self, pen):
pen.speed(0)
pen.color(self.color)
pen.shape(self.shape)
pen.up()
pen.goto(self.x, self.y)
class Helper(Sprite):
def __init__(self, color, speed, damage, shape, x, y):
self.sprite = Sprite(color, speed, shape, 'Helper', x, y)
self.helper = t.Turtle()
self.color = color
self.speed = speed
self.shape = shape
self.ento = 'Helper'
self.damage = damage
self.x = x
self.y = y
def fire(self):
pass
class Enemy(Sprite):
def __init__(self, color, speed, health, shape, x, y):
self.sprite = Sprite(color, speed, shape, 'Enemy', x, y)
self.enemy = t.Turtle()
self.color = color
self.speed = speed
self.shape = shape
self.ento = 'Enemy'
self.health = health
self.x = x
self.y = y
self.state = 'frozen'
self.tick = 0
def move(self):
if self.state == 'frozen':
self.state = 'moving'
self.tick = 0
self.enemy.forward(self.speed)
self.x = self.enemy.xcor()
self.y = self.enemy.ycor()
self.tick += 1
helpers = []
for i in range(1):
test = Helper('green', 1, 1, 'square', 0, 0)
enemies = []
for i in range(3):
enemies.append(Enemy('red', 1, 1, 'circle', r.randint(-100, 100), r.randint(-100, 100)))
print(wn._turtles)
while True:
wn.update()
test.render(test.helper)
for enemy in enemies:
enemy.render(enemy.enemy)
enemy.move()
Why is this not working. I work on a windows computer. I am not really sure if this applies to only me or to everyone.
Your object code is a disaster. Consider this logic:
class Sprite():
def __init__(self, color, speed, shape, ento, x, y):
self.sprite = t.Turtle()
#...
class Helper(Sprite):
def __init__(self, color, speed, damage, shape, x, y):
self.sprite = Sprite(color, speed, shape, 'Helper', x, y)
Helper is a Sprite but it doesn't call super(), and instead has a Sprite inside it where the turtle that Sprite creates would have been? I'm lost. I think we need to make the code object-oriented instead of simply object-based:
from turtle import Screen, Turtle
from random import randint
class Sprite(Turtle):
def __init__(self, color, pace, shape, ento, x, y):
super().__init__()
self.color(color)
self.shape(shape)
self.penup()
self.pace = pace
self.ento = ento
self.x = x
self.y = y
def render(self):
self.goto(self.x, self.y)
class Helper(Sprite):
def __init__(self, color, pace, damage, shape, x, y):
super().__init__(color, pace, shape, 'Helper', x, y)
self.damage = damage
def fire(self):
pass
class Enemy(Sprite):
def __init__(self, color, pace, health, shape, x, y):
super().__init__(color, pace, shape, 'Enemy', x, y)
self.health = health
self.state = 'frozen'
self.tick = 0
def move(self):
if self.state == 'frozen':
self.state = 'moving'
self.tick = 0
self.forward(self.pace)
self.x = self.xcor()
self.y = self.ycor()
self.tick += 1
screen = Screen()
screen.tracer(0)
helpers = []
for _ in range(1):
helpers.append(Helper('green', 1, 1, 'square', 0, 0))
enemies = []
for _ in range(3):
enemies.append(Enemy('red', 1, 1, 'circle', randint(-100, 100), randint(-100, 100)))
while True:
helpers[0].render()
for enemy in enemies:
enemy.render()
enemy.move()
screen.update()
This clean up of your code appears to also clean up your extra turtle issue. I renamed speed to be pace to keep it from being confused with turtle's own speed method.

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