Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to put an additional image or rectangle on the bottom of Sprite?

I need to add an icon on the bottom of the Sprite worker and then change this icon randomly at each iteration. Please notice that the Sprite worker has 2 states: RUNNING and IDLE. In each of these states, the worker has a specific image. What I need now is to put an additional small image on the bottom of worker that will specify emotional state: HAPPY or ANGRY.

In the class Worker I create the array emo_images and also specify the variable emo_state. This variable denotes an emotional state of the worker: happy or angry. Each emotional state has its image stored in emotional_images.

In the code I randomly generate the variable state_indicator. If it's greater than 9, then the emotional state of the worker is changed to ANGRY. Otherwise, it's happy.

   state_indicator = random.randint(0,10)
    if state_indicator > 9:
        print(state_indicator)
        self.alert_notif_worker()

    def alert_notif_worker(self):
        self.emo_state = Worker.ANGRY

However I don't not know how to put the emotional image on the bottom of the worker image, because I don't want to replace the worker image (IDLE, RUNNING). I only need to add another image on the bottom and this additional image should move together with the worker.

If it's very difficult to do, then it would be also fine to have rectangles of two colours: red and green, instead of images, in order to indicate emotional states.

Complete code:

import sys
import pygame, random
from pygame.math import Vector2
from scipy.optimize import minimize
import math


WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
BLACK = (0, 0 ,0)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)


SCREENWIDTH=1000
SCREENHEIGHT=578
# Create point vectors for the corners.
corners = [
    Vector2(0, 0), Vector2(SCREENWIDTH, 0),
    Vector2(SCREENWIDTH, SCREENHEIGHT), Vector2(0, SCREENHEIGHT)
    ]

ABS_PATH = "/Users/sb/Desktop/"
IMG_BACKGROUND = ABS_PATH + "images/background.jpg"
IMG_WORKER_RUNNING = ABS_PATH + "images/workers/worker_1.png"
IMG_WORKER_IDLE = ABS_PATH + "images/workers/worker_2.png"
IMG_WORKER_ACCIDENT = ABS_PATH + "images/workers/accident.png"
IMG_WORKER_HAPPY = ABS_PATH + "images/workers/happy.png"
IMG_WORKER_ANGRY = ABS_PATH + "images/workers/angry.png"


class Background(pygame.sprite.Sprite):
    def __init__(self, image_file, location, *groups):
        # we set a _layer attribute before adding this sprite to the sprite groups
        # we want the background to be actually in the back
        self._layer = -1
        pygame.sprite.Sprite.__init__(self, groups)
        # let's resize the background image now and only once
        self.image = pygame.transform.scale(pygame.image.load(image_file).convert(), (SCREENWIDTH, SCREENHEIGHT))
        self.rect = self.image.get_rect(topleft=location)


class Worker(pygame.sprite.Sprite):

    RUNNING = 0
    IDLE = 1
    HAPPY = 0
    ANGRY = 1
    IMAGE_CACHE = {}

    def __init__(self, idw, image_running, image_idle, image_happy, image_angry, location, *groups):

        self.font = pygame.font.SysFont('Arial', 20)

        # each state has it's own image
        self.images = {
            Worker.RUNNING: pygame.transform.scale(self.get_image(image_running), (45, 45)),
            Worker.IDLE: pygame.transform.scale(self.get_image(image_idle), (20, 45))
        }

        self.emo_images = {
            Worker.HAPPY: pygame.transform.scale(self.get_image(image_happy), (20, 20)),
            Worker.ANGRY: pygame.transform.scale(self.get_image(image_angry), (20, 20))
        }


        # we set a _layer attribute before adding this sprite to the sprite groups
        # we want the workers on top
        self._layer = 0
        pygame.sprite.Sprite.__init__(self, groups)

        self.idw = idw

        # let's keep track of the state and how long we are in this state already            
        self.state = Worker.IDLE
        self.emo_state = Worker.HAPPY
        self.ticks_in_state = 0

        self.image = self.images[self.state]
        self.rect = self.image.get_rect(topleft=location)

        self.direction = pygame.math.Vector2(0, 0)
        self.speed = random.randint(1, 3)
        self.set_random_direction()


    def set_random_direction(self):
        # random new direction or standing still
        vec = pygame.math.Vector2(random.randint(-100,100), random.randint(-100,100)) if random.randint(0, 5) > 1 else pygame.math.Vector2(0, 0)

        # check the new vector and decide if we are running or not
        length = vec.length()
        speed = sum(abs(int(v)) for v in vec.normalize() * self.speed) if length > 0 else 0

        if (length == 0 or speed == 0) and (self.state != Worker.ACCIDENT):
            new_state = Worker.IDLE
            self.direction = pygame.math.Vector2(0, 0)
        else:
            new_state = Worker.RUNNING
            self.direction = vec.normalize()

        self.ticks_in_state = 0
        self.state = new_state

        # use the right image for the current state
        self.image = self.images[self.state]
        #self.emo_image = self.emo_images[self.emo_state]


    def update(self, screen):
        self.ticks_in_state += 1

        # the longer we are in a certain state, the more likely is we change direction
        if random.randint(0, self.ticks_in_state) > 70:
            self.set_random_direction()

        # now let's multiply our direction with our speed and move the rect
        vec = [int(v) for v in self.direction * self.speed]
        self.rect.move_ip(*vec)

        # if we're going outside the screen, change direction
        if not screen.get_rect().contains(self.rect):
            self.direction = self.direction * -1


        send_alert = random.randint(0,10)
        if send_alert > 9:
            print(send_alert)
            self.alert_notif_worker()

        self.rect.clamp_ip(screen.get_rect())


    def alert_notif_worker(self):
        self.emo_state = Worker.ANGRY


    def get_image(self,key):
        if not key in Worker.IMAGE_CACHE:
            Worker.IMAGE_CACHE[key] = pygame.image.load(key)
        return Worker.IMAGE_CACHE[key]




pygame.init()

all_sprites = pygame.sprite.LayeredUpdates()
workers = pygame.sprite.Group()

screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("TEST")

# create multiple workers
idw = 1
for pos in ((30,30), (50, 400), (200, 100), (700, 200)):
    Worker(idw, IMG_WORKER_RUNNING, IMG_WORKER_IDLE, 
           IMG_WORKER_HAPPY, IMG_WORKER_ANGRY, 
           pos, all_sprites, workers)
    idw+=1

# and the background
Background(IMG_BACKGROUND, [0,0], all_sprites)

carryOn = True
clock = pygame.time.Clock()
while carryOn:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            carryOn = False
            pygame.display.quit()
            pygame.quit()
            quit()

    all_sprites.update(screen)
    all_sprites.draw(screen)

    pygame.display.flip()

    clock.tick(20)
like image 646
ScalaBoy Avatar asked Dec 17 '25 14:12

ScalaBoy


1 Answers

I'd either use Micheal O'Dwyer's solution and blit the icon images in a separate for loop or create an Icon sprite class which can be added as an attribute to the Worker class. Then you can just update the position of the icon sprite in the update method and swap the image when the workers state gets changed.

You need a LayeredUpdates group, so that the icon appears above the worker sprite.

import pygame as pg
from pygame.math import Vector2


pg.init()
WORKER_IMG = pg.Surface((30, 50))
WORKER_IMG.fill(pg.Color('dodgerblue1'))
ICON_HAPPY = pg.Surface((12, 12))
ICON_HAPPY.fill(pg.Color('yellow'))
ICON_ANGRY = pg.Surface((10, 10))
ICON_ANGRY.fill(pg.Color('red'))


class Worker(pg.sprite.Sprite):

    def __init__(self, pos, all_sprites):
        super().__init__()
        self._layer = 0
        self.image = WORKER_IMG
        self.rect = self.image.get_rect(center=pos)
        self.state = 'happy'
        self.emo_images = {'happy': ICON_HAPPY, 'angry': ICON_ANGRY}
        # Create an Icon instance pass the image, the position
        # and add it to the all_sprites group.
        self.icon = Icon(self.emo_images[self.state], self.rect.bottomright)
        self.icon.add(all_sprites)

    def update(self):
        # Update the position of the icon sprite.
        self.icon.rect.topleft = self.rect.bottomright

    def change_state(self):
        """Change the state from happy to angry and update the icon."""
        self.state = 'happy' if self.state == 'angry' else 'angry'
        # Swap the icon image.
        self.icon.image = self.emo_images[self.state]


class Icon(pg.sprite.Sprite):

    def __init__(self, image, pos):
        super().__init__()
        self._layer = 1
        self.image = image
        self.rect = self.image.get_rect(topleft=pos)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.LayeredUpdates()
    worker = Worker((50, 80), all_sprites)
    all_sprites.add(worker)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.MOUSEMOTION:
                worker.rect.center = event.pos
            elif event.type == pg.KEYDOWN:
                worker.change_state()

        all_sprites.update()
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)

        pg.display.flip()
        clock.tick(60)


if __name__ == '__main__':
    main()
    pg.quit()
like image 66
skrx Avatar answered Dec 20 '25 11:12

skrx



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!