3

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)
ScalaBoy
  • 3,254
  • 13
  • 46
  • 84

2 Answers2

2

This could be accomplished fairly easily, by just blitting each Worker's emotion image, at a certain location, in relation to the Worker's rect.x, and rect.y co-ordinates.

Unfortunately, I cannot test the code example below, because your code uses lots of images which I don't have. One problem I do see which you will need to fix before trying to implement this code, is that the Background object you are initializing is being added to all_sprites, so you may consider changing all_sprites to all_workers, and maybe add Background to a different group.

You will also need to initialize offset_x, and offset_y to values which work for you. The values used below will just move the image to the bottom left corner of the worker.

Here is the example code:

for worker in all_workers:
   offset_x = 0
   offset_y = worker.rect.height
   screen.blit(worker.emo_images[worker.emo_state], (worker.rect.x+offset_x, worker.rect.y+offset_y))

I hope this answer helps you! Please let me know if this works for you, and if you have any further questions, feel free to leave a comment below.

Micheal O'Dwyer
  • 1,237
  • 1
  • 16
  • 26
2

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()
skrx
  • 19,980
  • 5
  • 34
  • 48