Hi everyone ! I'm currently working on a 2d game and i would like to add a swimming logic similar to the one found in Ori and the will of the wisps. But i encounter big issues about flipping the character in water and orient the player upward while swimming in opposite direction. I've recorded what i want to achieve :

ori.zip
2MB

Can you help me please ?

  • Toxe and FreakyGoose replied to this.
  • SilverWolvesGames likes this.
  • SilverWolvesGames

    This code is an excerpt of the player CharacterBody2D script from one of my projects. It worked well enough with an analog controller for what I used it for but it can surely be optimised.

    var swim_speed: float = 250.0
    var swim_acceleration: float = 0.1
    var water_resistance: float = 0.1
    
    func _physics_process(delta):
           ##Get player input. Make sure you set these in the input maps
    	var input_direction: Vector2 = Vector2(Input.get_axis("Left","Right"), Input.get_axis("Up","Down")).normalized()
    
    	if input_direction.x > 0:
    		input_direction.x = 1
    	elif input_direction.x < 0:
    		input_direction.x = -1
    
    	if input_direction.length() > 0:
    		## Add 75 degrees to accommodate for player rotation head to be in front
    		var angle = input_direction.angle() + deg_to_rad(75)
    
                     ##Rotate agent to face input direction
    		rotation = lerp_angle(rotation, angle, 0.2)
    
                    ##Smoothly move player along the input direction with a slight acceleration
    		velocity = lerp(velocity, input_direction * swim_speed, swim_acceleration)
    
    		##Turn and flip agent depending on input direction
    		if input_direction.length_squared() >= 1 and !is_input_direction_equal_agent_direction(input_direction.x):
    			scale.x = input_direction.x
    	else:
                    ##If no input decelerate player movement
    		velocity = velocity.lerp(Vector2.ZERO, water_resistance)
    
    	set_velocity(velocity)
    	move_and_slide()
    
    func is_input_direction_equal_agent_direction(x_input_dir) -> bool:
            ##Check if player is currently facing input direction
    	if x_input_dir == scale.x:
    		return true
    	else:
    		return false
    SilverWolvesGames changed the title to How to orient player properly in water properly like Ori ? .

    SilverWolvesGames

    This code is an excerpt of the player CharacterBody2D script from one of my projects. It worked well enough with an analog controller for what I used it for but it can surely be optimised.

    var swim_speed: float = 250.0
    var swim_acceleration: float = 0.1
    var water_resistance: float = 0.1
    
    func _physics_process(delta):
           ##Get player input. Make sure you set these in the input maps
    	var input_direction: Vector2 = Vector2(Input.get_axis("Left","Right"), Input.get_axis("Up","Down")).normalized()
    
    	if input_direction.x > 0:
    		input_direction.x = 1
    	elif input_direction.x < 0:
    		input_direction.x = -1
    
    	if input_direction.length() > 0:
    		## Add 75 degrees to accommodate for player rotation head to be in front
    		var angle = input_direction.angle() + deg_to_rad(75)
    
                     ##Rotate agent to face input direction
    		rotation = lerp_angle(rotation, angle, 0.2)
    
                    ##Smoothly move player along the input direction with a slight acceleration
    		velocity = lerp(velocity, input_direction * swim_speed, swim_acceleration)
    
    		##Turn and flip agent depending on input direction
    		if input_direction.length_squared() >= 1 and !is_input_direction_equal_agent_direction(input_direction.x):
    			scale.x = input_direction.x
    	else:
                    ##If no input decelerate player movement
    		velocity = velocity.lerp(Vector2.ZERO, water_resistance)
    
    	set_velocity(velocity)
    	move_and_slide()
    
    func is_input_direction_equal_agent_direction(x_input_dir) -> bool:
            ##Check if player is currently facing input direction
    	if x_input_dir == scale.x:
    		return true
    	else:
    		return false

      FreakyGoose
      I've currently imlemented the code you've shown and it works quite well, but i've an issue.
      When i'm swimming upward or downward, without applying any horizontal input the sprite disappears. Can you help me please ?

        I'm not sure what's happening. You will need to investigate it. You should try logging the sprite positions to see what is happening.

          13 days later

          FreakyGoose

          I have finally achieved completing swimming movement :

          class_name WaterState
          
          extends PlayerMovementState
          
          var input_direction := Vector2.ZERO
          var is_swimming_fast := false
          var exit_water := false
          
          func enter():
          	if player.facing_direction == -1:
          		player.flip_controller(1)
          		player.sprite.scale.x = -1
          	elif player.facing_direction == 1:
          		player.flip_controller(1)
          		player.sprite.scale.x = 1
          		
          
          func update(_delta):
          	if player.is_on_surface && (INPUT.JUMP || is_swimming_fast):
          		switch_state("JumpPlayerState")
          		
          	set_swim_facing_direction(player.swim_facing_dir)
          	
          	
          func physics_update(delta):
          	set_anims()
          	player.handle_surface(delta)
          	
          	set_input_normalized(delta)	
          	
          		
          func exit():
          	player.sprite.rotation = 0.0
          	player.sprite.scale.x = 1
          	is_swimming_fast = false
          	exit_water = true
          
          
          func set_swim_facing_direction(input: float) -> void:
          	if input > 0 && player.velocity.x < 0:
          		player.swim_facing_dir *= -1
          	elif input < 0 && player.velocity.x > 0:
          		player.swim_facing_dir *= -1
          
          
          func set_anims():
          	if (player.is_on_surface || player.can_jump_out_of_water()) && INPUT.X == 0:
          		play_anim("swim_surf_idle")
          	elif (player.is_on_surface || player.can_jump_out_of_water()) && INPUT.X != 0:
          		play_anim("swim_surf")
          	elif player.is_in_water() && INPUT.X == 0:
          		play_anim("swim_idle")
          	elif player.is_in_water() && INPUT.X != 0:
          		play_anim("swim")
          
          
          func set_input_normalized(delta : float):
          	input_direction = Vector2(Input.get_axis("run_l", "run_r"), Input.get_axis("climb", "crouch")).normalized()	
          	
          	if input_direction.x > 0:
          		input_direction.x = 1
          	elif input_direction.x < 0:
          		input_direction.x = -1
          		
          	if input_direction.length() > 0:
          		var angle = input_direction.angle() + deg_to_rad(90.0)
          		if !(player.is_on_surface || player.can_jump_out_of_water()):
          			player.sprite.rotation = lerp_angle(player.sprite.rotation, angle, 0.1)
          			
          			if input_direction.length_squared() >= 1 and !is_input_direction_equal_agent_direction(input_direction.x):
          				player.sprite.scale.x = player.swim_facing_dir
          		else:
          			player.sprite.scale.x = player.swim_facing_dir
          			player.sprite.rotation = lerp_angle(player.sprite.rotation, 0.0, 0.4)
          
          		set_swim_velocity(delta)	
          	else:
          		player.velocity = lerp(Vector2.ZERO, player.velocity, pow(2, -player.data.swim_control * delta))
          		
          	
          func set_swim_velocity(delta : float):
          	if player.is_on_surface || player.can_jump_out_of_water():
          		player.apply_horizontal_velocity(player.data.swim_speed, delta)
          		if INPUT.Y >= 0:
          			player.reset_vertical_velocity()
          		elif INPUT.Y < 0:
          			player.velocity = lerp(player.velocity, input_direction * Data.swim_speed, Data.swim_accel * delta)
          	elif player.is_in_water():
          		if INPUT.SPRINT:
          			is_swimming_fast = true
          			player.velocity = lerp(player.velocity, input_direction * Data.swim_speed * 2, Data.swim_accel * delta)
          		elif !INPUT.SPRINT || INPUT.SPRINT_END:
          			is_swimming_fast = false
          			player.velocity = lerp(player.velocity, input_direction * Data.swim_speed, Data.swim_accel * delta)
          	
          		
          func is_input_direction_equal_agent_direction(x_input_dir) -> bool:
          	##Check if player is currently facing input direction
          	if x_input_dir == player.sprite.scale.x:
          		return true
          	else:
          		return false