Announcement

Collapse
No announcement yet.

[Video][CODE] How to Easily Make Upgrade-able Projectile Weapons

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [Video][CODE] How to Easily Make Upgrade-able Projectile Weapons

    Dear Community,

    In this tutorial I am providing you with the code to easily implement the idea of your weapons upgrading over time as the player gains experience while in-game.

    The goal is to be able to have perhaps 100s of different exact weapon stat combinations available to the player, but only have to write the unrealscript code for a single weapon class + projectile class.

    So as an example, the player might have the option to upgrade the projectile speed for 10 levels, and the damage dealt for 20 levels, and any individual player could have any combination of these upgrades at any time during gameplay.

    But in terms of unrealscript code, you only want to have to make one weapon/projectile class code that still allows for all these possible combinations per player.

    This tutorial makes it easy for you to modify existing weapons that you might have created that extend UTWeapon.

    This tutorial also works with a game scenario where you do not use weapon classes, and just spawn projectiles directly.

    Please note this tutorial focuses on Projectile/Beam weapons only, but the core concepts could be extended to other kinds of weapons.



    ~~~

    Saving Weapon Upgrade Data Permanently

    The upgrade data structure used in this tutorial can easily be saved using BasicSaveObject since it is a static array of structs of simple variable types.

    See my tutorial on BasicSaveObject and a Save System for more info.


    ~~~

    The Core Concepts for Upgrade-able Projectile Based Weapons

    If you are not using Utweapon or have other variations in your class structures,

    just keep in mind the basic ideas and you can apply my upgrade system easily to your own project

    1. create a struct that stores the upgrade-able weapon stats, or any kind of upgrade stats really.

    2. this upgrade struct belongs with the player class so each player has their own version of this struct.
    Don't put this code in the pawn class because if the pawn dies and is respawned the upgrade data is lost.

    3. modify the upgrade struct's values over time as the player acquires experience and chooses specific upgrades

    4. pass this upgrade struct to the weapon or directly to the projectile class if your game doesn't use Utweapon at all.

    5. Use a custom initialization function in your projectile class to utilize the upgrade data when each instance of the projectile is spawned.

    6. If you are using a UTWeapon class, then other aspects of how the weapon works can also be modified via the weapon upgrade data structure, as in this code each weapon instance obtains the upgrade data when the player picks up / activates that weapon.


    ~~~

    Player Class

    Code:
    //you could add this code to your existing custom playercontroller class
    
    Class UpgradeablePlayerClass extends PlayerController;
    
    //Make a struct for easy copy-pasting of upgrade data
    //With this setup, to copy all upgrade data from one
    //player to another you can just do:
    
    //player1.weaponStats = player2.weaponStats;
    
    //this is the struct for 1 weapon type
    //add additional variables even if
    //only 1 weapon uses them
    //since with this setup all weapons
    //will look for this one type of struct.
    
    struct weaponUpgradeData
    {
      var class weaponClass;
      var float damageMultiplier;
      var float weaponSpeed;
      var float weaponAccel;
      var float projLifeSpan;
      var vector drawscale;
      //other upgrade stats
    
    //you can even change what projectile
    //the weapon uses as it gets upgraded!
      var class projectileClass;
    
      //default upgrade stats
      structdefaultproperties
     {
       damageMultiplier = 1
       drawscale = (x=1,y=1,z=1);
       projLifeSpan = 10 //seconds
      }
    };
    
    //supposing 7 different weapon types available to player
    var weaponUpgradeData weaponStats[7];
    //only use static array, dynamic arrays will not work in multiplayer games
    
    Simulated Event PostBeginPlay() {
    	super.PostBeginPlayer();
    
    	//set the weapon classes and their
    	//starting projectile classes
    	
    	//weapon 1 starting upgrades
    	weaponStats[0].weaponClass = 'YourFirstweaponClass';
    	weaponStats[0].projectileClass = 'FirstweaponProjectileClass';
    	weaponStats[0].weaponSpeed = 3000;
    	weaponStats[0].weaponAccel = 30;
    
    	//weapon 2 starting upgrades
    	weaponStats[1].weaponClass = 'YourSecondweaponClass';
    	weaponStats[1].projectileClass = 'SecondweaponProjectileClass';
    	weaponStats[1].weaponSpeed = 1000;
    	weaponStats[1].weaponAccel = 300;
    
    	//etc...
    }
    ~~~

    Weapon Projectile Class

    This class depends on UpgradeablePlayerClass because it needs to know the weaponData struct definition

    Each of your actual projectile classes should extend this class, the weapon class code expects this

    You can write a new JoyInit() function for each projectile to access variables only pertaining to that projectile class. Make sure to call super.JoyInit(), or copy entire code below into new declaration of joyInit that is specific to a subclass of upgradeableProjBase

    Code:
    class upgradeableProjBase extends UTProjectile
         dependson(UpgradeablePlayerClass);
    
    //custom initialization func
    function joyInit(vector Direction, weaponUpgradeData weaponData ) {
    	
    	//set speed and acceleration
    	//per instance of projectile
    	speed      = weaponData.weaponSpeed;
    	accelrate = weaponData.weaponAccel;
    		
    	//~~~ Damage ~~~
    	//here's how you adjust the damage
    	//as weapon gets upgraded
    	//In the character class who is using this
    	//projectile, you store this multiplier permanently
    	//as a character statistic
    	Damage *= weaponData.damageMultiplier;
    	
    	//~~~ Life Span of Projectile ~~~
    	
    	//you can customize how long
    	//projectile stays alive
    	//per instance / upgraded weapon
    	LifeSpan = weaponData.projLifeSpan;
    	
    	//~~~ Asymetric draw scale ~~~
    	
    	//can make weapon proj look different
    	//per player
    	
    	//so as weapon gets upgraded it's projectile
    	//could get bigger or longer for example
    	SetDrawScale3D(weaponData.drawscale);
    	
    	//~~~ Emitter Parameter Changes ~~~
    	
    	//if your particle effect has parameters
        //you could set them here as well
    
        //to change color or other aspects of the particle system
    	
    	
    	//~~~ UTProjectile.uc Init ~~~
    	Init(Direction);
    	
    }
    Weapon Class

    Instead of your weapons extending UTweapon, have them extend this class to add in the functionality of upgradeable weapons

    Code:
    class upgradeableWeapon extends UTWeapon
         dependson(UpgradeablePlayerClass);
    
    //global variable to store the upgrade data
    var weaponUpgradeData weaponData;
    
    //copied out of engine/weapon.uc
    
    //editing this code to allow each player
    //who picks up an instance of a weapon
    //to modify that weapon based on the player's upgrade weapon data
    
    /************
     * State Active
     * A Weapon this is being held by a pawn should be in the active state.  In this state,
     * a weapon should loop any number of idle animations, as well as check the PendingFire flags
     * to see if a shot has been fired.
     *********/
    
    simulated state Active
    {
    	/** Initialize the weapon as being active and ready to go. */
    	simulated event BeginState(Name PreviousStateName)
    	{
    	  local int i;
    	  local weaponUpgradeData oneSetofStats;
    
    	  //set the global weaponData var when
    	  //a player picks up this weapon
    	  if(UpgradeablePlayerClass(Instigator.Controller) != none) {
    
    	      for(i=0; i < ArrayCount(
                         UpgradeablePlayerClass(Instigator.Controller).weaponStats), i++) {
    
    	            oneSetofStats = UpgradeablePlayerClass(Instigator.Controller).weaponStats[i];
                        if(oneSetofStats.weaponClass == Self.class){
        		           weaponData =  oneSetofStats;
                               break;
                        }
    	       } //end for
    	  } //end if
    		
    	  // Cache a reference to the AI controller
    	  if (Role == ROLE_Authority)
    	  {
    	    CacheAIController();
    	   }
    
    	// Check to see if we need to go down
       	if( bWeaponPutDown )
    	{
    	   `LogInv("Weapon put down requested during transition, put it down now");
    	    PutDownWeapon();
    	 }
    	 else if ( !HasAnyAmmo() )
    	{
    	   WeaponEmpty();
    	 }
    	  else
    	 {
    	      // if either of the fire modes are pending, perform them
    	      for( i=0; i<GetPendingFireLength(); i++ )
    	      {
    	         if( PendingFire(i) )
    	         {
    	            BeginFire(i);
    	            break;
    	       }
    	}
              }
            }
    
    	/** Override BeginFire so that it will enter the firing state right away. */
    	simulated function BeginFire(byte FireModeNum)
    	{
    	  if( !bDeleteMe && Instigator != None )
    	  {
    	   Global.BeginFire(FireModeNum);
    
                   // in the active state, fire right away if we have the ammunition
    	   if( PendingFire(FireModeNum) && HasAmmo(FireModeNum) )
    	   {
    	     SendToFiringState(FireModeNum);
    	    }
    	  }
    	}
    
    	/**
                * ReadyToFire() called by NPC firing weapon.
                 * bFinished should only be true if called from the Finished() function
     	 */
    
    	simulated function bool ReadyToFire(bool bFinished)
    	{
    		return true;
    	}
    
    	/** Activate() ignored since already active
    	*/
    	simulated function Activate()
    	{
    	}
    
    
    	/**
    	 * Put the weapon down
    	 */
    	simulated function bool TryPutDown()
    	{
    		PutDownWeapon();
    		return TRUE;
    	}
    }
    
    
    //copied out of utgame/utweapon.uc
    
    //add the custom initialization function
    //to the weapon firing function
    
    simulated function Projectile ProjectileFire()
    {
    	local vector		RealStartLoc;
    	local Projectile	SpawnedProjectile;
    
    	// tell remote clients that we fired, to trigger effects
    	IncrementFlashCount();
    
    	if( Role == ROLE_Authority )
    	{
    	  // this is the location where the projectile is spawned.
    	  RealStartLoc = GetPhysicalFireStartLoc();
    
    	  // Spawn projectile using weapon upgrade data 
                 //projectile class
    	  if(Instigator.Controller.isa('UpgradeablePlayerClass')){
    	     SpawnedProjectile = Spawn(weaponData.projectileClass,,, RealStartLoc);
    	  }
    	  else {
    	    GetProjectileClass();
    	  }
    
    	//if the user of weapon has weaponUpgradeData
    	if(Instigator.Controller.isa('UpgradeablePlayerClass')){
    	  if( SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe )
    	  {
    	    //use upgrade data via custom initialization function
    	    upgradeableProjBase(SpawnedProjectile).joyInit( 
    		Vector(GetAdjustedAim( RealStartLoc )), weaponData );
    	  }
    	}
                //standard code
    	else {
                if( SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe )
    	  {
    	    SpawnedProjectile.Init( Vector(GetAdjustedAim( RealStartLoc ));
    	  }
    	}
    	  // Return it up the line
    	   return SpawnedProjectile;
    	}
    
    	return None;
    }
    ~~~

    Partial Code Analysis

    Code:
    //in the weapon class extending UTWeapon
    
    //global variable to store the upgrade data
    var weaponUpgradeData weaponData;
    
     //set the global weaponData var when
    //a player picks up this weapon
    simulated state Active
    {
    	/** Initialize the weapon as being active and ready to go. */
    	simulated event BeginState(Name PreviousStateName)
    	{
    	  local int i;
    	  local weaponUpgradeData oneSetofStats;
    	 
    	  if(UpgradeablePlayerClass(Instigator.Controller) != none) {
    
    	    for(i=0; i < ArrayCount(
                         UpgradeablePlayerClass(Instigator.Controller).weaponStats), i++) {
    
    	            oneSetofStats = UpgradeablePlayerClass(Instigator.Controller).weaponStats[i];
                            if(oneSetofStats.weaponClass == Self.class){
        		           weaponData =  oneSetofStats;
                               break;
                            }
    	        } //end for
    	  } //end if
    
    //partial code snippet only
    ArrayCount gets the length of a static array and is converted to a simple value after your class gets compiled.


    This loop checks each of the weaponUpgradeData structs that exist in the player controller's static array to find the struct pertaining to this weapon. (the weapon class this code is running in)

    Once the weapon finds a struct with its class name it then saves the struct data as a global var for ProjectileFire() to use.

    So this loop is only running once, at the time that the player picks up the weapon / makes it active. This loop is NOT run every time player fires the weapon.


    Note that if the player chooses an upgrade while the weapon is active, you will need to make the weapon active again manually, or manually set the weaponData to have the upgrades take effect.

    A way around this would be to de-equip all weapons while the player is in the upgrades menu choosing weapon upgrades.


    You can get rid of the oneSetofStats variable in your own use of this code, I kept it here for clarity so you could see each step.

    But you can replace oneSetofStats with UpgradeablePlayerClass(Instigator.Controller).weap onStats[i].

    ~~~

    Summary

    By creating an array of custom upgrade stats structs in your player class and passing this data to your weapon/projectile classes,

    you can customize each and every weapon that you already have in your game based on upgrades / experience levels of each player in your game.

    In this way you can avoid having to create many weapon / projectile classes for each level of an upgrade that the player can obtain in-game, and instead just pass into the weapon/projectile classes the info they need to modify themselves for each player and that player's current upgrades.



    Rama

    #2
    Great work, sure a lot of people will find this very useful

    Comment


      #3
      hee hee!

      Rama

      Comment


        #4
        Cool i'm sure you could apply the same principle to make a leveling system for an rpg also.

        Comment


          #5
          yes, the core idea is the player controller class to have a struct of simple variable types like float, string, vector, rotator so that all this data can easily be saved using BasicSaveObject

          and then you can store any kind of data you want!

          you could have NPCs check this player data when deciding how to react to the player, or whether the player can wear a certain kind of armor, or anything really.

          The huge advantage here is that this can be saved to disk, which is what you need to be able to do for a long-term RPG

          Rama

          Comment


            #6
            Very nice! Thanks for helping out the community.

            Comment


              #7
              Thank you very much Rama!

              Comment


                #8
                You're welcome RyanJon2040 !!



                Rama

                PS: tutorial was at his request

                Comment

                Working...
                X