Constantly moving an object up and down between two points

Godot Version

4.2.1

Explanation

I’m fairly new to programming and Godot and I’m trying to recreate the shield from SpaceMaster X-7 for the Atari 2600. It’s a diamond shaped line with collision detection.

What I now have in Godot is a Node2D called ShieldControl, which contains the script.
Then I have an Area2D called ShieldCollision with a CollisionPolygon2D as a child for collision detection.
And I have a Line2D called ShieldLine, which just draws the line on the screen.

Here is the code that is in the ShieldControl script.

extends Node2D

@onready var Collision = $ShieldCollision/CollisionPolygon2D;
@onready var ShieldLine = $ShieldLine;

# Movement shield line and collision
const move_speed_x_base = 15;
const move_speed_y_base = 7;

var move_multiplier = 0.9;

var move_speed_x = move_speed_x_base * move_multiplier;
var move_speed_y = move_speed_y_base * move_multiplier;

var direction_collision = 1;
var direction_shieldline = 1;


func _physics_process(delta):
	shield_movement();


func shield_movement():
	# Shield line
	if ShieldLine.points[0].y <= 16:
		direction_shieldline = -direction_shieldline;
	elif ShieldLine.points[0].y >= 56:
		direction_shieldline = -direction_shieldline;
	
	# Collision field
	if Collision.polygon[0].y <= -56:
		direction_collision = -direction_collision;
	elif Collision.polygon[0].y >= -16:
		direction_collision = -direction_collision;
	
	# Shield line
	ShieldLine.points[0].y += direction_shieldline * move_speed_y * get_process_delta_time();
	ShieldLine.points[1].x -= direction_shieldline * move_speed_x * get_process_delta_time();
	ShieldLine.points[2].y -= direction_shieldline * move_speed_y * get_process_delta_time();
	ShieldLine.points[3].x += direction_shieldline * move_speed_x * get_process_delta_time();
	ShieldLine.points[4].y += direction_shieldline * move_speed_y * get_process_delta_time();
	
	# Collision positions
	Collision.polygon[0].y += direction_collision * move_speed_y * get_process_delta_time();
	Collision.polygon[1].x -= direction_collision * move_speed_x * get_process_delta_time();
	Collision.polygon[2].y -= direction_collision * move_speed_y * get_process_delta_time();
	Collision.polygon[3].x += direction_collision * move_speed_x * get_process_delta_time();
	

func _on_area_entered(area):
	if area.is_in_group("PlayerBullet"):
		area.queue_free();
		
	if area.is_in_group("Player"):
		area.death();

In the function shield_movement() I have the code to move the first point of the CollisionPolygon2D up and down.
If the point goes below 16 or above 56 it will flip the direction (basically flipping 1 to -1 and visa versa).

Then I apply the movement to the different points. The y-axis moves at a different speed than the x-axis, so the diamond shape will remain shaped like that. And all the points ‘listen’ to the direction flipping, so when point 0 flips, the others will as well.

The problem

When starting the game, sometimes the shield won’t start. And sometimes when it did start, it will get stuck at one of the outer ends and jitter at the position.

I know why, but I don’t know how to solve it.
When it gets stuck, it’s because it went out of the allowed position, but the move_speed wasn’t quick enough to get back into the allowed position within 1 frame or so, so it will flip the direction again and then will be out of the allowed position for ever.

At the start, it probably moves out of the allowed position immediately, so it can’t get back in and gets stuck.

I think it has something to do with the clamp function, but I can’t figure out how to apply it in my code.

Questions

Does it indeed have to do with the clamp function? And if so, how do I apply that to my code?
And what is a better way to code the constant up and down movement?

Why going so complicated route with moving individual points and constantly synchronizing visuals with collisions? Can’t you just move the whole shield altogether?

I move individual points, because that way the shield shrinks and expands.
If you look up the game SpaceMaster X-7 for the Atari 2600 on YouTube, you’ll see what I try to recreate.

And I’ve done it with lines and a polygon, because after I get this working, I need to somehow create a hole in the line where there is no collision detection, so bullets can get through.

Alright, I can see two potential solutions.

  1. The easy one: snap positions to allowed values when a point goes outside of boundaries.

Something like this:

	# Shield line
	if ShieldLine.points[0].y <= 16:
		ShieldLine.points[0].y = 16 # here
		direction_shieldline = -direction_shieldline;
	elif ShieldLine.points[0].y >= 56:
		ShieldLine.points[0].y = 56 # and here
		direction_shieldline = -direction_shieldline;
  1. Use tweens with loops for animating points. Tweens would not have a problem of going outside of boundaries or getting stuck. (given that you use them correctly).

In general, do not separate object shape from collision shape. I.e., you should have an scene called “LineShield” that inherits from Area2D and as children has both the collision shape and w/e drawing shape.

But in this example, your shield doesn’t need a big collision area. Instead, the “openings” that slide alongside the sides of the shield are the collision areas you need. Create another scene called “ShieldOpening” and it’s an Area2D with a rectangle collision area and a black sprite that’s 1 pixel, with Repeat property under Texture set to Enabled.

The “shield” in the game stays the same shape, just scales in size (grows and shrinks). Add a panel, give it 1px border, tilt it 45 degrees to become a diamond, add rectangle collision shape and also tilt it 45 degrees. Add AnimationPlayer or use tweens to change scale property of both equally and boom, you have a growing/shrinking shield.

Now add those 4 “openings” and make them move alongside one side of the diamond each. When the “opening” begins moving from it’s start, increase scale.y of it for the first 1 second or w/e until it’s big enough, then from there only change it’s position towards the end point. Then, when close to that endpoint, start decreasing scale.y.

Once you time things well, it will LOOK like the opening reaches a point and bends around it, but it’s just one opening coming to that point and shrinking, while another is starting from that point and growing. If you stare at the video of atari-version shield long eonugh, you’ll see what I’m saying. Since the background is black and you’re using a stretched black-pixel texture, it covers up the line so it looks like an opening in the shield, but it’s just black texture over the line.

Finally, you need to know when the bullet shot through the opening vs when it hit the shield. The “opening” collision shape should be let’s say 10 pixels wide. In “opening”'s _on_area_detected, add the bullet object’s ID to some array:

valid_bullets.append(area.get_instance_id()) # area is the bullet

Then a millisecond later the bullet will reach diamond shield’s collision area, so in that _on_area_entered you can write:

if not valid_bullets.has(area.get_instance_id()): area.queue_free()

If not freed, the bullet will be allowed through and so once it reaches the ship’s collision area, it’s always counted as a hit.

I hope this makes SOME sense haha