├── Weapon Handler ├── ammo.gd ├── weapon.gd └── weapon_handler.gd ├── FPS Weapon Sway and Movement ├── recoil_curve.tres ├── camera_shake.gd ├── recoil_handler.gd └── player_controller.gd ├── .gitignore ├── README.md ├── Weapon Pickup System - Unity ├── Weapon.cs ├── WeaponHandler.cs ├── WeaponPickup.cs ├── PickupHandler.cs └── RecoilHandler.cs └── LICENSE /Weapon Handler/ammo.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name Ammo 3 | 4 | enum AmmoType 5 | { 6 | LIGHT, MEDIUM, HEAVY 7 | } 8 | 9 | @export var ammo_type : AmmoType 10 | -------------------------------------------------------------------------------- /FPS Weapon Sway and Movement/recoil_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://hndva4k2monf"] 2 | 3 | [resource] 4 | min_value = -1.0 5 | _data = [Vector2(0, 0.5), 0.0, 0.0, 0, 0, Vector2(0.1, -0.2), 0.0, 0.0, 0, 0, Vector2(0.2, 0.1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0] 6 | point_count = 4 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial_Scripts 2 | A collection of scripts for my tutorials 3 | 4 | Use these scripts as a general guide, but I highly encourage you to look at other resources as well in order to improve your own versions. I'm a learner as well, so things may not be 100% optimal. If there are any improvements you'd like me to make, please let me know. 5 | 6 | You can visit my YouTube channel at: 7 | https://www.youtube.com/channel/UC4_tscT4ZNBnfQfXt2m2Nqg 8 | -------------------------------------------------------------------------------- /Weapon Handler/weapon.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name Weapon 3 | 4 | # This just adds a set of baseline stats for ranged weapons 5 | # Customize to suit your game 6 | 7 | @export var weapon_name : String 8 | @export var max_mag_size : int = 18 9 | @export var ammo_used_per_shot : int = 1 10 | @export var ammo : Ammo 11 | 12 | enum ShootType 13 | { 14 | SINGLE, SEMI_AUTO, FULL_AUTO 15 | } 16 | 17 | @export var shoot_type : ShootType 18 | 19 | @export var shot_delay : float = 0.1 20 | -------------------------------------------------------------------------------- /Weapon Pickup System - Unity/Weapon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class Weapon : MonoBehaviour 6 | { 7 | [SerializeField] private string weaponName; 8 | public string GetName {get {return weaponName;}} 9 | 10 | [SerializeField] private WeaponPickup weaponDrop; 11 | 12 | public void Drop(Vector3 dir) 13 | { 14 | WeaponPickup weaponPickup = Instantiate(weaponDrop, transform.position, Quaternion.identity); 15 | weaponPickup.Spawn(dir); 16 | Destroy(gameObject); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Weapon Pickup System - Unity/WeaponHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | public class WeaponHandler : MonoBehaviour 5 | { 6 | [SerializeField] private float fireRate = 1f; 7 | private float fireTime; 8 | 9 | [SerializeField] private UnityEvent onFireEvents; 10 | 11 | private void Update() 12 | { 13 | if(Input.GetKeyDown(KeyCode.Mouse0)) 14 | { 15 | fireTime = 0f; 16 | } 17 | if(Input.GetKey(KeyCode.Mouse0)) 18 | { 19 | if(fireTime > 0f) fireTime -= Time.deltaTime; 20 | else 21 | { 22 | onFireEvents?.Invoke(); 23 | fireTime = fireRate; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Weapon Pickup System - Unity/WeaponPickup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class WeaponPickup : MonoBehaviour 6 | { 7 | [SerializeField] private Weapon weapon; 8 | public Weapon GetWeapon {get {return weapon;} } 9 | 10 | [SerializeField] private Rigidbody rb; 11 | 12 | public Weapon Use() 13 | { 14 | Weapon _w = Instantiate(weapon, transform.position, Quaternion.identity); 15 | Invoke("Destroy", 0.1f); 16 | return _w; 17 | } 18 | 19 | public void Spawn(Vector3 dir) 20 | { 21 | if(rb) 22 | { 23 | rb.AddForce(dir, ForceMode.Impulse); 24 | rb.AddTorque(dir,ForceMode.Impulse); 25 | } 26 | } 27 | 28 | public void Destroy() 29 | { 30 | Destroy(gameObject); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 jungaboon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Weapon Handler/weapon_handler.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var weapon : Weapon 4 | var can_fire : bool = true 5 | var holding_trigger : bool = false 6 | var shot_time : float = 0 7 | 8 | func _input(event): 9 | fire_weapon_handler(event) 10 | 11 | func fire_weapon_handler(event): 12 | if !weapon: return 13 | match weapon.shoot_type: 14 | weapon.ShootType.SINGLE: 15 | if event.is_action_pressed("fire") and can_fire: 16 | can_fire = false 17 | fire() 18 | 19 | weapon.ShootType.SEMI_AUTO: 20 | if event.is_action_pressed("fire") and can_fire: 21 | fire() 22 | 23 | weapon.ShootType.FULL_AUTO: 24 | if event.is_action_pressed("fire"): 25 | holding_trigger = true 26 | if event.is_action_released("fire"): 27 | holding_trigger = false 28 | 29 | func _process(delta): 30 | if holding_trigger: 31 | shot_time -= delta 32 | if shot_time < 0: 33 | fire() 34 | shot_time = weapon.shot_delay 35 | else: 36 | shot_time = 0 37 | 38 | func fire(): 39 | print("Shoot") 40 | # Add firing logic here 41 | # You may want to add the recoil function from the recoil script here as well as any animations/effects you may want to play 42 | -------------------------------------------------------------------------------- /FPS Weapon Sway and Movement/camera_shake.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | class_name CameraShake 3 | 4 | # NOTE: This script is adapted from Pefeper's script. Watch his video to learn more 5 | 6 | # trauma will decrease by this amount every second 7 | @export var trauma_reduction_rate = 1.0 8 | var trauma = 0.0 9 | 10 | @export var max_x = 10.0 11 | @export var max_y = 10.0 12 | @export var max_z = 5.0 13 | 14 | 15 | # If you're coming from the Pefeper video, SimpleNoise is replaced by FastNoiseLite in Godot 4 16 | @export var noise: FastNoiseLite 17 | @export var noise_speed = 50.0 18 | var time = 0.0 19 | 20 | @onready var initial_rotation = self.rotation_degrees as Vector3 21 | 22 | func _physics_process(delta): 23 | time += delta 24 | trauma = max(trauma - delta * trauma_reduction_rate, 0.0) 25 | 26 | self.rotation_degrees.x = initial_rotation.x + max_x * get_shake_intensity() * get_noise_from_seed(0) 27 | self.rotation_degrees.y = initial_rotation.y + max_y * get_shake_intensity() * get_noise_from_seed(1) 28 | self.rotation_degrees.z = initial_rotation.z + max_z * get_shake_intensity() * get_noise_from_seed(2) 29 | 30 | 31 | func add_trauma(trauma_amount : float): 32 | trauma = clamp(trauma + trauma_amount, 0.0, 1.0) 33 | 34 | func get_shake_intensity() -> float: 35 | return trauma * trauma 36 | 37 | func get_noise_from_seed(_seed : int) -> float: 38 | noise.seed = _seed 39 | return noise.get_noise_1d(time * noise_speed) 40 | -------------------------------------------------------------------------------- /FPS Weapon Sway and Movement/recoil_handler.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name RecoilHandler 3 | 4 | @export var target_object : Node3D 5 | @export var recoil_rotation_x : Curve 6 | @export var recoil_rotation_z : Curve 7 | @export var recoil_position_z : Curve 8 | @export var recoil_amplitude := Vector3(1,1,1) 9 | @export var lerp_speed : float = 1 10 | @export var recoil_speed : float = 1 11 | 12 | @export var camera_shake : CameraShake 13 | @export var camera_shake_amount : float = 0.3 14 | 15 | var def_pos : Vector3 16 | var def_rot : Vector3 17 | var target_rot : Vector3 18 | var target_pos : Vector3 19 | var current_time : float 20 | 21 | func _ready(): 22 | def_pos = target_object.position 23 | def_rot = target_object.rotation 24 | target_rot.y = target_object.rotation.y 25 | current_time = 1 26 | 27 | func _physics_process(delta): 28 | if current_time < 1: 29 | current_time += delta * recoil_speed 30 | target_object.position.z = lerp(target_object.position.z, def_pos.z + target_pos.z, lerp_speed * delta) 31 | target_object.rotation.z = lerp(target_object.rotation.z, def_rot.z + target_rot.z, lerp_speed * delta) 32 | target_object.rotation.x = lerp(target_object.rotation.x, def_rot.x + target_rot.x, lerp_speed * delta) 33 | 34 | target_rot.z = recoil_rotation_z.sample(current_time) * recoil_amplitude.y 35 | target_rot.x = recoil_rotation_x.sample(current_time) * -recoil_amplitude.x 36 | target_pos.z = recoil_position_z.sample(current_time) * recoil_amplitude.z 37 | 38 | func apply_recoil(): 39 | camera_shake.add_trauma(camera_shake_amount) 40 | recoil_amplitude.y *= -1 if randf() > 0.5 else 1 41 | target_rot.z = recoil_rotation_z.sample(0) * recoil_amplitude.y 42 | target_rot.x = recoil_rotation_x.sample(0) * -recoil_amplitude.x 43 | target_pos.z = recoil_position_z.sample(0) * recoil_amplitude.z 44 | current_time = 0 45 | -------------------------------------------------------------------------------- /Weapon Pickup System - Unity/PickupHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class PickupHandler : MonoBehaviour 6 | { 7 | [SerializeField] private Camera mainCamera; 8 | [SerializeField] private Weapon[] weaponSlots; 9 | [SerializeField] private Transform weaponParent; 10 | 11 | private bool AllSlotsFull() 12 | { 13 | for (int i = 0; i < weaponSlots.Length; i++) 14 | { 15 | if(weaponSlots[i] == null) return false; 16 | } 17 | return true; 18 | } 19 | 20 | private void Start() 21 | { 22 | Cursor.lockState = CursorLockMode.Locked; 23 | Cursor.visible = false; 24 | } 25 | 26 | private void Update() 27 | { 28 | if (Input.GetKeyDown(KeyCode.F)) 29 | { 30 | CheckForWeapon(); 31 | } 32 | } 33 | 34 | private void CheckForWeapon() 35 | { 36 | if(Physics.Raycast(mainCamera.transform.position, mainCamera.transform.forward, out RaycastHit raycastHit, 5f)) 37 | { 38 | if(raycastHit.collider.TryGetComponent(out WeaponPickup weaponPickup)) 39 | { 40 | Weapon weapon = weaponPickup.Use(); 41 | 42 | for (int i = 0; i < weaponSlots.Length; i++) 43 | { 44 | if(weaponSlots[i] == null) 45 | { 46 | SetWeapon(weapon,i); 47 | break; 48 | } 49 | else if(AllSlotsFull()) 50 | { 51 | weaponSlots[i].Drop(mainCamera.transform.forward * 7f); 52 | SetWeapon(weapon,i); 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | private void SetWeapon(Weapon weapon, int i) 61 | { 62 | weapon.transform.SetParent(weaponParent); 63 | weapon.transform.localPosition = Vector3.zero; 64 | weapon.transform.localRotation = Quaternion.Euler(Vector3.zero); 65 | weapon.transform.localScale = Vector3.one; 66 | weaponSlots[i] = weapon; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Weapon Pickup System - Unity/RecoilHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class RecoilHandler : MonoBehaviour 6 | { 7 | [SerializeField] private Transform targetObject; 8 | private Transform actualTargetObject; 9 | [Header("Position Curves")] 10 | [SerializeField] private AnimationCurve xPosCurve; 11 | [SerializeField] private AnimationCurve yPosCurve; 12 | [SerializeField] private AnimationCurve zPosCurve; 13 | [Header("Rotation Curves")] 14 | [SerializeField] private AnimationCurve xRotCurve; 15 | [SerializeField] private AnimationCurve yRotCurve; 16 | [SerializeField] private AnimationCurve zRotCurve; 17 | [Space] 18 | [SerializeField] private float recoilSpeed = 1f; 19 | [SerializeField] private float posLerpSpeed = 1f; 20 | [SerializeField] private float rotLerpSpeed = 1f; 21 | [Space] 22 | [SerializeField] private Vector3 recoilPosAmplitude = Vector3.forward; 23 | [SerializeField] private Vector3 recoilRotAmplitude = Vector3.forward; 24 | 25 | private Vector3 defPos; 26 | private Vector3 defRot; 27 | private Vector3 recoilPosTarget; 28 | private Vector3 recoilRotTarget; 29 | private float recoilTime; 30 | 31 | private void Start() 32 | { 33 | if(targetObject) actualTargetObject = targetObject; 34 | else actualTargetObject = transform; 35 | 36 | defPos = actualTargetObject.localPosition; 37 | defRot = actualTargetObject.localEulerAngles; 38 | recoilTime = 1f; 39 | } 40 | 41 | public void ApplyRecoil() 42 | { 43 | recoilTime = 0f; 44 | EvaluateCurves(); 45 | } 46 | 47 | private void EvaluateCurves() 48 | { 49 | recoilPosTarget.x = xPosCurve.Evaluate(recoilTime) * recoilPosAmplitude.x; 50 | recoilPosTarget.y = yPosCurve.Evaluate(recoilTime) * recoilPosAmplitude.y; 51 | recoilPosTarget.z = zPosCurve.Evaluate(recoilTime) * recoilPosAmplitude.z; 52 | 53 | recoilRotTarget.x = xRotCurve.Evaluate(recoilTime) * recoilRotAmplitude.x; 54 | recoilRotTarget.y = yRotCurve.Evaluate(recoilTime) * recoilRotAmplitude.y; 55 | recoilRotTarget.z = zRotCurve.Evaluate(recoilTime) * recoilRotAmplitude.z; 56 | recoilRotTarget *= Random.value > 0.5f ? 1f : -1f; 57 | } 58 | 59 | private void Update() 60 | { 61 | if(recoilTime < 1f) 62 | { 63 | recoilTime += recoilSpeed * Time.deltaTime; 64 | EvaluateCurves(); 65 | 66 | targetObject.localPosition = Vector3.Lerp(targetObject.localPosition, -recoilPosTarget, posLerpSpeed * Time.deltaTime); 67 | targetObject.localRotation = Quaternion.Slerp(targetObject.localRotation, Quaternion.Euler(-recoilRotTarget), rotLerpSpeed * Time.deltaTime); 68 | } 69 | else 70 | { 71 | targetObject.localPosition = Vector3.Lerp(targetObject.localPosition, defPos, posLerpSpeed * Time.deltaTime); 72 | targetObject.localRotation = Quaternion.Slerp(targetObject.localRotation, Quaternion.Euler(defRot), rotLerpSpeed * Time.deltaTime); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /FPS Weapon Sway and Movement/player_controller.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody3D 2 | 3 | 4 | @export var speed = 5.0 5 | @export var cam : Node3D 6 | @export var cam_speed : float = 5 7 | @export var cam_rotation_amount : float = 1 8 | 9 | @export var weapon_holder : Node3D 10 | @export var weapon_sway_amount : float = 5 11 | @export var weapon_rotation_amount : float = 1 12 | @export var invert_weapon_sway : bool = false 13 | 14 | const JUMP_VELOCITY = 4.5 15 | 16 | var def_weapon_holder_pos : Vector3 17 | var mouse_input : Vector2 18 | 19 | # Get the gravity from the project settings to be synced with RigidBody nodes. 20 | var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") 21 | 22 | func _input(event): 23 | if !cam: return 24 | if event is InputEventMouseMotion: 25 | cam.rotation.x -= event.relative.y * cam_speed 26 | cam.rotation.x = clamp(cam.rotation.x,-1.25,1.5) 27 | self.rotation.y -= event.relative.x * cam_speed 28 | mouse_input = event.relative 29 | 30 | func _ready(): 31 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 32 | def_weapon_holder_pos = weapon_holder.position 33 | 34 | func _physics_process(delta): 35 | # Add the gravity. 36 | if not is_on_floor(): 37 | velocity.y -= gravity * delta 38 | 39 | # Handle Jump. 40 | if Input.is_action_just_pressed("ui_accept") and is_on_floor(): 41 | velocity.y = JUMP_VELOCITY 42 | 43 | # Get the input direction and handle the movement/deceleration. 44 | # Note that the negative value for the forward/back movement is "fwd" because Godot's forward is -Z 45 | var input_dir = Input.get_vector("left","right","fwd","bwd") 46 | var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() 47 | if direction: 48 | velocity.x = direction.x * speed 49 | velocity.z = direction.z * speed 50 | else: 51 | velocity.x = move_toward(velocity.x, 0, speed) 52 | velocity.z = move_toward(velocity.z, 0, speed) 53 | 54 | move_and_slide() 55 | cam_tilt(input_dir.x, delta) 56 | weapon_tilt(input_dir.x, delta) 57 | weapon_sway(delta) 58 | weapon_bob(velocity.length(),delta) 59 | 60 | func cam_tilt(input_x, delta): 61 | if cam: 62 | cam.rotation.z = lerp(cam.rotation.z, -input_x * cam_rotation_amount, 10 * delta) 63 | 64 | func weapon_tilt(input_x, delta): 65 | if weapon_holder: 66 | weapon_holder.rotation.z = lerp(weapon_holder.rotation.z, -input_x * weapon_rotation_amount * 10, 10 * delta) 67 | 68 | func weapon_sway(delta): 69 | mouse_input = lerp(mouse_input,Vector2.ZERO,10*delta) 70 | weapon_holder.rotation.x = lerp(weapon_holder.rotation.x, mouse_input.y * weapon_rotation_amount * (-1 if invert_weapon_sway else 1), 10 * delta) 71 | weapon_holder.rotation.y = lerp(weapon_holder.rotation.y, mouse_input.x * weapon_rotation_amount * (-1 if invert_weapon_sway else 1), 10 * delta) 72 | 73 | func weapon_bob(vel : float, delta): 74 | if weapon_holder: 75 | if vel > 0 and is_on_floor(): 76 | var bob_amount : float = 0.01 77 | var bob_freq : float = 0.01 78 | weapon_holder.position.y = lerp(weapon_holder.position.y, def_weapon_holder_pos.y + sin(Time.get_ticks_msec() * bob_freq) * bob_amount, 10 * delta) 79 | weapon_holder.position.x = lerp(weapon_holder.position.x, def_weapon_holder_pos.x + sin(Time.get_ticks_msec() * bob_freq * 0.5) * bob_amount, 10 * delta) 80 | 81 | else: 82 | weapon_holder.position.y = lerp(weapon_holder.position.y, def_weapon_holder_pos.y, 10 * delta) 83 | weapon_holder.position.x = lerp(weapon_holder.position.x, def_weapon_holder_pos.x, 10 * delta) 84 | --------------------------------------------------------------------------------