Blobby/src/Actors/BlobbyCam.gd

270 lines
11 KiB
GDScript

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 levelState := $"%LevelState"
onready var signalManager := $"%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
signalManager.connect("terminal_activated", self, "_on_SignalManager_terminal_activated")
signalManager.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(!levelState.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):
$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())