I'm making a top-down racing game that has local multiplayer. I want each player to have their own view on the screen. The problem I'm having is that it seems you can't change a camera's position within the window. Ideally, I want to be able to have 4 different views of that map (1 for each player). If anyone knows of a way to change the camera's position within the window or a better way to do this, it would be much appreciated.
Here is an example I made which has 2 cameras, one is as big as the window, the other is smaller. I want them to both take up an equal portion of the screen and not overlap each other.
import arcade as arc
from arcade.pymunk_physics_engine import PymunkPhysicsEngine
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500
LEFT_VIEWPORT_MARGIN = 100
RIGHT_VIEWPORT_MARGIN = 100
TOP_VIEWPORT_MARGIN = 100
BOTTOM_VIEWPORT_MARGIN = 100
PHY_ENGINE = PymunkPhysicsEngine()
class Player(arc.Sprite):
def __init__(self, player_num=0, control="wasd"):
super().__init__()
if player_num == 0:
self.texture = arc.load_texture(":resources:images/space_shooter/playerShip1_orange.png")
elif player_num == 1:
self.texture = arc.load_texture(":resources:images/space_shooter/playerShip1_blue.png")
self.control = control
self.move_up = False
self.move_down = False
self.move_left = False
self.move_right = False
def update(self):
if self.move_up:
PHY_ENGINE.apply_force(self, (0, 1000))
if self.move_down:
PHY_ENGINE.apply_force(self, (0, -1000))
if self.move_right:
PHY_ENGINE.apply_force(self, (1000, 0))
if self.move_left:
PHY_ENGINE.apply_force(self, (-1000, 0))
class GameView(arc.View):
def __init__(self):
super().__init__()
self.width = SCREEN_WIDTH
self.height = SCREEN_HEIGHT
self.scene = None
self.p_cameras = []
self.gui_camera = None
self.camera_info = [[0, 0], [0, 0]]
self.map_width = 10000
self.map_height = 10000
self.players = []
# input stuff
self.w_pressed = False
self.s_pressed = False
self.a_pressed = False
self.d_pressed = False
self.up_pressed = False
self.down_pressed = False
self.left_pressed = False
self.right_pressed = False
self.move_up = False
self.move_down = False
self.move_left = False
self.move_right = False
# game stuff
self.physics_engine = PHY_ENGINE
self.background = arc.load_texture(":resources:images/backgrounds/abstract_1.jpg")
def process_keychange(self):
for player in self.players:
# WASD movement
if player.control == "wasd":
if self.w_pressed:
player.move_up = True
else:
player.move_up = False
if self.s_pressed:
player.move_down = True
else:
player.move_down = False
if self.d_pressed:
player.move_right = True
else:
player.move_right = False
if self.a_pressed:
player.move_left = True
else:
player.move_left = False
# arrow key movement
elif player.control == "arrows":
if self.up_pressed:
player.move_up = True
else:
player.move_up = False
if self.down_pressed:
player.move_down = True
else:
player.move_down = False
if self.right_pressed:
player.move_right = True
else:
player.move_right = False
if self.left_pressed:
player.move_left = True
else:
player.move_left = False
def on_key_press(self, key, modifiers):
if key == arc.key.W:
self.w_pressed = True
if key == arc.key.S:
self.s_pressed = True
if key == arc.key.A:
self.a_pressed = True
if key == arc.key.D:
self.d_pressed = True
if key == arc.key.UP:
self.up_pressed = True
if key == arc.key.DOWN:
self.down_pressed = True
if key == arc.key.LEFT:
self.left_pressed = True
if key == arc.key.RIGHT:
self.right_pressed = True
if key == arc.key.ESCAPE:
arc.exit()
def on_key_release(self, key, modifiers):
if key == arc.key.W:
self.w_pressed = False
if key == arc.key.S:
self.s_pressed = False
if key == arc.key.A:
self.a_pressed = False
if key == arc.key.D:
self.d_pressed = False
if key == arc.key.UP:
self.up_pressed = False
if key == arc.key.DOWN:
self.down_pressed = False
if key == arc.key.LEFT:
self.left_pressed = False
if key == arc.key.RIGHT:
self.right_pressed = False
self.process_keychange()
def on_show_view(self):
arc.set_viewport(0, self.window.width, 0, self.window.height)
self.load_level()
def load_level(self):
self.p_cameras = [arc.Camera(), arc.Camera(viewport_width=500, viewport_height=400)]
self.scene = arc.Scene()
self.scene.add_sprite_list("player")
player1 = Player()
player1.center_y = 200
player1.center_x = 200
self.scene.add_sprite("player", player1)
self.physics_engine.add_sprite(player1, moment_of_inertia=PymunkPhysicsEngine.MOMENT_INF,
collision_type="player", damping=.01)
player2 = Player(player_num=1, control="arrows")
player2.center_y = 300
player2.center_x = 200
self.scene.add_sprite("player", player2)
self.physics_engine.add_sprite(player2, moment_of_inertia=PymunkPhysicsEngine.MOMENT_INF,
collision_type="player", damping=.01)
self.players.append(player1)
self.players.append(player2)
def on_draw(self):
if self.p_cameras is None:
return
for camera in self.p_cameras:
camera.use()
arc.draw_lrwh_rectangle_textured(camera.position.x,
camera.position.y,
camera.viewport_width, camera.viewport_height, self.background)
self.scene.draw()
def center_camera_to_player(self, index=0):
target_player = self.players[index]
camera = self.p_cameras[index]
view_left = self.camera_info[index][0]
view_bottom = self.camera_info[index][1]
# Scroll left
left_boundary = view_left + LEFT_VIEWPORT_MARGIN
if target_player.left < left_boundary:
view_left -= left_boundary - target_player.left
# Scroll right
right_boundary = view_left + self.width - RIGHT_VIEWPORT_MARGIN - (SCREEN_WIDTH - camera.viewport_width)
if target_player.right > right_boundary:
view_left += target_player.right - right_boundary
# Scroll up
top_boundary = view_bottom + self.height - TOP_VIEWPORT_MARGIN - (SCREEN_HEIGHT - camera.viewport_height)
if target_player.top > top_boundary:
view_bottom += target_player.top - top_boundary
# Scroll down
bottom_boundary = view_bottom + BOTTOM_VIEWPORT_MARGIN
if target_player.bottom < bottom_boundary:
view_bottom -= bottom_boundary - target_player.bottom
# keeps camera in left bound of map
if view_left < 0:
view_left = 0
# keeps camera in right bound of map
if (view_left + self.width) > self.map_width:
view_left = self.map_width - self.width
# keeps camera in bottom bound of map
if view_bottom < 0:
view_bottom = 0
# keeps camera in top bound of map
if view_bottom + self.height > self.map_height:
view_bottom = self.map_height - self.height
# Scroll to the proper location
position = view_left, view_bottom
camera.move_to(position, .3)
def on_update(self, delta_time: float):
self.process_keychange()
self.center_camera_to_player(0)
self.center_camera_to_player(1)
self.scene.update()
self.physics_engine.step()
def main():
"""Main function"""
window = arc.Window(SCREEN_WIDTH, SCREEN_HEIGHT)
start_view = GameView()
window.show_view(start_view)
arc.run()
if __name__ == "__main__":
main()
The cameras in 2.6 doesn't support split screen (they will in 3.0+). However, it's not hard to set viewport and projection to achieve this.
Assumming you have a 200 x 100 window you can define two smaller screen areas by setting the viewport manually. Then the projection decides what range of geometry will be rendered inside those viewports.
Simple example
window.ctx.viewport = 0, 0, 100, 100 # (XYWH) A 100x100 area at 0,0
window.ctx.projection_2d = 0, 100, 0, 100
# Draw left part here
window.ctx.viewport = 100, 0, 100, 100 # (XYWH) A 100x100 area at 100,0
window.ctx.projection_2d = 0, 100, 0, 100
# Draw right part here
Usually you want the projection to have the same size as the viewport to get 1:1 pixel ratio. Everything in arcade is basically trinagles, so your defining what range of your "world" should be projected/rendered into the area. These are lower level OpenGL functions, so all this tranformation will be harware accellerated.
It becomes more obvious how it works when you play with the values.