diff --git a/examples/core/core_2d_camera.py b/examples/core/core_2d_camera.py new file mode 100644 index 0000000..ece199e --- /dev/null +++ b/examples/core/core_2d_camera.py @@ -0,0 +1,137 @@ +""" + +raylib [core] example - 2d camera + +""" +from raylib.pyray import PyRay +from raylib.colors import ( + RAYWHITE, + DARKGRAY, + RED, + GREEN, + SKYBLUE, + BLUE, + BLACK, +) + + +pyray = PyRay() + +# Initialization +MAX_BUILDINGS = 100 +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 450 + +pyray.init_window(SCREEN_WIDTH, SCREEN_HEIGHT, + 'raylib [core] example - 2d camera') + +player = pyray.Rectangle(400, 280, 40, 40) +buildings = [] +build_colors = [] +spacing = 0 + +for i in range(MAX_BUILDINGS): + width = pyray.get_random_value(50, 200) + height = pyray.get_random_value(100, 800) + y = SCREEN_HEIGHT - 130 - height + x = -6000 + spacing + + buildings.append(pyray.Rectangle(x, y, width, height)) + + spacing += width + + build_colors.append(pyray.Color( + pyray.get_random_value(200, 240), + pyray.get_random_value(200, 240), + pyray.get_random_value(200, 250), + 255 + )) + +camera = pyray.Camera2D() +camera.target = pyray.Vector2(player.x + 20, player.y + 20) +camera.offset = pyray.Vector2(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2) +camera.rotation = 0.0 +camera.zoom = 1.0 + +pyray.set_target_fps(60) # Set our game to run at 60 frames-per-second + + +# Main game loop +while not pyray.window_should_close(): # Detect window close button or ESC key + # Update + + # Player movement + if pyray.is_key_down(pyray.KEY_RIGHT): + player.x += 2 + elif pyray.is_key_down(pyray.KEY_LEFT): + player.x -= 2 + + # Camera target follows player + camera.target = pyray.Vector2(player.x + 20, player.y + 20) + + # Camera rotation controls + if pyray.is_key_down(pyray.KEY_A): + camera.rotation -= 1 + elif pyray.is_key_down(pyray.KEY_S): + camera.rotation += 1 + + # Limit camera rotation to 80 degrees (-40 to 40) + if camera.rotation > 40: + camera.rotation = 40 + elif camera.rotation < -40: + camera.rotation = -40 + + # Camera zoom controls + camera.zoom += pyray.get_mouse_wheel_move() * 0.05 + + if camera.zoom > 3.0: + camera.zoom = 3.0 + elif camera.zoom < 0.1: + camera.zoom = 0.1 + + # Camera reset (zoom and rotation) + if pyray.is_key_pressed(pyray.KEY_R): + camera.zoom = 1.0 + camera.rotation = 0.0 + + # Draw + pyray.begin_drawing() + pyray.clear_background(RAYWHITE) + + pyray.begin_mode_2d(camera) + + pyray.draw_rectangle(-6000, 320, 13000, 8000, DARKGRAY) + + for i in range(MAX_BUILDINGS): + pyray.draw_rectangle_rec(buildings[i], build_colors[i]) + + pyray.draw_rectangle_rec(player, RED) + + x = int(camera.target.x) + y = int(camera.target.y) + pyray.draw_line(x, -SCREEN_HEIGHT * 10, x, SCREEN_HEIGHT * 10, GREEN) + pyray.draw_line(-SCREEN_WIDTH * 10, y, SCREEN_WIDTH * 10, y, GREEN) + + pyray.end_mode_2d() + + pyray.draw_text('SCREEN AREA', 640, 10, 20, RED) + + pyray.draw_rectangle(0, 0, SCREEN_WIDTH, 5, RED) + pyray.draw_rectangle(0, 5, 5, SCREEN_HEIGHT - 10, RED) + pyray.draw_rectangle(SCREEN_WIDTH - 5, 5, 5, SCREEN_HEIGHT - 10, RED) + pyray.draw_rectangle(0, SCREEN_HEIGHT - 5, SCREEN_WIDTH, 5, RED) + + pyray.draw_rectangle(10, 10, 250, 113, pyray.fade(SKYBLUE, 0.5)) + pyray.draw_rectangle_lines(10, 10, 250, 113, BLUE) + + pyray.draw_text('Free 2d camera controls:', 20, 20, 10, BLACK) + pyray.draw_text('- Right/Left to move Offset', 40, 40, 10, DARKGRAY) + pyray.draw_text('- Mouse Wheel to Zoom in-out', 40, 60, 10, DARKGRAY) + pyray.draw_text('- A / S to Rotate', 40, 80, 10, DARKGRAY) + pyray.draw_text('- R to reset Zoom and Rotation', 40, 100, 10, DARKGRAY) + + pyray.end_drawing() + + +# De-Initialization +pyray.close_window() # Close window and OpenGL context diff --git a/examples/core/core_2d_camera_platformer.py b/examples/core/core_2d_camera_platformer.py new file mode 100644 index 0000000..4737798 --- /dev/null +++ b/examples/core/core_2d_camera_platformer.py @@ -0,0 +1,314 @@ +""" + +raylib [core] example - 2d camera platformer + +""" +from math import sqrt + +from raylib.pyray import PyRay +from raylib.colors import ( + DARKGRAY, + RED, + BLACK, + GRAY, + LIGHTGRAY, +) + + +pyray = PyRay() + +# Initialization +global g_evening_out, g_even_out_target +g_evening_out = False + +G = 400 +PLAYER_JUMP_SPD = 350.0 +PLAYER_HOR_SPD = 200.0 + +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 450 + +pyray.init_window(SCREEN_WIDTH, SCREEN_HEIGHT, + 'raylib [core] example - 2d camera') + + +# Raylib Math +def vector2_subtract(v1, v2): + return pyray.Vector2(v1.x - v2.x, v1.y - v2.y) + + +def vector2_add(v1, v2): + return pyray.Vector2(v1.x + v2.x, v1.y + v2.y) + + +def vector2_length(v): + return sqrt((v.x * v.x) + (v.y * v.y)) + + +def vector2_scale(v, scale): + return pyray.Vector2(v.x * scale, v.y * scale) + + +class Player: + + def __init__(self, position, speed, can_jump): + self.position = position + self.speed = speed + self.can_jump = can_jump + + +class EnvItem: + + def __init__(self, rect, blocking, color): + self.rect = rect + self.blocking = blocking + self.color = color + + +def update_player(player, env_items, delta): + if pyray.is_key_down(pyray.KEY_LEFT): + player.position.x -= PLAYER_HOR_SPD * delta + if pyray.is_key_down(pyray.KEY_RIGHT): + player.position.x += PLAYER_HOR_SPD * delta + if pyray.is_key_down(pyray.KEY_SPACE) and player.can_jump: + player.speed = -PLAYER_JUMP_SPD + player.can_jump = False + + hit_obstacle = False + for ei in env_items: + p = player.position + if ( + ei.blocking and + ei.rect.x <= p.x and + ei.rect.x + ei.rect.width >= p.x and + ei.rect.y >= p.y and + ei.rect.y < p.y + player.speed * delta + ): + hit_obstacle = True + player.speed = 0.0 + p.y = ei.rect.y + + if not hit_obstacle: + player.position.y += player.speed * delta + player.speed += G * delta + player.can_jump = False + else: + player.can_jump = True + + +def update_camera_center( + camera, player, env_items, delta, width, height +): + camera.offset = pyray.Vector2(width / 2, height / 2) + camera.target = player.position + + +def update_camera_center_inside_map( + camera, player, env_items, delta, width, height +): + camera.target = player.position + camera.offset = pyray.Vector2(width / 2, height / 2) + + minX = 1000 + minY = 1000 + maxX = -1000 + maxY = -1000 + + for ei in env_items: + minX = min(ei.rect.x, minX) + maxX = max(ei.rect.x + ei.rect.width, maxX) + + minY = min(ei.rect.y, minY) + maxY = max(ei.rect.y + ei.rect.height, maxY) + + wmax = pyray.get_world_to_screen_2d(pyray.Vector2(maxX, maxY), camera) + wmin = pyray.get_world_to_screen_2d(pyray.Vector2(minX, minY), camera) + + if wmax.x < width: + camera.offset.x = width - (wmax.x - width / 2) + if wmax.y < height: + camera.offset.y = height - (wmax.y - height / 2) + if wmin.x > 0: + camera.offset.x = width / 2 - wmin.x + if wmin.y > 0: + camera.offset.y = height / 2 - wmin.y + + +def update_camera_center_smooth_follow( + camera, player, env_items, delta, width, height +): + min_speed = 30 + min_effect_length = 10 + fraction_speed = 0.8 + + camera.offset = pyray.Vector2(width / 2, height / 2) + diff = vector2_subtract(player.position, camera.target) + length = vector2_length(diff) + + if length > min_effect_length: + speed = max(fraction_speed * length, min_speed) + camera.target = vector2_add( + camera.target, vector2_scale(diff, speed * delta / length) + ) + + +def update_camera_even_out_on_landing( + camera, player, env_items, delta, width, height +): + global g_evening_out, g_even_out_target + + even_out_speed = 700 + + camera.offset = pyray.Vector2(width / 2, height / 2) + camera.target.x = player.position.x + + if g_evening_out: + if g_even_out_target > camera.target.y: + camera.target.y += even_out_speed * delta + + if camera.target.y > g_even_out_target: + camera.target.y = g_even_out_target + g_evening_out = False + else: + camera.target.y -= even_out_speed * delta + if camera.target.y < g_even_out_target: + camera.target.y = g_even_out_target + g_evening_out = False + else: + if ( + player.can_jump and + (player.speed == 0) and + (player.position.y != camera.target.y) + ): + g_evening_out = True + g_even_out_target = player.position.y + + +def update_camera_player_bounds_push( + camera, player, env_items, delta, width, height +): + bbox = pyray.Vector2(0.2, 0.2) + + bbox_world_min = pyray.get_world_to_screen_2d( + pyray.Vector2((1 - bbox.x) * 0.5 * width, + (1 - bbox.y) * 0.5 * height), + camera + ) + bbox_world_max = pyray.get_world_to_screen_2d( + pyray.Vector2((1 + bbox.x) * 0.5 * width, + (1 + bbox.y) * 0.5 * height), + camera + ) + camera.offset = pyray.Vector2((1 - bbox.x) * 0.5 * width, + (1 - bbox.y) * 0.5 * height) + + if player.position.x < bbox_world_min.x: + camera.target.x = player.position.x + if player.position.y < bbox_world_min.y: + camera.target.y = player.position.y + if player.position.x > bbox_world_max.x: + camera.target.x = ( + bbox_world_min.x + (player.position.x - bbox_world_max.x) + ) + if player.position.y > bbox_world_max.y: + camera.target.y = ( + bbox_world_min.y + (player.position.y - bbox_world_max.y) + ) + + +# Main intialization +player = Player(pyray.Vector2(400, 280), 0, False) +env_items = ( + EnvItem(pyray.Rectangle(0, 0, 1000, 400), 0, LIGHTGRAY), + EnvItem(pyray.Rectangle(0, 400, 1000, 200), 1, GRAY), + EnvItem(pyray.Rectangle(300, 200, 400, 10), 1, GRAY), + EnvItem(pyray.Rectangle(250, 300, 100, 10), 1, GRAY), + EnvItem(pyray.Rectangle(650, 300, 100, 10), 1, GRAY), +) + +camera = pyray.Camera2D() +camera.target = player.position +camera.offset = pyray.Vector2(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2) +camera.rotation = 0.0 +camera.zoom = 1.0 + +pyray.set_target_fps(60) # Set our game to run at 60 frames-per-second + +# Store pointers to the multiple update camera functions +camera_updaters = ( + update_camera_center, + update_camera_center_inside_map, + update_camera_center_smooth_follow, + update_camera_even_out_on_landing, + update_camera_player_bounds_push, +) +camera_option = 0 +camera_updaters_length = len(camera_updaters) + +camera_descriptions = ( + 'Follow player center', + 'Follow player center, but clamp to map edges', + 'Follow player center smoothed', + ('Follow player center horizontally ' + 'update player center vertically after landing'), + 'Player push camera on getting too close to screen edge', +) + +# Main game loop +while not pyray.window_should_close(): # Detect window close button or ESC key + # Update + delta_time = pyray.get_frame_time() + + update_player(player, env_items, delta_time) + + camera.zoom += pyray.get_mouse_wheel_move() * 0.05 + + if camera.zoom > 3.0: + camera.zoom = 3.0 + elif camera.zoom < 0.25: + camera.zoom = 0.25 + + if pyray.is_key_pressed(pyray.KEY_R): + camera.zoom = 1.0 + player.position = pyray.Vector2(400, 280) + + if pyray.is_key_pressed(pyray.KEY_C): + camera_option = (camera_option + 1) % camera_updaters_length + + # Call update camera function by its pointer + camera_updaters[camera_option]( + camera, player, env_items, delta_time, + SCREEN_WIDTH, SCREEN_HEIGHT + ) + + # Draw + pyray.begin_drawing() + pyray.clear_background(LIGHTGRAY) + + pyray.begin_mode_2d(camera) + + for env_item in env_items: + pyray.draw_rectangle_rec(env_item.rect, env_item.color) + + player_rect = pyray.Rectangle( + int(player.position.x) - 20, + int(player.position.y) - 40, + 40, 40 + ) + pyray.draw_rectangle_rec(player_rect, RED) + + pyray.end_mode_2d() + + pyray.draw_text('Controls:', 20, 20, 10, BLACK) + pyray.draw_text('- Right/Left to move', 40, 40, 10, DARKGRAY) + pyray.draw_text('- Space to jump', 40, 60, 10, DARKGRAY) + pyray.draw_text('- Mouse Wheel to Zoom in-out, R to reset zoom', + 40, 80, 10, DARKGRAY) + pyray.draw_text('- C to change camera mode', 40, 100, 10, DARKGRAY) + pyray.draw_text('Current camera mode:', 20, 120, 10, BLACK) + pyray.draw_text(camera_descriptions[camera_option], 40, 140, 10, DARKGRAY) + + pyray.end_drawing() + +# De-Initialization +pyray.close_window() # Close window and OpenGL context diff --git a/examples/core/core_input_gestures.py b/examples/core/core_input_gestures.py index 765654b..9d60bfd 100644 --- a/examples/core/core_input_gestures.py +++ b/examples/core/core_input_gestures.py @@ -3,7 +3,7 @@ raylib [core] example - Input Gestures Detection """ -from raylib.pyray import PyRay, makeStructHelper +from raylib.pyray import PyRay from raylib.colors import ( RAYWHITE, LIGHTGRAY, @@ -73,15 +73,11 @@ while not pyray.window_should_close(): # Detect window close button or ESC key pyray.draw_rectangle_rec(touch_area, GRAY) pyray.draw_rectangle(225, 15, SCREEN_WIDTH - 240, SCREEN_HEIGHT - 30, RAYWHITE) - pyray.draw_text( 'GESTURES TEST AREA', SCREEN_WIDTH - 270, SCREEN_HEIGHT - 40, 20, pyray.fade(GRAY, 0.5) ) - pyray.draw_rectangle(10, 29, 200, SCREEN_HEIGHT - 50, GRAY) - pyray.draw_text('DETECTED GESTURES', 50, 15, 10, GRAY) - for i, val in enumerate(gesture_strings): if i % 2 == 0: pyray.draw_rectangle( @@ -95,11 +91,12 @@ while not pyray.window_should_close(): # Detect window close button or ESC key else: pyray.draw_text(val, 35, 36 + 20 * i, 10, MAROON) + pyray.draw_rectangle_lines(10, 29, 200, SCREEN_HEIGHT - 50, GRAY) + pyray.draw_text('DETECTED GESTURES', 50, 15, 10, GRAY) if current_gesture != pyray.GESTURE_NONE: pyray.draw_circle_v(touch_position, 30, MAROON) - pyray.end_drawing() diff --git a/examples/core/core_input_keys.py b/examples/core/core_input_keys.py index 3680d6e..87883e0 100644 --- a/examples/core/core_input_keys.py +++ b/examples/core/core_input_keys.py @@ -28,10 +28,14 @@ pyray.set_target_fps(60) # Set our game to run at 60 frames-per-second # Main game loop while not pyray.window_should_close(): # Detect window close button or ESC key # Update - if pyray.is_key_down(pyray.KEY_RIGHT): ball_position.x += 2 - if pyray.is_key_down(pyray.KEY_LEFT): ball_position.x -= 2 - if pyray.is_key_down(pyray.KEY_UP): ball_position.y -= 2 - if pyray.is_key_down(pyray.KEY_DOWN): ball_position.y += 2 + if pyray.is_key_down(pyray.KEY_RIGHT): + ball_position.x += 2 + if pyray.is_key_down(pyray.KEY_LEFT): + ball_position.x -= 2 + if pyray.is_key_down(pyray.KEY_UP): + ball_position.y -= 2 + if pyray.is_key_down(pyray.KEY_DOWN): + ball_position.y += 2 # Draw pyray.begin_drawing()