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
~~~
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
Weapon Class
Instead of your weapons extending UTweapon, have them extend this class to add in the functionality of upgradeable weapons
~~~
Partial Code Analysis
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
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); }
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
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
Comment