Skip to content

Instantly share code, notes, and snippets.

@plexsoup
Created April 9, 2019 03:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plexsoup/9a825198d0f2b6cab6334e3d1724c516 to your computer and use it in GitHub Desktop.
Save plexsoup/9a825198d0f2b6cab6334e3d1724c516 to your computer and use it in GitHub Desktop.
Flocking NPC Wingman for Godot. Work in Progress.
"""
NPC starts locked in a cage, then flocks to the player when released.
It'll shoot toward the mouse cursor when the player shoots.
It should seek a new position if it's shot is blocked by the player,
but that's not 100% solved yet.
"""
extends KinematicBody2D
# Declare member variables here. Examples:
enum States { CAPTIVE, FREE }
var State = States.CAPTIVE
var velocity : Vector2 = Vector2.ZERO
var speed = 200.0
var shooting : bool = false
var blocked : bool = false
var leader : KinematicBody2D # probably global.player, but maybe someday we'll have other groups in formations.
var formation_position : Vector2
export (PackedScene) var bulletScene = load("res://Projectiles/Arrow/Arrow.tscn")
signal projectile_requested(projectile, vel, pos, rot)
signal follower_acquired(node)
# Called when the node enters the scene tree for the first time.
func _ready():
call_deferred("start")
func start():
connect("projectile_requested", global.current_level, "_on_projectile_requested")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if State == States.FREE:
velocity = aggregate_vectors()
var collision = move_and_collide(velocity * speed * delta)
if collision:
pass
func aggregate_vectors():
var returnVec : Vector2 = Vector2.ZERO
var blocked = is_blocked()
if blocked:
returnVec += get_formation_vector()
else:
returnVec += get_follow_leader_vector()
returnVec += get_avoid_allies_vector()
return returnVec.normalized()
func get_formation_vector():
# store an array of idealized positions (assuming the leader is facing Vector2.RIGHT)
# find your position, based on your order in parent node
# go toward that position.
var returnVec = Vector2.ZERO
var myPos = get_global_position()
var leaderPos = leader.get_global_position()
var mousePos = get_global_mouse_position()
var offset = 45
var formation_positions = [ Vector2(-1, -1)*offset, Vector2(-1, 1)*offset, Vector2(-2, -2)*offset, Vector2(-2, 2)*offset]
var leader_targeting_angle = Vector2.RIGHT.angle_to_point(mousePos - leaderPos)
var my_ideal_position = leaderPos + formation_positions[get_position_in_parent()].rotated(leader_targeting_angle)
formation_position = my_ideal_position
update()
returnVec += (my_ideal_position - myPos).normalized()
return returnVec
func _draw():
draw_circle(to_local(formation_position), 15, Color.antiquewhite)
func get_follow_leader_vector():
return (leader.get_global_position() - self.get_global_position()).normalized()
func get_avoid_allies_vector():
var returnVec = Vector2.ZERO
var avoid_distance = 45.0
# do this for the player and each NPC ally following the player.
var myPos = get_global_position()
if myPos.distance_squared_to(leader.get_global_position()) < avoid_distance * avoid_distance:
returnVec += (self.get_global_position() - leader.get_global_position()).normalized()
for NPC in get_parent().get_children():
# check for all sibling NPCs
if NPC != self:
if myPos.distance_squared_to(NPC.get_global_position()) < avoid_distance * avoid_distance:
returnVec += (myPos - NPC.get_global_position()).normalized()
if returnVec != Vector2.ZERO:
return returnVec.normalized()
else:
return returnVec
func _on_cage_unlocked(rescuer):
State = States.FREE
leader = rescuer # most likely global.player
if leader.has_method("_on_NPC_follower_acquired"):
connect("follower_acquired", leader, "_on_NPC_follower_acquired")
emit_signal("follower_acquired", self) # tell the player we're following them
func commence_shooting():
shooting = true
shoot()
$ReloadTimer.start()
func stop_shooting():
shooting = false
$ReloadTimer.stop()
func _on_player_started_shooting():
commence_shooting()
func _on_player_stopped_shooting():
stop_shooting()
func is_blocked():
var ray = $RayCast2D
var rot = Vector2(1,0).angle_to_point(get_global_position()-get_global_mouse_position())
var bulletVelocity = velocity + Vector2(global.options["arrow_speed"],0).rotated(rot)
ray.set_cast_to(bulletVelocity / 5)
if ray.is_colliding() == true:
var obstacle = ray.get_collider()
if obstacle:
if obstacle.is_in_group("player") or obstacle.is_in_group("player_allies"):
return true
return false
func shoot():
# check for a clean line of sight. Move if you don't have one. Then shoot.
var rot = Vector2(1,0).angle_to_point(get_global_position()-get_global_mouse_position())
var bulletVelocity = velocity + Vector2(global.options["arrow_speed"],0).rotated(rot)
if not is_blocked():
emit_signal("projectile_requested", bulletScene, bulletVelocity, get_global_position(), rad2deg(rot))
func _on_ReloadTimer_timeout():
if shooting == true:
shoot()
$ReloadTimer.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment