make Tank turret collide with environment

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By ArthurER

I am working on a project stemming from the KidsCanCode tank tutorial. i have done a few projects now on this but am just starting a new one to resolve certain issues I came across. getting the project to go in the direction a I want. my aim is in the direction of RTS like C&C

the issue I am stuck on right now has to do with tank turrets not colliding with the environment , the project I am started is just in the beginning stage. I have made a kinematicbody2d vehicle with a a script that I think can apply to both player and enemy , and a kinematicbody2d turret with its own script to handle a turret type of targeting that I think will be useful for either stationary or mobile turrets.

I have made a player tank inheriting from the vehicle body then instanced the turret to the player tank. i have this working the vehicle body is able to drive to click locations and the turret is able to target.

what I cant seem to get to happen is the turret react to collision with an obstacle.

my test scene is just a node 2d, a staticbody2d with a collision shape and the player tank, the only control I have on the tank is click to location, the obstacle is set in a layer that the turret will target and if I click to a location close to the obstacle the tank will drive up to it the turret will target but the collision shape on the muzzle of the turret will be ignored and the turret passes right into the obstacle, the tank collides with the obstacle just fine , and I know this time I have everything in the right collision layers.

I am kind of stumped now as to how to get the turret to act the way I am trying to get it to act.

I got the collision to occur, it was not happening because there is no move and slide called for that body, I put in move and slide with velocity as an empty Vector 2 , now the turret collides but gets pushed off the tank :-\

ArthurER | 2020-09-26 22:42

:bust_in_silhouette: Reply From: ArthurER

I don’t think that Godot is meant to make a collision in the way I was looking for

I did get close to what I was looking for. I changed the node type for the turret to node2d , got rid of the turret collision shape , added a raycast2d, on collision with the obstacle call to get collision normal().angle() subtract that from the turrets current angle then add the result to the target angle. this keeps the turret out of obstacles and makes it react the way I wanted. it vibrates against the obstacle because it is stuck outside of its aim tolerance but is close enough for now.

:bust_in_silhouette: Reply From: RaebaldKashban

Hey ArthurER:

I was able to get something working that I think approximates what you’re looking for. The key was to use a single KinematicBody2D with multiple collision shapes so that all the collision shapes are updated with the same move_and_slide() function. Here’s what my tank scene looks like:

KinematicBody2D (Name: "TankBody")
  - Sprite (for the tank body)
  - CollisionShape2D (Name: "TankBodyShape", for the tank body)
  - CollisionShape2D (Name: "TankTurretShape", for the tank turret)
    > Sprite (for tank turret)
  - Camera2D

My obstacles are also pretty simple:

StaticBody2D
  - Sprite
  - CollisionShape2D

The messy part is the tank script which I will copy in a separate answer for you to look at. You’re welcome to copy it and use it in your project however you see fit.

Your first problem was that when your tank collided with an obstacle, the turret got displaced from the tank’s body. The way I solved this issue was to make the turret into a second CollisionShape2D so that Godot treats it as part of the same kinematic body. When you structure the tank in this way, both the tank’s body and turret will stop when the turret collides with an obstacle. Unfortunately, making rotation work was a bit trickier, but I’ll spare you the details and let you see for yourself.

The script below is mildly customizable. If you want the tank to move when the turret is rotated against an object, set “tank_move_when_turret_rotates” to true. Otherwise, when it’s false, the turret will stop rotating when it hits an object and only displace the tank by a small amount. It also lets you adjust how quickly the tank rotates towards your mouse cursor, how fast the tank moves, and how far the turret is from the main body of the tank. The script is designed to make the turret rotate around the center of the tank, so position your CollisionShape2D accordingly.

Note: This all works on one collision layer.

I tried doing a write up explaining how this works, but it’s late and I am sleepy. I hope this gives you a good base to work off of. If you have any questions feel free to let me know! Happy programming.

	

		

	

Here’s the script, free for your perusal and use. Make sure you name your nodes as I named them above or you’ll run into trouble. :slight_smile:

extends KinematicBody2D

export var movement_speed = 150.0
var turret_offset = 40.0  #the radius of the circle that the turret will sweep through
var turret_rotation_speed = 30.0 #how quickly the turret rotates towards the cursor
export var tank_move_when_turret_rotates = false #whether turret rotation will cause the tank to move
export var dot_product_test_sensitivity = 0 #keep as is

func _ready():
	$TankTurretShape.position.x = turret_offset
	pass

func _physics_process(delta): #process tank movement
	var tank_moving = false
	if Input.is_action_pressed("ui_up"):
		move_and_slide(Vector2(0.0,-1.0)*movement_speed)
		tank_moving = true
	if Input.is_action_pressed("ui_down"):
		move_and_slide(Vector2(0.0,1.0)*movement_speed)
		tank_moving = true
	if Input.is_action_pressed("ui_left"):
		move_and_slide(Vector2(-1.0,0.0)*movement_speed)
		tank_moving = true
	if Input.is_action_pressed("ui_right"):
		move_and_slide(Vector2(1.0,0.0)*movement_speed)
		tank_moving = true
	if tank_moving == false:
		move_and_slide(Vector2(0.0,0.0))
	
	var collisions = [] #set up variables to receive collision information
	var collided_with_turret = false
	var turret_collision_global_position
	var turret_collision_relative_to_tank
	var obstacle_turret_collided_with
	var obstacle_turret_collided_with_global_position
	var obstacle_turret_collided_with_position_relative_to_tank
	for i in get_slide_count():
		if get_slide_collision(i).local_shape.name == "TankTurretShape":
			collided_with_turret = true
			turret_collision_global_position = get_slide_collision(i).position 
			obstacle_turret_collided_with = get_slide_collision(i).collider
			obstacle_turret_collided_with_global_position = obstacle_turret_collided_with.global_position
			obstacle_turret_collided_with_position_relative_to_tank = obstacle_turret_collided_with_global_position - self.global_position
	if turret_collision_global_position != null:
		turret_collision_relative_to_tank = turret_collision_global_position - self.global_position
		turret_collision_relative_to_tank = turret_collision_relative_to_tank.normalized()

	if tank_move_when_turret_rotates == false: #handle turret rotation
		if collided_with_turret == false:
			var mouse_position = get_viewport().get_mouse_position()
			var global_tank_position = get_global_transform_with_canvas().get_origin()
			var target_turret_vector = mouse_position - global_tank_position
			var current_turret_vector = $TankTurretShape.position.normalized()*turret_offset
			
			target_turret_vector = target_turret_vector.normalized()*turret_offset
			var neutral_turret_position = Vector2(1.0,0.0)
			var target_turret_rotation_angle = target_turret_vector.angle_to(neutral_turret_position)
			
			var position_displacement_direction = (target_turret_vector - current_turret_vector).normalized()
			var final_position_displacement_vector = position_displacement_direction*turret_rotation_speed*delta
			
			var final_turret_position_this_physics_frame = current_turret_vector + final_position_displacement_vector
			var final_turret_rotation_angle = final_turret_position_this_physics_frame.angle_to(neutral_turret_position)
				
			$TankTurretShape.rotation_degrees = rad2deg(-final_turret_rotation_angle) #rotate the turret accordingly
			$TankTurretShape.position = final_turret_position_this_physics_frame
		else:
			var mouse_position = get_viewport().get_mouse_position()
			var global_tank_position = get_global_transform_with_canvas().get_origin()
			var target_turret_vector = mouse_position - global_tank_position
			var current_turret_vector = $TankTurretShape.position.normalized()*turret_offset
			
			target_turret_vector = target_turret_vector.normalized()*turret_offset
			var neutral_turret_position = Vector2(1.0,0.0)
			var target_turret_rotation_angle = target_turret_vector.angle_to(neutral_turret_position)
			var current_turret_rotation_angle = $TankTurretShape.position.normalized().angle_to(neutral_turret_position)
			
			var position_displacement_direction = (target_turret_vector - current_turret_vector).normalized()
			var final_position_displacement_vector = position_displacement_direction*turret_rotation_speed*delta
			
			var final_turret_position_this_physics_frame = current_turret_vector + final_position_displacement_vector
			var final_turret_rotation_angle = final_turret_position_this_physics_frame.angle_to(neutral_turret_position)
			
			#determine if we're rotating the turret away from or toward an obstacle
			var turret_turning_toward_obstacle
			
			var test_rotated_vector = current_turret_vector.rotated(final_turret_rotation_angle - current_turret_rotation_angle)
			var dot_product_test_current = current_turret_vector.normalized().dot(obstacle_turret_collided_with_position_relative_to_tank.normalized())
			var dot_product_test_new_rotated = test_rotated_vector.normalized().dot(obstacle_turret_collided_with_position_relative_to_tank.normalized())
		
			if dot_product_test_new_rotated < dot_product_test_current:
				turret_turning_toward_obstacle = true
			else:
				turret_turning_toward_obstacle = false
			if turret_turning_toward_obstacle == false:
				$TankTurretShape.rotation_degrees = rad2deg(-final_turret_rotation_angle)
				$TankTurretShape.position = final_turret_position_this_physics_frame
			else:
				pass
	else:
		var mouse_position = get_viewport().get_mouse_position()
		var global_tank_position = get_global_transform_with_canvas().get_origin()
		var target_turret_vector = mouse_position - global_tank_position
		var current_turret_vector = $TankTurretShape.position.normalized()*turret_offset
		
		target_turret_vector = target_turret_vector.normalized()*turret_offset
		var neutral_turret_position = Vector2(1.0,0.0)
		var target_turret_rotation_angle = target_turret_vector.angle_to(neutral_turret_position)
		
		var position_displacement_direction = (target_turret_vector - current_turret_vector).normalized()
		var final_position_displacement_vector = position_displacement_direction*turret_rotation_speed*delta
		
		var final_turret_position_this_physics_frame = current_turret_vector + final_position_displacement_vector
		var final_turret_rotation_angle = final_turret_position_this_physics_frame.angle_to(neutral_turret_position)
			
		$TankTurretShape.rotation_degrees = rad2deg(-final_turret_rotation_angle) #rotate the turret accordingly
		$TankTurretShape.position = final_turret_position_this_physics_frame
	

	

RaebaldKashban | 2020-09-27 04:10

thanks I appreciate the reply and efforts, I mentioned above that I had found a solution that I am reasonably happy with, it could perhaps stand a little tweaking but for now I am putting that aside and going on to learning the box selecting method for selecting multiple units. my project is not that similar to yours I don’t drive or aim the units, just point them at where to go, sort of aiming for a Command and Conquer style of RTS.

ArthurER | 2020-09-27 08:39