chafmere / Godot4-FPS-Template

An FPS Template for Godot 4
MIT License
57 stars 7 forks source link

Firing guns/projectiles from enemies/NPCs #1

Open GregRGM opened 4 months ago

GregRGM commented 4 months ago

I'm a little new to GDScript and I like the Template so far. I like it so much that I want to adapt the shooting logic to enemies.

Right now, I'm trying to adapt the RigidBody projectile logic to an enemy bullet but I discovered there's no logic that creates a raycast directly from the shooter itself. This makes it difficult for me to fire bullets that move in the correct direction.

Currently what happens is either the bullet fires downward and to the left of the enemy that shoots it or some random error pops up. If I could get help with this, I'd really appreciate it.

Here's code for my enemy_attack.gd:

@export var projectile_to_load: PackedScene
@export var fire_rate: float = 10
@export var damage = 1
@export var spread: Vector2 = Vector2.ZERO
@export var range =  1000

@onready var Bullet_Point = get_node("%BulletPoint")
@onready var Debug_Bullet = preload("res://Player_Controller/Spawnable_Objects/hit_debug.tscn")

func Load_Projectile():
    var _projectile:Enemy_Projectile = projectile_to_load.instantiate()
    Bullet_Point.add_child(_projectile)
    #_projectile.rotate(Vector3.UP,deg_to_rad(Bullet_Point.transform.rotation))
    #_projectile.body_entered.connect(_on_detection_area_body_entered.bind(_projectile))
    _projectile._Set_Projectile(damage,spread,range,Bullet_Point)

Here's the code I've attempted to modify in Projectile.gd:

`

func _Set_Projectile(_Damage: int = 0,_spread:Vector2 = Vector2.ZERO, _Range: int = 1000, _source: Node3D = null):
    Damage = _Damage
    Fire_Projectile(_spread,_Range,Rigid_Body_Projectile, _source)

func Fire_Projectile(_spread,_range, _proj, _source: Node3D = null):
    var Camera_Collision = Camera_Ray_Cast(_spread,_range)
    match Projectile_Type:
        "Hitscan":
            if Fire_Towards_Camera == true:
                Hit_Scan_Collision(Camera_Collision, Damage)
        "Rigidbody_Projectile":
            if Fire_Towards_Camera == true:
                Launch_Rigid_Body_Projectile(Camera_Collision[1], _proj)
            else:
                var Shooter_Collision = Shooter_Ray_Cast(_source, _spread,_range)
                Launch_Rigid_Body_Projectile(Shooter_Collision[1], _proj)

func Shooter_Ray_Cast(_source: Node3D, _spread: Vector2 = Vector2.ZERO, _range: float = 1000):
    var Ray_Origin = _source.global_transform.origin
    var Ray_End = (Ray_Origin + _source.global_transform.basis.z*_range)

    var New_Intersection = PhysicsRayQueryParameters3D.create(Ray_Origin,Ray_End)
    # New_Intersection.set_collision_mask(0b11101111)
    New_Intersection.set_collision_mask(0b11111111)

    var Intersection = get_world_3d().direct_space_state.intersect_ray(New_Intersection)

    if not Intersection.is_empty():
        var Collision = [Intersection.collider,Intersection.position]
        return Collision
    else:
        return [null,Ray_End]

Any help would be greatly appreciated as I enjoy this template quite a bit.

chafmere commented 4 months ago

Hey, Thanks for using the project and I'm happy to help where I can. I haven't really put much thought into enemies for the FPS template and left it up to the user. That being said. I don't think the script for firing projectiles is currently suitable for use outside of the player. The main reason being that it assumes that the entity firing the projectile is using the camera to determine the target.

Enemies don't need the camera. And I can see you've allowed for that. However, when I've designed enemies that shoot rigid body projectiles I've never used a ray cast to determine a direction. Since the enemies most likely already knows the position of the player it can just fire towards it. The player needs a ray cast because we don't know where the player is aiming and if simply fired forward, because of perspective, the rigid body won't land where you expect it to.

A ray cast would be useful to determine if the enemy should fire a projectile but once it knows it can. I can't think of a reason to use one. I could be wrong here, this is just how I like to do it.

here is a very simple script that I am using for a game jam at the moment. All it does is take in a target and fire the projectile at the target. You can add variation and spread easily by manipulating the target position variable.

@export var spawn_point: Marker3D
@export var attack_rate: int = 3
@export var projectile_velocity: int = 2
@export var projectile_damage: float = 10

var target: CharacterBody3D
var cooldown: bool = false

func start_attack(new_target:CharacterBody3D)->void:
    if not cooldown:
        target = new_target
        var new_rigid_body_projectile = rigid_body_projectile.instantiate()
        var target_position = target.global_position
        var direction:Vector3 = (target_position - spawn_point.global_position).normalized()
        new_rigid_body_projectile.global_transform = spawn_point.global_transform
        new_rigid_body_projectile.set_as_top_level(true)
        new_rigid_body_projectile.set_linear_velocity(direction*projectile_velocity)
        new_rigid_body_projectile.damage = projectile_damage
        get_tree().get_root().add_child(new_rigid_body_projectile)
        cooldown = true
        get_tree().create_timer(attack_rate).timeout.connect(on_attack_cooldown)

func on_attack_cooldown()->void:
    cooldown = false

For future releases I will look at abstracting the projectile class so that it can be used with more objects that need to fire a projectile.