Created Multiple Instances of the Same Image Using a Loop, How to Move Each Instance of the Image Independently

Created multiple instances of the same image using a loop, can I move each instance of the image independently?

I recommend to use pygame.sprite.Sprite and pygame.sprite.Group:

Create a class derived from pygame.sprite.Sprite:

class MySprite(pygame.sprite.Sprite):

def __init__(self, image, pos_x, pos_y):
super().__init__()
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = (pos_x, pos_y)

Load the image

image = pygame.transform.scale(pygame.image.load('pawn.png'), (100,100))

Create a list of sprites

imageList = [MySprite(image, x_pos*100, 100) for x_pos in range(0,8,1)]

and create a sprite group:

group = pygame.sprite.Group(imageList)

The sprites of a group can be drawn by .draw (screen is the surface created by pygame.display.set_mode()):

group.draw(screen)

The position of the sprite can be changed by changing the position of the .rect property (see pygame.Rect).

e.g.

imageList[0].rect = imageList[0].rect.move(move_x, move_y)

Of course, the movement can be done in a method of class MySprite:

e.g.

class MySprite(pygame.sprite.Sprite):

# [...]

def move(self, move_x, move_y):
self.rect = self.rect.move(move_x, move_y)
imageList[1].move(0, 100)

Shoot bullet from player

Create a pygame.sprite.Group for the bullets:

bullets = pygame.sprite.Group()

Add the bullets to the group:

class Player(pygame.sprite.Sprite):
# [...]

def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)

Update the bullets Group in the main application loop:

while running:
# [...]

player.update(pressed_keys)
enemies.update()
clouds.update()
bullets.update() # <-----
pygame.display.flip()

Pygame: item in pygame.sprite.Group() has no attribute rect

bullet has no attribute rect, but it has an attribute bullet_rect. I recommend to rename bullet_rect to rect. This way you can use pygame.sprite.Group.draw():

Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect for the position.

Furthermore use pygame.sprite.Sprite.kill():

The Sprite is removed from all the Groups that contain it. [...]

Example:

class Bullet(pygame.sprite.Sprite):
def __init__(self, ai_game):
super().__init__()
self.image = pygame.Surface((15, 3))
self.image.fill((60, 60, 60))
self.rect = self.image.get_rect(midleft = ai_game.ship_rect.midright)

def update(self):
self.rect.x += 5
class Game:
def __init__(self):
# [...]
self.bullets = pygame.sprite.Group()

def game_loop(self):
while True:
self._check_user_inputs()
self._ship_update()
self._bullet_update()
self._screen_update()

def _check_user_inputs(self):
for event in pygame.event.get():
elif event.type == pygame.KEYDOWN:
# [...]
elif event.key == pygame.K_SPACE:
self.bullets.add(Bullet(self))
elif event.type == pygame.KEYUP:
# [...]

def _bullet_update(self):
self.bullets.update()
for bullet in self.bullets:
if bullet.rect.left > self.screen_rect.right:
bullet.kill()

def _screen_update(self):
# [...]
self.bullets.draw(ai_game.screen)
pygame.display.flip()

Pygame independent moving images on the screen

I've tried to keep everything simple

Example:

import pygame
pygame.init()

WHITE = (255,255,255)
BLUE = (0,0,255)
window_size = (400,400)
screen = pygame.display.set_mode(window_size)
clock = pygame.time.Clock()

class Image():
def __init__(self,x,y,xd,yd):
self.image = pygame.Surface((40,40))
self.image.fill(BLUE)
self.x = x
self.y = y
self.x_delta = xd
self.y_delta = yd
def update(self):
if 0 <= self.x + self.x_delta <= 360:
self.x += self.x_delta
else:
self.x_delta *= -1
if 0 <= self.y + self.y_delta <= 360:
self.y += self.y_delta
else:
self.y_delta *= -1
screen.blit(self.image,(self.x,self.y))

list_of_images = []
list_of_images.append(Image(40,80,2,0))
list_of_images.append(Image(160,240,0,-2))

done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
screen.fill(WHITE)
for image in list_of_images:
image.update()
pygame.display.update()
clock.tick(30)

pygame.quit()

Each image can be called individually from the list and moved by simply changing Image.x/y to whatever you want

Make animation more smoother

Animation is a complex subject, with lots of boring theory. Basically, animation is the illusion of change over time. This is very important, as everything you do in animation will based around time.

In something like a game, you will have a bunch of entities all playing at a different rates of time. One of the challenges is taking the time to devise a solution which allows a entity to play over a period of time while been decoupled from the refresh cycle (ie frame count), unless you have sprite with the correct number of frames to match you refresh cycle, but even then, I'd be concerned, as the system won't be flexible enough to adapt to situations where the OS and hardware can't keep up.

The following is a simple example which takes a sprite sheet (a series of images stored in a single image), the number of expected images/frames and the time to complete a full cycle.

It calculates the individual frame size and returns a frame based on the amount of time that the sprite has been animated...

public class Sprite {

private BufferedImage source;
private int imageCount;
private int imageWidth;

// How long it takes to play a full cycle
private Duration duration;
// When the last cycle was started
private Instant startedAt;

public Sprite(BufferedImage source, int imageCount, int cycleTimeInSeconds) throws IOException {
this.source = source;
this.imageCount = imageCount;
imageWidth = source.getWidth() / imageCount;
duration = Duration.ofSeconds(cycleTimeInSeconds);
}

public BufferedImage getFrame() {
if (startedAt == null) {
startedAt = Instant.now();
}
Duration timePlayed = Duration.between(startedAt, Instant.now());
double progress = timePlayed.toMillis() / (double)duration.toMillis();
if (progress > 1.0) {
progress = 1.0;
startedAt = Instant.now();
}
int frame = Math.min((int)(imageCount * progress), imageCount - 1);
return getImageAt(frame);
}

protected BufferedImage getImageAt(int index) {
if (index < 0 || index >= imageCount) {
return null;
}
int xOffset = imageWidth * index;
return source.getSubimage(xOffset, 0, imageWidth, source.getHeight());
}

}

nb: It also needs a means to be reset or stopped, so you can force the sprite back to the start, but I'll leave that to you

Next, we need some way to play the animation

public class TestPane extends JPanel {

private Sprite sprite;

public TestPane(Sprite sprite) {
this.sprite = sprite;
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
}

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

protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
BufferedImage img = sprite.getFrame();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.dispose();
}

}

There's nothing really special here, it's a simple Swing Timer set to a high resolution (5 milliseconds) which constantly updates the UI, requesting the next frame from the sprite and painting it.

The important part here is the sprite and the refresh cycle are independent. Want the character to walk faster, change the sprite duration, want the character walk slower, changed the sprite duration, the refresh cycle doesn't need be altered (or any other entity)

So, starting with...

Sprite Sheet

Same cycle, first over 1 second, second over 5 seconds

FastwalkSlowWalk

You can also have a look at something like How to create a usable KeyReleased method in java, which demonstrates the use of key bindings and a centralised Set as a "action" repository



Related Topics



Leave a reply



Submit