Asked  7 Months ago    Answers:  5   Viewed   43 times

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?

 Answers

93

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.

Tuesday, June 1, 2021
 
Bharanikumar
answered 7 Months ago
20
  1. First of all you have a lot of white spaces between each line, it makes it hard to read the code.

  2. Yes, you could try using a Swing Timer, here is an example and another example and another example.

  3. You have an empty catch block which is not secure, at least do this:

    catch (IOException e){
        e.printStackTrace();
    }
    
  4. You're not placing your program on the Event Dispatch Thread (EDT) to solve it just change your main method as follows:

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                //Your constructor here
            }
        });
    }
    
  5. You're extending JFrame but not making use of the frame generated by it, and at the same time you're creating an instance of a JFrame, remove the extends JFrame in your code. Related reading: Java Swing using extends JFrame vs calling it inside of class

  6. Instead of calling frm1.setSize(400, 400); override the Painel1's getPreferredSize() method to return a new Dimension of 400, 400 and call frm1.pack()

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }
    
  7. The animation processing is too fast!

    It's not the animation processing too fast, but the for loop prevents the GUI to be painted before it ends, that's why you're only seeing the last sprite being painted.


With all the above points in mind, you now can have your code as follows, which includes the use of a Swing Timer and the above recommendations already included:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Sprites {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frm1 = new JFrame();
                Painel1 pn1 = new Painel1();
                frm1.getContentPane().add(pn1);

                frm1.pack();
                frm1.setVisible(true);
                frm1.setLocationRelativeTo(null);
                frm1.setResizable(false);
                frm1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }
}

class Painel1 extends JPanel {

    int[][] spriteSheetCoords = { { 8, 10, 119, 129 }, { 138, 10, 118, 130 }, { 267, 10, 118, 132 },
            { 402, 11, 113, 132 }, { 538, 12, 106, 134 }, { 671, 13, 103, 133 }, { 804, 12, 102, 132 },
            { 23, 161, 100, 134 }, { 157, 162, 96, 134 }, { 287, 159, 95, 135 }, { 418, 158, 95, 133 },
            { 545, 159, 99, 133 }, { 673, 159, 102, 134 }, { 798, 158, 108, 130 }, { 9, 309, 116, 126 },
            { 137, 309, 118, 127 }, { 274, 310, 110, 128 }, { 412, 311, 102, 129 }, { 541, 312, 103, 130 },
            { 671, 312, 104, 131 }, { 806, 312, 98, 132 }, { 29, 463, 94, 135 }, { 155, 462, 98, 135 },
            { 279, 461, 104, 135 }, { 409, 461, 106, 135 }, { 536, 461, 109, 135 }, { 662, 461, 112, 133 } };

    int i = 0;
    BufferedImage img;

    private ActionListener actionListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            i++;
            if (i == spriteSheetCoords.length) {
                i = 0;
            }
            revalidate();
            repaint();
        }
    };

    public Painel1() {
        Timer timer = new Timer(50, actionListener);
        timer.setInitialDelay(0);
        timer.start();
        setBackground(Color.yellow);
        try {
            img = ImageIO.read(new File("/home/jesus/Pictures/tokyo.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        Image subSprite;
        super.paintComponent(g);
        subSprite = img.getSubimage(spriteSheetCoords[i][0], spriteSheetCoords[i][1], spriteSheetCoords[i][2], spriteSheetCoords[i][3]);
        g.drawImage(subSprite, 140, 120, null);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }
}

enter image description here

As you can see the Timer has a delay of 50ms to make the transition of the sprites smoother, you can adjust it as you please.

Wednesday, July 7, 2021
 
Teno
answered 5 Months ago
41

self.image is the loaded image, where you want to change specific regions by a certain color and self.mask is a mask which defines the regions.

And you create an image masked, which contains the regions which are specified in mask tinted in a specific color.

So all you've to do is to .blit the tinted mask (masked) on the image without any special_flags set:

self.image.blit(self.masked, (0, 0))

See the example, where the red rectangle is changed to a blue rectangle:

repl.it/@Rabbid76/PyGame-ChangeColorOfSurfaceArea


Minimal example: repl.it/@Rabbid76/PyGame-ChangeColorOfSurfaceArea-3

Sprite:

Mask:

import pygame

def changColor(image, maskImage, newColor):
    colouredImage = pygame.Surface(image.get_size())
    colouredImage.fill(newColor)
    
    masked = maskImage.copy()
    masked.set_colorkey((0, 0, 0))
    masked.blit(colouredImage, (0, 0), None, pygame.BLEND_RGBA_MULT)

    finalImage = image.copy()
    finalImage.blit(masked, (0, 0), None)

    return finalImage

pygame.init()
window = pygame.display.set_mode((404, 84))

image = pygame.image.load('avatar64.png').convert_alpha()
maskImage = pygame.image.load('avatar64mask.png').convert_alpha()

colors = []
for hue in range (0, 360, 60):
    colors.append(pygame.Color(0))
    colors[-1].hsla = (hue, 100, 50, 100)

images = [changColor(image, maskImage, c) for c in colors]

clock = pygame.time.Clock()
nextColorTime = 0
run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    window.fill((255, 255, 255))
    for i, image in enumerate(images):
        window.blit(image, (10 + i * 64, 10))
    pygame.display.flip()

pygame.quit()
exit()
Saturday, July 31, 2021
 
Connor Johnson
answered 4 Months ago
70

Straightforwardly, you can't:

The Group does not keep sprites in any order, so the draw order is arbitrary.

Use an OrderedUpdates group instead:

This class derives from pygame.sprite.RenderUpdates - Group class that tracks dirty updates. It maintains the order in which the Sprites were added to the Group for rendering. This makes adding and removing Sprites from the Group a little slower than regular Groups.

Alternatively, you can keep different 'layers' of sprites in different groups, keeping the order of groups correct.

Thursday, November 11, 2021
 
HamidR
answered 3 Weeks ago
65

Here is a 'working example' (still with many problems). The underlying problem is that a component can only appear in a single container at a time. To have 36 animations, there need to be 36 Ash objects.

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class Main extends Component{
    public static void main(String[] args) {
        Ash[] ash = new Ash[36];
        JFrame f = new JFrame("Game sample");

        JPanel panel1 = new JPanel(new GridLayout(6,6,6,6));
        JPanel[] p1 = new JPanel[36];

        for(int i = 0;i < 36;i++){
          p1[i] = new JPanel(new BorderLayout());
          ash[i] = new Ash();
          p1[i].add(ash[i]);
          panel1.add(p1[i]);
        }
        f.add(panel1,BorderLayout.CENTER);
        f.setSize(500,500);
        f.setVisible(true);
        long start, trigger = 0, delay = 1000 / 8;
        while(true) {
            start = System.currentTimeMillis();
            if(start > trigger) {
                trigger = start + delay;
                for (int ii=0; ii<ash.length; ii++) {
                    ash[ii].repaint();
                }
            }
        }
    }
}

class Ash extends JPanel{

    BufferedImage sprite[] = {
        new BufferedImage(25,100,BufferedImage.TYPE_INT_RGB),
        new BufferedImage(55,100,BufferedImage.TYPE_INT_RGB),
        new BufferedImage(75,100,BufferedImage.TYPE_INT_RGB),
        new BufferedImage(100,100,BufferedImage.TYPE_INT_RGB)
    };
    int step = 0, start = 0;

    int count = 0;


    public Ash() {
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D)g;
        g2d.drawImage(sprite[count++%4],0,10,null);
        if(step == 96) {
            step = 0;
        } else {
            step += 32;
        }
    }
}
Monday, November 22, 2021
 
newbie
answered 2 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share