extends Camera2D export var camera_horizontal_shift = 60 export var offset_reset_seconds := 1 export var offset_adapt_seconds := 1 export var offset_input_seconds := 0.618 * 2 export var alarm_light_shader: Material export var fixed_position: bool = false onready var level_state := $"%LevelState" onready var signal_manager := $"%SignalManager" onready var shiftLeft = $CameraAnimationPlayer.get_animation("shiftingLeft") onready var shiftRight = $CameraAnimationPlayer.get_animation("shiftingRight") onready var shiftCenter = $CameraAnimationPlayer.get_animation("shiftingCenter") onready var anim_player := $CameraAnimationPlayer onready var original_x_zoom := zoom.x onready var original_y_zoom := zoom.y onready var blobby := get_node("%Blobby") var horizontal_facing = 0 var vertical_facing = 0 var camera_vertical_shift = 0 var right_move_time: float = 0 var left_move_time: float = 0 var slow_time: float = 0 var input_time: float = 0 var original_limit_left: int var original_limit_right: int var original_limit_bottom: int var original_limit_top: int var camera_is_panning: bool = false var target_offset: Vector2 = Vector2(0, 0) var terminal_activated: bool = false var image = Image.new() var texture = ImageTexture.new() var prev_pos: Vector2 var camera_state := "centered" var screen_rect = Vector2() var old_screen_rect = Vector2( ProjectSettings.get_setting("display/window/size/width") * zoom.x, ProjectSettings.get_setting("display/window/size/height") * zoom.y ) var screen_center = Vector2() var screen_bottom = Vector2() var screen_top = Vector2() var screen_left = Vector2() var screen_right = Vector2() # Gets the camera limits from the tilemap of the level # Requires "TileMap" to be a sibling of blobby func _ready(): _set_boundaries() get_tree().get_root().connect("size_changed", self, "_set_boundaries") if !fixed_position: self.position = blobby.global_position image.create(128, 2, false, Image.FORMAT_RGBAH) # TODO Test Performance material.set_shader_param("light_data", null) _update_lighting_shader() # TODO Trigger when needed signal_manager.connect("terminal_activated", self, "_on_SignalManager_terminal_activated") signal_manager.connect("player_died", self, "_death_cam") func _on_SignalManager_terminal_activated(animation_number: int = 0): terminal_activated = true get_node("LightAnimationPlayer").play("Pulsing") #func _draw(): # draw_line(Vector2((limit_left - position.x), screen_center.y), screen_left, Color(255, 0, 0), 1) func _physics_process(delta: float) -> void: if fixed_position: return # update() screen_center = (get_camera_screen_center() - position) screen_bottom = screen_center + Vector2(0, screen_rect.y / 2) screen_top = screen_center - Vector2(0, screen_rect.y / 2) screen_left = screen_center - Vector2(screen_rect.x / 2, 0) screen_right = screen_center + Vector2(screen_rect.x / 2, 0) var was_adjusted := false if !level_state.is_dead: was_adjusted = _adjust_offset(delta) if anim_player.is_playing() || was_adjusted: position = blobby.position prev_pos = position _update_lighting_shader() return var player_vel = (blobby.position - prev_pos) / delta # TODO Take average of velocity here if ( abs(player_vel.x) >= blobby.max_velocity["walk"] * 0.97 && (sign(player_vel.x) == sign(target_offset.x) || target_offset.x == 0) ): if player_vel.x > 0: right_move_time += delta left_move_time = max(0, left_move_time - delta) slow_time = max(0, slow_time - delta) else: left_move_time += delta right_move_time = max(0, right_move_time - delta) slow_time = max(0, slow_time - delta) elif ( abs(player_vel.x) <= blobby.max_velocity["walk"] * 0.9 || sign(player_vel.x) != sign(target_offset.x) || target_offset.x == 0 ): slow_time += delta left_move_time = max(0, left_move_time - delta) right_move_time = max(0, right_move_time - delta) _adapt_to_movement(player_vel) if abs(player_vel.x) <= blobby.max_velocity["walk"] * 0.9: _adapt_to_input(player_vel, delta) position = blobby.position prev_pos = position _update_lighting_shader() # TODO This has to be redone when the screen is resized in any way # Otherwise the boundaries will not be correct anymore func _set_boundaries(): screen_rect = get_viewport_rect().size screen_rect.x *= zoom.x screen_rect.y *= zoom.y original_x_zoom = zoom.x original_y_zoom = zoom.y # This is ok, because it only happens on initialization # But it is also quite fickle var tilemap = get_node("./%TileMap") # TODO: This goes wrong when overwriting old tiles with new sprites # New pngs -> completely new tiles and rebuild map var rect = tilemap.get_used_rect() var cell_size = tilemap.cell_size # TODO is fixed for camera issue in adjust horizontal limit_right = rect.end.x * cell_size.x - 6 limit_left = rect.position.x * cell_size.x + 6 limit_top = rect.position.y * cell_size.y + 6 limit_bottom = rect.end.y * cell_size.y - 6 original_limit_left = limit_left original_limit_right = limit_right original_limit_top = limit_top original_limit_bottom = limit_bottom var screen_size = get_viewport_rect() var h_pixels = limit_right - limit_left var v_pixels = limit_bottom - limit_top # TODO: Fix that it can zoom both? if screen_size.end.x * original_x_zoom - h_pixels > 0: zoom.x = h_pixels / screen_size.end.x zoom.y = zoom.x if screen_size.end.y * original_y_zoom - v_pixels > 0: zoom.y = v_pixels / screen_size.end.y zoom.x = zoom.y # Smoothing the camera limits in godot ruins something func _adapt_to_movement(velocity: Vector2) -> void: var offset_track var center = get_camera_screen_center() var left_edge_pos = center.x - screen_rect.x / 2 + camera_horizontal_shift var right_edge_pos = center.x + screen_rect.x / 2 - camera_horizontal_shift if left_move_time >= offset_adapt_seconds && !anim_player.is_playing(): left_move_time = 0 target_offset.x = -camera_horizontal_shift if offset == target_offset: return offset_track = shiftLeft.find_track(".:offset") shiftLeft.track_set_key_value(offset_track, 0, offset) shiftLeft.track_set_key_value(offset_track, 1, target_offset) camera_state = "shiftedLeft" anim_player.play("shiftingLeft") elif right_move_time >= offset_adapt_seconds && !anim_player.is_playing(): right_move_time = 0 target_offset.x = camera_horizontal_shift if offset == target_offset: return offset_track = shiftRight.find_track(".:offset") shiftRight.track_set_key_value(offset_track, 0, offset) shiftRight.track_set_key_value(offset_track, 1, target_offset) camera_state = "shiftedRight" anim_player.play("shiftingRight") elif ( slow_time >= offset_reset_seconds && !(Input.is_action_pressed("up") || Input.is_action_pressed("duck")) ): slow_time = 0 target_offset.x = 0 if offset == target_offset: return if left_edge_pos > limit_left && limit_right > right_edge_pos: offset_track = shiftCenter.find_track(".:offset") shiftCenter.track_set_key_value(offset_track, 0, offset) shiftCenter.track_set_key_value(offset_track, 1, target_offset) camera_state = "centered" anim_player.play("shiftingCenter") return func _adapt_to_input(velocity: Vector2, delta: float) -> void: # TODO Den bug dass man damit durch die map gucken kann wenn man sich weiter bewegt # lasse ich erstmal drin if velocity.length() > 20.0: input_time = 0 return if input_time < offset_input_seconds: input_time += delta return if Input.is_action_pressed("duck"): if original_limit_bottom - position.y - 2 > screen_bottom.y && offset.y < 48: offset.y += 0.5 elif Input.is_action_pressed("up"): if original_limit_top - position.y + 2 < screen_top.y && offset.y > -48: offset.y -= 0.5 # TODO This is a regulatory problem, it doesn't adapt fast enough # TODO Maybe just make background black and dont bother func _adjust_offset(delta: float) -> bool: var new_offset = offset if limit_left - position.x - screen_left.x > 0.1: if anim_player.is_playing(): anim_player.stop(true) new_offset.x += (limit_left - position.x - screen_left.x) / 1.5 if limit_right - position.x - screen_right.x < 0.1: if anim_player.is_playing(): anim_player.stop(true) new_offset.x += (limit_right - position.x - screen_right.x) / 1.5 if limit_top - position.y - screen_top.y > 0.001: new_offset.y += (limit_top - position.y - screen_top.y) / 1.5 if limit_bottom - position.y - screen_bottom.y < 0.001: new_offset.y += (limit_bottom - position.y - screen_bottom.y) / 1.5 #print(abs(offset.x) - abs(new_offset.x)) if abs(offset.x) > abs(new_offset.x) || abs(offset.y) > abs(new_offset.y): offset = new_offset return true else: return false func reset_limits() -> void: limit_left = original_limit_left limit_right = original_limit_right limit_bottom = original_limit_bottom limit_top = original_limit_top func _death_cam(animation_number: int = 0) -> void: if animation_number == -1: return if animation_number == 0: $CameraAnimationPlayer.play("deathCamJustZoom") if animation_number == 1: $CameraAnimationPlayer.play("deathCamLateRotation") # TODO Rename to alarm lights specially func _update_lighting_shader() -> void: if !terminal_activated: return # Props to gameendaevour # TODO get this into a central world update management system var lights = get_tree().get_nodes_in_group("light") image.lock() for i in lights.size(): var light = lights[i] # TODO To make the lighting affect all layers properly # I would have the access the global positions of nodes in different Z layers # without the projection to the global center layer. # var vtrans = get_canvas_transform() # var top_left = -vtrans.origin / vtrans.get_scale() # var vsize = get_viewport_rect().size # var t = Transform2D(0, (top_left + 0.5*vsize/vtrans.get_scale()).rotated(rotation)) image.set_pixel( i, 0, Color(light.position.x, light.position.y, light.strength, light.radius) ) image.set_pixel(i, 1, light.color) image.unlock() texture.create_from_image(image) material.set_shader_param("n_lights", lights.size()) material.set_shader_param("light_data", texture) material.set_shader_param("global_transform", get_global_transform()) material.set_shader_param("viewport_transform", get_viewport_transform())