I need advice on how to make a decent weapon switching system in Godot, any advice?

AudiobellumAudiobellum Posts: 27Member
edited June 10 in Programming

I'm a beginner to Godot and I want to develop the skills necessary for making a decent weapon switching system in Godot for a first person shooter demo I'm trying to make; ideally I'll learn what it takes to make a first person shooter like Wolfenstein 3d or Doom with 3d models. I've already made a first person controller, I know how to make the guns fire and I also made a weapon switching system. However, the weapon switching system seems amateurish and I want it to be more improved. In order to give you guys of where I'm at, I'll say that I've been following tutorials by the Youtuber Garbaj, I've followed his tutorial on hitscans weapons, making a basic first person controller and switching weapons. I've also been following tutorials by Yotuber PandaDev on how to make a hitscan weapon and how to make an ammo system. PandaDev's videos are similiar to that of Garbaj but, he also taught me how to seamlessly integrate animations and interface element into the demo I'm working on. In order to understand where I am at this stage, I'd recommend look at the videos that have influenced me as it might be helpful in helping you to help me.
Here is my code for weapon switching (this piece of code is from the player controller script):

    func weapon_select(): #Weapon Button Switching Code



        if Input.is_action_just_pressed("weapon1") and not current_weapon == 1:
            anim_player.play("pistol_equip")
            current_weapon = 1
        elif Input.is_action_just_pressed("weapon2")and not current_weapon == 2:
            anim_player.play("shot_equip")
            current_weapon = 2
        elif Input.is_action_just_pressed("weapon3")and not current_weapon == 3:
            anim_player.play("rifle_equip")
            current_weapon = 3


        if current_weapon == 1:
            pistol.visible = true
            pistol.shoot()
        else:
            pistol.visible = false

        if current_weapon == 2:
            shotgun.visible = true
            shotgun.shoot()
        else:
            shotgun.visible = false

        if current_weapon == 3:
            rifle.visible = true
            rifle.shoot()
        else:
            rifle.visible = false

Now here is the code I use for shooting the guns (this works well with the weapon switching system code):

extends Spatial

class_name weapon

export var damage = 10
export var ammo = 5
export var max_ammo = 5
export var fire_anim = "pistol_fire"
export var gun_action = "pistol fire"
export var reload_anim = "pistol_reload"

onready var aimcast = $"../../../Head/Camera/AimCast"
onready var anim_player = $"../../../AnimationPlayer"
onready var ammo_text = $"../../../Head/Camera/Control/ammo_count"

func _ready():
    pass





func shoot():
    if Input.is_action_pressed("fire"):
        if ammo > 0:
            if not anim_player.is_playing():
                anim_player.play(fire_anim)
                ammo -= 1
                if aimcast.is_colliding():
                    var target = aimcast.get_collider()
                    if target.is_in_group("Enemy"):
                        target.health -= damage
        print(gun_action)
#Reloading Code underneath here!
    ammo_text.text = String(ammo)
    if ammo <= 0:
        anim_player.play(reload_anim)
        ammo = max_ammo
    if Input.is_action_just_pressed("reload") and ammo < 5:
        anim_player.play(reload_anim)
        ammo = max_ammo

What I did is I applied the shooting script to one of the guns(the pistol) and then I make the other guns inherit the exported variables from it in their own respect scripts and then swap out a few things to make each gun feel distinct. So now what do I want improved? Well, I want to implement weapon switching animations to my weapon switching code. However, I don't know how to completely do it. I've managed to implement weapon equipping animation but, only when it comes to switching the weapons with buttons, not when it comes to using the mouse wheel. I've also don't know how I'd integrate weapon unequipping animations into the code. The Youtuber Skyvastern has a tutorial on creating a fps weapon manager that does indeed use equipping and unequipping animations but, I don't want to follow his tutorial again as his weapon manager isn't to my liking (it's not old school as I want). I also don't know how to use Skyvastern's animation code in the weapon switching system created by Garbaj, as I don't really understand Skyvastern's animation code. Any serious help would be greatly appreciated in this endeavor. I've gone elsewhere for help on the internet. However, as I noob, the advice I received previously was difficult to understand. They're other things that I also need help with but, they're a lot of tutorials on those subject that I haven't explored yet. So for now, I'm focusing strictly on getting this weapon switching system improved. Again, any help would be appreciated.

«1

Comments

  • cyberealitycybereality Posts: 1,619Moderator
    edited June 10

    So, the first thing I would do is abstract the weapon into a class object. It can have the data needed for the use of the weapon (weapon stats) as well as a reference to the 3D model and the function that can be performed (shooting, reloading, etc.). When you switch to a new weapons, you can hide the current weapon at that point or do nothing if that weapon is equipped (avoiding all the if statements).

    func weapon_select():
        if Input.is_action_just_pressed("weapon1"):
            switch_to_weapon(pistol_weapon)
        elif Input.is_action_just_pressed("weapon2"):
            switch_to_weapon(shotgun_weapon)
    
    func switch_to_weapon(next_weapon):
        if next_weapon == current_weapon:
            return
        current_weapon.model.visible = false
        next_weapon.model.visible = true
        anim_player.play(next_weapon.animation)
        next_weapon.shoot()
        current_weapon = next_weapon
    
  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    So, the first thing I would do is abstract the weapon into a class object. It can have the data needed for the use of the weapon (weapon stats) as well as a reference to the 3D model and the function that can be performed (shooting, reloading, etc.). When you switch to a new weapons, you can hide the current weapon at that point or do nothing if that weapon is equipped (avoiding all the if statements).

    func weapon_select():
        if Input.is_action_just_pressed("weapon1"):
            switch_to_weapon(pistol_weapon)
        elif Input.is_action_just_pressed("weapon2"):
            switch_to_weapon(shotgun_weapon)
    
    func switch_to_weapon(next_weapon):
        if next_weapon == current_weapon:
            return
        current_weapon.model.visible = false
        next_weapon.model.visible = true
        anim_player.play(next_weapon.animation)
        next_weapon.shoot()
        current_weapon = next_weapon
    

    I tried this code but, I got an error I don't know to fix:

  • cyberealitycybereality Posts: 1,619Moderator

    That was just pseudo-code. You have to create a Weapon class and give it those properties.

  • cyberealitycybereality Posts: 1,619Moderator

    Here is an example of what I mean. This would be in its own file, like Weapon.gd

    class_name Weapon
        var model : MeshInstance
        var animation : String
    
        func shoot():
            pass
    
        func reload():
            pass
    

    Then in the main class you can create a weapon.

    var current_weapon
    var pistol_weapon
    
    func _ready():
        pistol_weapon = Weapon.new()
        pistol_weapon.model = get_node("Path/To/Pistol")
        pistol_weapon.animation = "pistol_equip"
        current_weapon = pistol_weapon
    
  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    That was just pseudo-code. You have to create a Weapon class and give it those properties.

    Luckily for me. I got my unedited code backed up.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    Here is an example of what I mean. This would be in its own file, like Weapon.gd

    class_name Weapon
        var model : MeshInstance
        var animation : String
    
        func shoot():
            pass
        
        func reload():
            pass
    

    So basically, does this mean that I should just break up the code for firing and reloading into two seperate functions within the pistol script I already have?

    Then in the main class you can create a weapon.

    var current_weapon
    var pistol_weapon
    
    func _ready():
        pistol_weapon = Weapon.new()
        pistol_weapon.model = get_node("Path/To/Pistol")
        pistol_weapon.animation = "pistol_equip"
        current_weapon = pistol_weapon
    

    I'm a bit confused by this part. I'm going to try to explain what I understand. If my memory serves me right, pistol_weapon.model means get/add model to the script of the pistol_weapon script right? Also, does the get_node do the same thing as the $? I can understand what this pseudo-code is getting at but, I'm still a bit confused. Perhaps it would help if I showed you all the variables I have for the weapon code I use in the main player script.

    var weapon = ["pistol", "shotgun", "rifle"]#this is useless(I really don't know why I have this here)
    
    var current_weapon_index = 0
    
    var current_weapon = 1
    
    
    
    onready var head = $Head
    onready var aimcast = $Head/Camera/AimCast
    onready var pistol = $Head/Hand/Pistol
    onready var shotgun = $Head/Hand/Shotgun
    onready var rifle = $Head/Hand/Rifle
    onready var anim_player = $AnimationPlayer
    
  • cyberealitycybereality Posts: 1,619Moderator

    get_node() is the same as $ ($ is actually just an alias/macro for get_node()). My method is using a class to hold the data, which makes things easier as you can just call functions on the weapon object, rather than having like 3 different if statements with the same code. current_weapon becomes an object (rather than a number) so you can call the function or properties on it. In your case, you can keep the onready variables for getting the node, that you will need either way. But here you can reference the nodes you already saved.

    func _ready():
        pistol_weapon = Weapon.new()
        pistol_weapon.model = pistol
        pistol_weapon.animation = "pistol_equip"
        current_weapon = pistol_weapon
    

    The shoot and reload functions were examples. You could also just call that from your main function, but it would require less code if you handled the visual part of the shooting/reloading in the class. You could store another String with the shoot/reload animation and call the animation from within the Weapon class. The actual shooting logic should still be in the main player class (like if you need to shoot a ray and check for collision, the makes sense to keep in the player class).

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    get_node() is the same as $ ($ is actually just an alias/macro for get_node()). My method is using a class to hold the data, which makes things easier as you can just call functions on the weapon object, rather than having like 3 different if statements with the same code. current_weapon becomes an object (rather than a number) so you can call the function or properties on it. In your case, you can keep the onready variables for getting the node, that you will need either way. But here you can reference the nodes you already saved.

    func _ready():
        pistol_weapon = Weapon.new()
        pistol_weapon.model = pistol
        pistol_weapon.animation = "pistol_equip"
        current_weapon = pistol_weapon
    

    The shoot and reload functions were examples. You could also just call that from your main function, but it would require less code if you handled the visual part of the shooting/reloading in the class. You could store another String with the shoot/reload animation and call the animation from within the Weapon class. The actual shooting logic should still be in the main player class (like if you need to shoot a ray and check for collision, the makes sense to keep in the player class).

    I tried this could and got another error regarding the "Weapon" identifier. I suspect this error has something to do with the node structure (the code for the shooting and reloading is the pistol script) but, I'm not certain. The Player2 script is just a copy of my original Player script.

  • cyberealitycybereality Posts: 1,619Moderator

    You need a Weapon.gd file. With the code from the post which starts with:

    class_name Weapon
    

    That defines a class, then you can use the Weapon class elsewhere.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    You need a Weapon.gd file. With the code from the post which starts with:

    class_name Weapon
    

    That defines a class, then you can use the Weapon class elsewhere.

    I'm not sure if this was the right decision but I created an empty spatial node called Weapon, created a script for it called "Weapon.pd", pasted cut and paste all the code from the pistol script into it (as that was the script that originally had theclass_name weapon)and let all the scripts for all the guns inherit from it. So now I have different error:

  • cyberealitycybereality Posts: 1,619Moderator

    The Weapon class is just a text file. You don't need to attach it to anything (and it's better if you don't, it's just for in memory values, not visual objects). At the very least you will need this in the class.

    class_name Weapon
    var model : MeshInstance
    var animation : String
    

    However, I'm not sure what your level of programming experience is, or your familiarity with GDScript. I may be a bit off base with my recommendation. I think this is an okay design, but I can come up with something easier if that makes more sense for you.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    The Weapon class is just a text file. You don't need to attach it to anything (and it's better if you don't, it's just for in memory values, not visual objects). At the very least you will need this in the class.

    class_name Weapon
    var model : MeshInstance
    var animation : String
    

    However, I'm not sure what your level of programming experience is, or your familiarity with GDScript. I may be a bit off base with my recommendation. I think this is an okay design, but I can come up with something easier if that makes more sense for you.

    Most of everything I know about using GDscript, I learnt from watching Garbaj's Youtube video on the subject. All of his video's are incredibly short and easy to follow in the sense that almost everyone can follow his instructions and get results. You wouldn't mind if I just gave you a copy of my project if it helps, right? I mean, it's just a demo for teaching purposes only. It would give a better indication of what I understand and what I don't understand where GDscript is concerned. I'll attach my original script to the player but, if you want to see what the ended version looks like ,it's named Player2. Anyway, I'm asking for permission before I send it.

  • AudiobellumAudiobellum Posts: 27Member

    The only Garbaj Youtube tutorials I followed were the one's that I mentioned in first post on this thread; I haven't watched all of them. I've watched other GDscript tutorials but, I find myself going back to Garbaj's tutorials the most and that's why it's easier to recall concepts in his video; other concepts I've learnt elsewhere are harder to recall.

  • AudiobellumAudiobellum Posts: 27Member

    Anyway, here is a zip file of what my demo looks like. It doesn't have any proper character models yet (or ever as it's just a demo I'm using to teach myself with).

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    The Weapon class is just a text file. You don't need to attach it to anything (and it's better if you don't, it's just for in memory values, not visual objects). At the very least you will need this in the class.

    class_name Weapon
    var model : MeshInstance
    var animation : String
    

    However, I'm not sure what your level of programming experience is, or your familiarity with GDScript. I may be a bit off base with my recommendation. I think this is an okay design, but I can come up with something easier if that makes more sense for you.

    Okay, I've used all the var model: MeshInstance
    var animation: String

    Now I get different errors

    To me, getting this weapon switching system right is more important than learning anything else in Godot, as there is an abundance of Youtube tutorials on other Godot topics while there is very view tutorials on Youtube that tackle the issue I've raised specifically.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    The Weapon class is just a text file. You don't need to attach it to anything (and it's better if you don't, it's just for in memory values, not visual objects). At the very least you will need this in the class.

    class_name Weapon
    var model : MeshInstance
    var animation : String
    

    However, I'm not sure what your level of programming experience is, or your familiarity with GDScript. I may be a bit off base with my recommendation. I think this is an okay design, but I can come up with something easier if that makes more sense for you.

    Do you know of any tutorial or resource that could teach me how to find the solution on my opinion?

  • cyberealitycybereality Posts: 1,619Moderator

    Not sure about that. I haven't really used many tutorials, I've mostly just been reading the documentation and testing features out myself.

  • DaveTheCoderDaveTheCoder Posts: 277Member

    @Audiobellum said:
    Anyway, here is a zip file of what my demo looks like. It doesn't have any proper character models yet (or ever as it's just a demo I'm using to teach myself with).

    I downloaded that .rar archive. I was able to view the filenames inside it, but was unable to extract the files.
    I'm using Linux (Pop!_OS 20.04).
    Can you provide a .tar.gz or .zip archive?

  • AudiobellumAudiobellum Posts: 27Member

    @DaveTheCoder said:

    @Audiobellum said:
    Anyway, here is a zip file of what my demo looks like. It doesn't have any proper character models yet (or ever as it's just a demo I'm using to teach myself with).

    I downloaded that .rar archive. I was able to view the filenames inside it, but was unable to extract the files.
    I'm using Linux (Pop!_OS 20.04).
    Can you provide a .tar.gz or .zip archive?

    I used a website to convert the rar to a zip file. Anyway, I've went over to Garbaj's discord to ask for some advice on how to fix the problem I had. The advice I got implied that my solution will involve yield(I don't know what does completely); more specifically someone showed me this pseudo-code(yield($animpath, "animation_finished"). It was also recommened that I used an array modifier. You can try and help me to find a solution but, I'll also be trying to find a solution on my own.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    Not sure about that. I haven't really used many tutorials, I've mostly just been reading the documentation and testing features out myself.

    Cool. I originally avoided reading the documentation on the official website as I've only had constant access to the internet recently relatively recently and I didn't know where to find a PDF version of it, so that I can download. I'll check it out.

  • AudiobellumAudiobellum Posts: 27Member

    @cybereality said:
    Not sure about that. I haven't really used many tutorials, I've mostly just been reading the documentation and testing features out myself.

    Do you know where I can download a pdf of Godot's documentation?

  • CalinouCalinou Posts: 686Admin Godot Developer

    Do you know where I can download a pdf of Godot's documentation?

    PDF downloads are no longer offered because the PDF generated by Sphinx was almost unreadable. Instead, you can use the HTML downloads provided here: https://docs.godotengine.org/en/stable/ (see the section about HTML at the top)

  • AudiobellumAudiobellum Posts: 27Member

    @Calinou said:

    Do you know where I can download a pdf of Godot's documentation?

    PDF downloads are no longer offered because the PDF generated by Sphinx was almost unreadable. Instead, you can use the HTML downloads provided here: https://docs.godotengine.org/en/stable/ (see the section about HTML at the top)

    Thanks

  • cyberealitycybereality Posts: 1,619Moderator

    Okay, I downloaded the project and made a bunch of changes. Hopefully you can look through and it will help you understand what I did.
    https://drive.google.com/file/d/1V6kZ-OjR4GaqWlRqX01LCnVFUEE982zW/view?usp=sharing

  • AudiobellumAudiobellum Posts: 27Member
    edited June 11

    @cybereality said:
    Okay, I downloaded the project and made a bunch of changes. Hopefully you can look through and it will help you understand what I did.
    https://drive.google.com/file/d/1V6kZ-OjR4GaqWlRqX01LCnVFUEE982zW/view?usp=sharing

    Thanks. I'm starting to understand how the changes work the more I make comparisons between what I have and the changes you made. I think I understand what you've down to the Weapon.pd in it's entirety. I never knew that wheel up and wheel down could have been established as inputs, cool. It seems that the more I think about the code, is the more I understand what's going.

    The code underfunc setup_weapons(): gets the variable for each weapon in the Weapon.pd script and then set it's string equal to it's respective equip animation. I think I understand the code underfunc switch_weapon(): but, I don't understand what the != does in if current_weapon != weapons[weapon_index]: . Judging by the context, I think it means not or =/= in Godot.

    When it comes to func switch weapon(): it seems that all the code under if current_weapon != weapons[weapon_index]: executes itself in a sequential matter, (one after the other) as oppose to all at the same time.

    When it comes to the code for switching weapons with mouse wheel, I don't understand what weapon_index = (weapon_index + 1) % weapons.size() works, more specifically it's the % weapons.size() I don't understand. Anyway, I'm going to do my best to understand these changes and then let them inform how I do coding in GDscript in the future.

  • DaveTheCoderDaveTheCoder Posts: 277Member

    weapon_index = (weapon_index + 1) % weapons.size()

    That's a common way of cycling through a list.
    The modulus operator (%) means "remainder upon division by".

  • Erich_LErich_L Posts: 45Member

    give me enough time I'll think of a way to use AStar to do it B) lolol

  • AudiobellumAudiobellum Posts: 27Member

    @DaveTheCoder said:

    weapon_index = (weapon_index + 1) % weapons.size()

    That's a common way of cycling through a list.
    The modulus operator (%) means "remainder upon division by".

    I just discovered that I don't know what the "size" in weapons.size() means. Could you explain that to me?

  • DaveTheCoderDaveTheCoder Posts: 277Member

    It's the number of elements in the weapons array (assuming it's an array).
    https://docs.godotengine.org/en/stable/classes/class_array.html#class-array-method-size

  • AudiobellumAudiobellum Posts: 27Member
    edited June 12

    Okay, so where I'm at now is that I've managed to successfully implement the "wheel up" and "wheel down" Inputs into the mouse wheel code I originally had; replying the unnecessary stuff I previously had and the code looks neater for it.

        if Input.is_action_just_pressed("wheel_up"):
            if current_weapon < 3:
                current_weapon += 1
            else:
                current_weapon = 1
        if Input.is_action_just_pressed("wheel_down"):
            if current_weapon > 1:
                current_weapon -= 1
            else:
                current_weapon = 3
    

    As you can see, I haven't got around to using the arrays yet. Will be right back when I have success with that.

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file