pythonarcade / arcade

Easy to use Python library for creating 2D arcade games.
http://arcade.academy
Other
1.7k stars 321 forks source link

Changing Sprite.width/height does not update hitbox #752

Closed eruvanos closed 3 years ago

eruvanos commented 4 years ago

Bug Report

Changing Sprite.width/height does not update hitbox.

This results in wrong behaviour using:

Actual behavior:

Running example code fails with AssertionError

Expected behavior:

Changing properties of sprite like Sprite.width/height should update the hitbox. Example code draws Sprite, passing the asserts

Steps to reproduce/example code:

import arcade
from arcade import Window, Sprite, get_window

class MyWindow(Window):
    def __init__(self):
        super().__init__()
        arcade.set_background_color((0, 0, 0))

        self.sprite = Sprite(":resources:images/space_shooter/playerShip1_orange.png")
        self.sprite.position = get_window().width / 2, get_window().height / 2,

        # Sprite width before : 90
        print(self.sprite.width)

        # Double sprite width: 180
        self.sprite.width = 180
        print(self.sprite.width)

        hit = self.sprite.collides_with_point((self.sprite.center_x + 80, self.sprite.center_y))
        assert hit
        assert self.sprite.right == self.sprite.center_x + 90

    def on_draw(self):
        arcade.start_render()
        self.sprite.draw()

if __name__ == '__main__':
    window = MyWindow()
    arcade.run()
akapkotel commented 4 years ago

Ok, I inspected the code and found that error comes from the self._points attribute NOT being set to None in _set_width setter in Sprite class. In the result, get_hit_box getter returns self._points already set to the original texture dimensions instead of recalculating it.

It is now: is

And should be: should_be

Of course the same problem exists for _set_height setter too.

I've also found other, strange thing.In _set_width setter there is a call to spritelist.update_size() but in _set_height there is call to spritelist.update_height(). I wonder why is that so? Should not ther be a call to spritelist.update_width() ? @pvcraven ?

pvcraven commented 4 years ago

Still seems to happen in 2.4.3:

2020-10-03_11-04-44
""" Sprite Sample Program """

import random
import arcade

# --- Constants ---
SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_COIN = 0.2
COIN_COUNT = 50

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

class MyGame(arcade.Window):
    """ Our custom Window Class"""

    def __init__(self):
        """ Initializer """
        # Call the parent class initializer
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprite Example")

        self.player_list = None
        self.coin_list = None

        self.player_sprite = None
        self.score = 0

        self.set_mouse_visible(False)
        arcade.set_background_color(arcade.color.AMAZON)

    def setup(self):

        # Sprite Lists
        self.player_list = arcade.SpriteList()
        self.coin_list = arcade.SpriteList()

        self.score = 0

        self.player_sprite = arcade.SpriteSolidColor(20, 20, arcade.color.RED)
        self.player_sprite.center_x = 400
        self.player_sprite.center_y = 300
        self.player_list.append(self.player_sprite)

        for i in range(COIN_COUNT):
            coin = arcade.SpriteSolidColor(20, 20, arcade.color.AMARANTH_PINK)
            coin.center_x = random.randrange(SCREEN_WIDTH)
            coin.center_y = random.randrange(SCREEN_HEIGHT)
            self.coin_list.append(coin)
            coin.width = 50
            coin.height = 50

    def on_draw(self):
        arcade.start_render()
        self.player_list.draw()
        self.coin_list.draw()

        self.coin_list.draw_hit_boxes(arcade.color.BLACK, 2)

        output = f"Score: {self.score}"
        arcade.draw_text(output, 10, 20, arcade.color.WHITE, 24)

    def on_mouse_motion(self, x, y, dx, dy):
        self.player_sprite.center_x = x
        self.player_sprite.center_y = y

    def on_update(self, delta_time):
        coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
                                                              self.coin_list)
        for coin in coins_hit_list:
            coin.remove_from_sprite_lists()
            self.score += 1

def main():
    """ Main method """
    window = MyGame()
    window.setup()
    arcade.run()

if __name__ == "__main__":
    main()
pvcraven commented 3 years ago

I think this was fixed with that Oct 3 check-in.