Move with a Scrolling Screen - Margins

Unlike the Move with a Scrolling Screen - Centered which centers the camera on the player, this example only moves the camera if the user gets within so many pixels of the edge. It allows for a ‘box’ in the middle where the user can move around and NOT move the camera.

Screen shot of using a scrolling window
sprite_move_scrolling_box.py
  1"""
  2Scroll around a large screen.
  3
  4Artwork from https://kenney.nl
  5
  6If Python and Arcade are installed, this example can be run from the command line with:
  7python -m arcade.examples.sprite_move_scrolling_box
  8"""
  9
 10import random
 11import arcade
 12
 13SPRITE_SCALING = 0.5
 14
 15DEFAULT_SCREEN_WIDTH = 800
 16DEFAULT_SCREEN_HEIGHT = 600
 17SCREEN_TITLE = "Sprite Move with Scrolling Screen Example"
 18
 19# How many pixels to keep as a minimum margin between the character
 20# and the edge of the screen.
 21VIEWPORT_MARGIN = 200
 22
 23# How fast the camera pans to the player. 1.0 is instant.
 24CAMERA_SPEED = 0.1
 25
 26# How fast the character moves
 27PLAYER_MOVEMENT_SPEED = 7
 28
 29
 30class MyGame(arcade.Window):
 31    """ Main application class. """
 32
 33    def __init__(self, width, height, title):
 34        """
 35        Initializer
 36        """
 37        super().__init__(width, height, title, resizable=True)
 38
 39        # Sprite lists
 40        self.player_list = None
 41        self.wall_list = None
 42
 43        # Set up the player
 44        self.player_sprite = None
 45
 46        self.physics_engine = None
 47
 48        # Track the current state of what key is pressed
 49        self.left_pressed = False
 50        self.right_pressed = False
 51        self.up_pressed = False
 52        self.down_pressed = False
 53
 54        self.camera_sprites = arcade.camera.Camera2D()
 55        self.camera_gui = arcade.camera.Camera2D()
 56
 57    def setup(self):
 58        """ Set up the game and initialize the variables. """
 59
 60        # Sprite lists
 61        self.player_list = arcade.SpriteList()
 62        self.wall_list = arcade.SpriteList()
 63
 64        # Set up the player
 65        self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",
 66                                           scale=0.4)
 67        self.player_sprite.center_x = 256
 68        self.player_sprite.center_y = 512
 69        self.player_list.append(self.player_sprite)
 70
 71        # -- Set up several columns of walls
 72        for x in range(200, 1650, 210):
 73            for y in range(0, 1600, 64):
 74                # Randomly skip a box so the player can find a way through
 75                if random.randrange(5) > 0:
 76                    wall = arcade.Sprite(":resources:images/tiles/grassCenter.png", scale=SPRITE_SCALING)
 77                    wall.center_x = x
 78                    wall.center_y = y
 79                    self.wall_list.append(wall)
 80
 81        self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
 82
 83        # Set the background color
 84        self.background_color = arcade.color.AMAZON
 85
 86    def on_draw(self):
 87        """
 88        Render the screen.
 89        """
 90
 91        # This command has to happen before we start drawing
 92        self.clear()
 93
 94        # Select the camera we'll use to draw all our sprites
 95        self.camera_sprites.use()
 96
 97        # Draw all the sprites.
 98        self.wall_list.draw()
 99        self.player_list.draw()
100
101        # Select the (unscrolled) camera for our GUI
102        self.camera_gui.use()
103
104        # Draw the GUI
105        arcade.draw_rect_filled(arcade.rect.XYWH(self.width // 2, 20, self.width, 40), arcade.color.ALMOND)
106        text = f"Scroll value: ({self.camera_sprites.position[0]:5.1f}, {self.camera_sprites.position[1]:5.1f})"
107        arcade.draw_text(text, 10, 10, arcade.color.BLACK_BEAN, 20)
108
109        # Draw the box that we work to make sure the user stays inside of.
110        # This is just for illustration purposes. You'd want to remove this
111        # in your game.
112        left_boundary = VIEWPORT_MARGIN
113        right_boundary = self.width - VIEWPORT_MARGIN
114        top_boundary = self.height - VIEWPORT_MARGIN
115        bottom_boundary = VIEWPORT_MARGIN
116        arcade.draw_lrbt_rectangle_outline(left_boundary, right_boundary, bottom_boundary, top_boundary,
117                                           arcade.color.RED, 2)
118
119    def on_key_press(self, key, modifiers):
120        """Called whenever a key is pressed. """
121
122        if key == arcade.key.UP:
123            self.up_pressed = True
124        elif key == arcade.key.DOWN:
125            self.down_pressed = True
126        elif key == arcade.key.LEFT:
127            self.left_pressed = True
128        elif key == arcade.key.RIGHT:
129            self.right_pressed = True
130
131    def on_key_release(self, key, modifiers):
132        """Called when the user releases a key. """
133
134        if key == arcade.key.UP:
135            self.up_pressed = False
136        elif key == arcade.key.DOWN:
137            self.down_pressed = False
138        elif key == arcade.key.LEFT:
139            self.left_pressed = False
140        elif key == arcade.key.RIGHT:
141            self.right_pressed = False
142
143    def on_update(self, delta_time):
144        """ Movement and game logic """
145
146        # Calculate speed based on the keys pressed
147        self.player_sprite.change_x = 0
148        self.player_sprite.change_y = 0
149
150        if self.up_pressed and not self.down_pressed:
151            self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
152        elif self.down_pressed and not self.up_pressed:
153            self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
154        if self.left_pressed and not self.right_pressed:
155            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
156        elif self.right_pressed and not self.left_pressed:
157            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
158
159        # Call update on all sprites (The sprites don't do much in this
160        # example though.)
161        self.physics_engine.update()
162
163        # Scroll the screen to the player
164        self.scroll_to_player()
165
166    def scroll_to_player(self):
167        """
168        Scroll the window to the player.
169        This method will attempt to keep the player at least VIEWPORT_MARGIN
170        pixels away from the edge.
171
172        if CAMERA_SPEED is 1, the camera will immediately move to the desired position.
173        Anything between 0 and 1 will have the camera move to the location with a smoother
174        pan.
175        """
176
177        # --- Manage Scrolling ---
178
179        _target_x, _target_y = self.camera_sprites.position
180
181        top_left = self.camera_sprites.top_left
182        bottom_right = self.camera_sprites.bottom_right
183
184        # Scroll left
185        left_boundary = top_left[0] + VIEWPORT_MARGIN
186        if self.player_sprite.left < left_boundary:
187            _target_x -= left_boundary - self.player_sprite.left
188
189        # Scroll right
190        right_boundary = bottom_right[0] - VIEWPORT_MARGIN
191        if self.player_sprite.right > right_boundary:
192            _target_x += self.player_sprite.right - right_boundary
193
194        # Scroll up
195        top_boundary = top_left[1] - VIEWPORT_MARGIN
196        if self.player_sprite.top > top_boundary:
197            _target_y += self.player_sprite.top - top_boundary
198
199        # Scroll down
200        bottom_boundary = bottom_right[1] + VIEWPORT_MARGIN
201        if self.player_sprite.bottom < bottom_boundary:
202            _target_y -= bottom_boundary - self.player_sprite.bottom
203
204        # Scroll to the proper location
205        position = _target_x, _target_y
206        self.camera_sprites.position = arcade.math.lerp_2d(self.camera_sprites.position, position, CAMERA_SPEED)
207
208    def on_resize(self, width: int, height: int):
209        """
210        Resize window
211        Handle the user grabbing the edge and resizing the window.
212        """
213        super().on_resize(width, height)
214        self.camera_sprites.match_screen(and_projection=True)
215        self.camera_gui.match_screen(and_projection=True)
216
217
218def main():
219    """ Main function """
220    window = MyGame(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT, SCREEN_TITLE)
221    window.setup()
222    arcade.run()
223
224
225if __name__ == "__main__":
226    main()