Python Forum

Full Version: How to copy an object
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi members,

Does anyone know how to do a deep copy (copy.deepcopy) with pygame?

I'm having trouble performing a deep copy of an object (a class instance ?). The problem seems to stem from the fact that its Parent is from the pygame.sprite (but that's just a guess). I thought I would check here first because I think someone else must have come across this. Its advising me to use pygame to do this.

PS. I don't need to carry across any of the images in the copy operation.

Error:
Traceback (most recent call last): File "/home/pi/Documents/Alan/Dev/RocksInSpace/Prototype/v0.10/Python/rocksinspace.py", line 2332, in <module> TaskManager.process_task_list() File "/home/pi/Documents/Alan/Dev/RocksInSpace/Prototype/v0.10/Python/clsTaskManager.py", line 102, in process_task_list cls.process_task_queue(tl) File "/home/pi/Documents/Alan/Dev/RocksInSpace/Prototype/v0.10/Python/clsTaskManager.py", line 120, in process_task_queue q.linked_routine(q) File "/home/pi/Documents/Alan/Dev/RocksInSpace/Prototype/v0.10/Python/clsAutopilot.py", line 95, in _autopilot_point_craft_in_oaf self._autopilot_turn_to_angle(obj, update_task_status) File "/home/pi/Documents/Alan/Dev/RocksInSpace/Prototype/v0.10/Python/clsAutopilot.py", line 269, in _autopilot_turn_to_angle so_dupl =copy.deepcopy(so) File "/usr/lib/python3.4/copy.py", line 182, in deepcopy y = _reconstruct(x, rv, 1, memo) File "/usr/lib/python3.4/copy.py", line 300, in _reconstruct state = deepcopy(state, memo) File "/usr/lib/python3.4/copy.py", line 155, in deepcopy y = copier(x, memo) File "/usr/lib/python3.4/copy.py", line 246, in _deepcopy_dict y[deepcopy(key, memo)] = deepcopy(value, memo) File "/usr/lib/python3.4/copy.py", line 182, in deepcopy y = _reconstruct(x, rv, 1, memo) File "/usr/lib/python3.4/copy.py", line 295, in _reconstruct y = callable(*args) File "/usr/lib/python3.4/copyreg.py", line 88, in __newobj__ return cls.__new__(cls, *args) TypeError: object.__new__(pygame.mask.Mask) is not safe, use pygame.mask.Mask.__new__()
What is your purpose in deepcopying the object in the first place? From the error it seems you might be copying an object to rotate it, which would be overkill to handle such a task
If you deepcopy anything. It copies everything.
Personal I never use pygame sprites class.

Example.
class Ship
    # class instance. There be only one. doesn't matter how many objects you make.
    image = # load ship image

    def __init__(self):
        self.image = pygame.transform.rotate(Ship.image, angle)

class Rock:
    image = # load rock image

    def __init__(self):
        self.image = pygame.transform.rotate(Rock.image, angle)
(Aug-05-2018, 01:15 PM)metulburr Wrote: [ -> ]What is your purpose in deepcopying the object in the first place? From the error it seems you might be copying an object to rotate it, which would be overkill to handle such a task

I'm working with a class based on the pygame.sprite, and working on a task that involved simulating an autopilot program that kicks in when the craft strays to far off the screen. I have a routine that predicts the maximum speed the craft can turn without missing and over shooting the target angle. I thought working on a copy of the object would be the easiest way. Performing a shallow copy means I have to treat the object differently.
You shouldnt need to copy an object to rotate it at all. The only trick is to keep the original untainted. An example of rotation is given in the post above by Windspar. And it doesnt have nor need any copy (deep or shallow) in it at all.

self.image = pygame.transform.rotate(original_image, angle)
This rotates self.image to the specified angle. You just keep recreating self.image with a new angle as it rotates from the same unmodified original_image.

Here is a full fledged workable examples
https://github.com/Mekire/pygame-samples...et/tank.py
https://github.com/Mekire/pygame-samples...animate.py
Please leave conversation in the forums so future searchers can benefit from it as well.

If you really dont want to handle it at runtime, you can always do the same and save the 360 degrees of animated objects in a list and then display the proper one accordingly. But python is plenty fast enough to not have to worry about speed until there is an actual problem. And most bottlenecks are a result of poor programming than python's speed, if that is what your thinking.
I always avoid sprite class.
Example how I handle Sprite.
import pygame
import random

pygame.init()

class Point:
    @classmethod
    def rnd(cls, min_size, max_size):
        return cls( random.randint(min_size, max_size),
                    random.randint(min_size, max_size))

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def tup(self):
        return self.x, self.y

    def __add__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x + rhs.x, self.y + rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x + rhs, self.y + rhs)

    def __sub__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x - rhs.x, self.y - rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x - rhs, self.y - rhs)

    def __mul__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x * rhs.x, self.y * rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x * rhs, self.y * rhs)

    def __str__(self):
        return "Point({0}, {1})".format(self.x, self.y)


class TickTimer:
    ticks = 0

    @classmethod
    def tick(cls):
        cls.ticks = pygame.time.get_ticks()

    def __init__(self, interval):
        self.interval = interval
        self.next_tick = TickTimer.ticks + interval

    def elapse(self):
        if TickTimer.ticks > self.next_tick:
            self.next_tick += self.interval
            if TickTimer.ticks > self.next_tick:
                self.next_tick = TickTimer.ticks + self.interval
            return True

        return False

class Rock:
    rocks = []

    @staticmethod
    def create_image(size, color):
        image = pygame.Surface((size, size))
        image = image.convert_alpha()
        image.fill((0,0,0,0))
        pygame.draw.rect(image, color, (1,1,size - 1,size - 1))
        return image

    @classmethod
    def load(cls):
        cls.images = [  cls.create_image(10, (0,200,0)),
                        cls.create_image(20, (0,0,200)),
                        cls.create_image(30, (200,0,200))]

    @classmethod
    def update(cls, surface):
        alive_rocks = []
        for rock in cls.rocks:
            if rock.alive:
                rock.draw(surface)
                alive_rocks.append(rock)

        cls.rocks = alive_rocks

    def __init__(self, position, angle, direction, size=2, rotation=1):
        self.image = pygame.transform.rotate(Rock.images[size], angle)
        self.size = size
        self.position = position
        self.direction = direction
        self.rect = self.image.get_rect()
        self.rect.topleft = position.tup()
        self.angle = angle
        self.rotation = rotation
        self.rotation_timer = TickTimer(40)
        self.timer = TickTimer(20)
        Rock.rocks.append(self)
        self.alive = True

    def copy(self):
        if self.size > 0:
            return Rock(
                self.position,
                self.angle,
                self.direction,
                self.size - 1,
                self.rotation)

        return None

    def draw(self, surface):
        if self.alive:
            if self.rotation_timer.elapse():
                self.angle += self.rotation
                self.angle %= 360
                self.image = pygame.transform.rotate(Rock.images[self.size], self.angle)
                rect = self.image.get_rect()
                rect.center = self.rect.center
                point = Point(*self.rect.topleft) - Point(*rect.topleft)
                self.position -= point
                self.rect = rect


            if self.timer.elapse():
                self.position += self.direction
                self.rect.x = int(self.position.x)
                self.rect.y = int(self.position.y)

                clamp = self.rect.clamp(Screen.rect)
                if clamp.x != self.rect.x:
                    self.direction.x = -self.direction.x
                    self.position.x = clamp.x

                if clamp.y != self.rect.y:
                    self.direction.y = -self.direction.y
                    self.position.y = clamp.y

                self.rect = clamp

            surface.blit(self.image, self.rect)

class Screen:
    rect = pygame.Rect(0,0,800,600)
    clock = pygame.time.Clock()
    surface = pygame.display.set_mode(rect.size)

    @classmethod
    def loop(cls, fps=30):
        cls.running = True
        while cls.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    cls.running = False

            cls.surface.fill((0,0,0))
            TickTimer.tick()
            Rock.update(cls.surface)
            pygame.display.flip()

        pygame.quit()

def main():
    Rock.load()
    for i in range(5):
        Rock(
            Point.rnd(1, 770),
            random.randint(0, 360),
            Point.rnd(-200, 200) * 0.03,
            2,
            random.choice([-2, 2]))

    Screen.loop()

main()
Nice one, thanks for the examples and tips.
Improve Example. They randomly blow up.
import pygame
import random

pygame.init()

class Point:
    @classmethod
    def rnd(cls, min_size, max_size):
        return cls( random.randint(min_size, max_size),
                    random.randint(min_size, max_size))

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def tup(self):
        return self.x, self.y

    def __add__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x + rhs.x, self.y + rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x + rhs, self.y + rhs)

    def __sub__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x - rhs.x, self.y - rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x - rhs, self.y - rhs)

    def __mul__(self, rhs):
        if isinstance(rhs, Point):
            return Point(self.x * rhs.x, self.y * rhs.y)
        elif isinstance(rhs, (int, float)):
            return Point(self.x * rhs, self.y * rhs)

    def __str__(self):
        return "Point({0}, {1})".format(self.x, self.y)


class TickTimer:
    ticks = 0

    @classmethod
    def tick(cls):
        cls.ticks = pygame.time.get_ticks()

    def __init__(self, interval):
        self.interval = interval
        self.next_tick = TickTimer.ticks + interval

    def elapse(self):
        if TickTimer.ticks > self.next_tick:
            self.next_tick += self.interval
            if TickTimer.ticks > self.next_tick:
                self.next_tick = TickTimer.ticks + self.interval
            return True

        return False

class Rock:
    rocks = []

    @staticmethod
    def get_direction(size):
        speed = [150, 100, 50][size]
        value_x = random.randint(speed - 25, speed) * 0.02
        if random.choice([False, True]):
            value_x = -value_x

        value_y = random.randint(speed - 25, speed) * 0.02
        if random.choice([False, True]):
            value_y = -value_y

        return Point(value_x, value_y)

    @staticmethod
    def create_image(size, color):
        image = pygame.Surface((size, size))
        image = image.convert_alpha()
        image.fill((0,0,0,0))
        pygame.draw.rect(image, color, (1,1,size - 1,size - 1))
        return image

    @classmethod
    def load(cls):
        cls.images = [  cls.create_image(10, (0,200,0)),
                        cls.create_image(20, (0,0,200)),
                        cls.create_image(30, (200,0,200))]

    @classmethod
    def update(cls, surface):
        alive_rocks = []
        for rock in cls.rocks:
            if rock.alive:
                rock.draw(surface)
                alive_rocks.append(rock)

                if random.randint(1, 200) == 1:
                    rock.alive = False
                    if rock.size > 0:
                        for i in range(2):
                            alive_rocks.append(rock.copy())

        cls.rocks = alive_rocks
        if len(cls.rocks) < 5:
            Rock(
                Point.rnd(1, 770),
                random.randint(0, 360),
                Rock.get_direction(2),
                2,
                random.choice([-2, 2]))



    def __init__(self, position, angle, direction, size=2, rotation=1, add_rock=True):
        self.image = pygame.transform.rotate(Rock.images[size], angle)
        self.size = size
        self.position = position
        self.direction = direction
        self.rect = self.image.get_rect()
        self.rect.topleft = position.tup()
        self.angle = angle
        self.rotation = rotation
        self.rotation_timer = TickTimer(40)
        self.timer = TickTimer(20)
        self.alive = True

        if add_rock:
            Rock.rocks.append(self)

    def copy(self):
        if self.size > 0:
            return Rock(
                self.position,
                self.angle,
                Rock.get_direction(self.size - 1),
                self.size - 1,
                self.rotation,
                False)

        return None

    def draw(self, surface):
        if self.alive:
            if self.rotation_timer.elapse():
                self.angle += self.rotation
                self.angle %= 360
                self.image = pygame.transform.rotate(Rock.images[self.size], self.angle)
                rect = self.image.get_rect()
                rect.center = self.rect.center
                point = Point(*self.rect.topleft) - Point(*rect.topleft)
                self.position -= point
                self.rect = rect


            if self.timer.elapse():
                self.position += self.direction
                self.rect.x = int(self.position.x)
                self.rect.y = int(self.position.y)

                clamp = self.rect.clamp(Screen.rect)
                if clamp.x != self.rect.x:
                    self.direction.x = -self.direction.x
                    self.position.x = clamp.x

                if clamp.y != self.rect.y:
                    self.direction.y = -self.direction.y
                    self.position.y = clamp.y

                self.rect = clamp

            surface.blit(self.image, self.rect)

class Screen:
    rect = pygame.Rect(0,0,800,600)
    clock = pygame.time.Clock()
    surface = pygame.display.set_mode(rect.size)

    @classmethod
    def loop(cls, fps=30):
        cls.running = True
        while cls.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    cls.running = False

            cls.surface.fill((0,0,0))
            TickTimer.tick()
            Rock.update(cls.surface)
            pygame.display.flip()
            cls.clock.tick(fps)

        pygame.quit()

def main():
    Rock.load()
    for i in range(5):
        Rock(
            Point.rnd(1, 770),
            random.randint(0, 360),
            Rock.get_direction(2),
            2,
            random.choice([-2, 2]))

    Screen.loop()

main()