PDA

View Full Version : Tutorial Delay Fire



seenooh
08-18-2010, 10:38 AM
Hello all!

In the past week, I've been researching how to delay the weapon firing instead of immediately when the firing animation starts. This is useful for melee attacks or if your weapon actually shoots in the middle/end of the firing animation.

Initially, I used AnimNotify_Script, it worked for me but it wasn't convenient since alot of the weapons that I'll be developing will require delay. So ultimately I managed to develop a super class which provides this functionality, and all my weapons that need delay can inherit from it, tweak a couple of variables, and you're up and running! :)

So this is the super class, lots of comments explaining every step:


/**
* ************************************************** ************************************************** *********
* OCWeapon. The shared Weapon class implementation.
*
* By Hamad Al-Hasan (aka seenooh) for The Outcasts project (http://www.the-outcasts.tk/).
*
* ************************************************** ************************************************** *********
* Version: 1.0
*
* Description: This class will be subclassed by all our weapon implementations. It will have
* all the common functional features between all weapons.
*
* Last Modified by: Hamad Al-Hasan
* Last Modify Date: Aug, 16th 2010
*
*/

class OCWeapon extends UTWeapon;

/** This is an array of two elements (fire/alt fire) containing how much (in seconds) we can delay fire after the fire animation starts */
var array<float> DelayFireTime;


/*
* The WeaponFiring state is overriden to provide the delay fire functionality. The uncommented functions are stock implementations
*
* Workflow:
*
* 1- Once we fire, WeaponFiring state is activated, and BeginState is immediately executed.
* 2- If we don't have delay, we'll fire immediately and refire according to FireInterval.
* 3- If we have delay, we'll play the firing animation first. Then activate the delay timer,
* which will delay the firing logic according to DelayFireTime[FireNumMode].
* 4- Once we fire, the refire logic will start.
*/

simulated state WeaponFiring
{
simulated event bool IsFiring()
{
return true;
}

/**
* Timer event, call is set up in Weapon::TimeWeaponFiring().
* The weapon is given a chance to evaluate if another shot should be fired.
* This event defines the weapon's rate of fire.
*/
simulated function RefireCheckTimer()
{
// if switching to another weapon, abort firing and put down right away
if( bWeaponPutDown )
{
`LogInv("Weapon put down requested during fire, put it down now");
PutDownWeapon();
return;
}

// If weapon should keep on firing, then do not leave state and fire again.
if( ShouldRefire() )
{
//Hamad: If we have delay, refire according to our delay logic
if (DelayFireTime[CurrentFireMode] > 0)
{
ClearTimer('DelayFire');
PlayFireEffects( CurrentFireMode );
}
else
FireAmmunition(); //Else, follow the default implementation

return;
}

// Otherwise we're done firing
HandleFinishedFiring();
}

simulated event BeginState( Name PreviousStateName )
{
`LogInv("PreviousStateName:" @ PreviousStateName);

//If we don't have delays, resume with default implementation
if (DelayFireTime[CurrentFireMode] <= 0)
{
FireAmmunition();
TimeWeaponFiring( CurrentFireMode );
}
else
PlayFireEffects( CurrentFireMode ); //Otherwise, use ours

}

simulated event EndState( Name NextStateName )
{
`LogInv("NextStateName:" @ NextStateName);
// Set weapon as not firing
ClearFlashCount();
ClearFlashLocation();
ClearTimer('RefireCheckTimer');

NotifyWeaponFinishedFiring( CurrentFireMode );
}
}


simulated function TimeWeaponFiring( byte FireModeNum )
{
// if weapon is not firing, then start timer. Firing state is responsible to stopping the timer.
if( !IsTimerActive('RefireCheckTimer') )
{
SetTimer( GetFireInterval(FireModeNum) , true, nameof(RefireCheckTimer) );
}
}

//Hamad: This is the delay timer function. It'll fire the default implementation and clear itself.
simulated function DelayFire()
{
FireAmmunition();
TimeWeaponFiring( CurrentFireMode );
ClearTimer('DelayFire');
}

//Hamad: Play the animation and activate the delay timer if we are in delay mode
simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation )
{

if (DelayFireTime[FireModeNum] > 0) // Do we have delay?
{
/* Is the timer active already? if yes, don't do anything. This is a tricky part.
* We injected this function at the BeginState, which is originally fired by Pawn.WeaponFired()
* I wanted to remove this effect without touching the pawn's base code, so I could do it
* by checking if the timer is active or not (caused by our BeginState). If it does, then
* the second call by Pawn.WeaponFired() will have no effect, this avoid duplication.
*/

if( !IsTimerActive('DelayFire') )
{
SetTimer( DelayFireTime[FireModeNum] , true, 'DelayFire' );
super.PlayFireEffects(FireModeNum, HitLocation);
}

}
else
{
//No delay. Resume default implementation.
super.PlayFireEffects(FireModeNum, HitLocation);
}

}

DefaultProperties
{

DelayFireTime(0) = 0; //By default, no delay for the main fire.
DelayFireTime(1) = 0; //By default, no delay for the alt fire.

}



Now all you have to do is extend this class and you can delay your fire easily. I hope you find this useful. :)

Enjoy!

vichnaiev
08-18-2010, 10:49 AM
Very nice and detailed. Thank you very much. I just have one question:

- if PlayFireEffects is handling the animation and you are not calling it for non-delayed weapons (in BeginState), how are they going to animate?

seenooh
08-18-2010, 11:15 AM
You welcome. :)

As for your question, I'll be following the default implementation. So PlayFireEffects will be executed by the pawn. Pawn.WeaponFired() is responsible for it. So if we have a delay, the logic above will avoid duplication in function call.

Jonny T
08-23-2010, 08:25 AM
Hey guys - i'm new to scripting - I thought that PlayFireEffects only played the muzzleflash and not the animation for the weapon?

seenooh
08-23-2010, 12:54 PM
That, as well as the firing animation. :)

sabius
07-09-2011, 07:09 PM
Amazing code man, I was having a lot of trouble implementing the fire delay!!

It worked like a charm, thanks!

seenooh
07-09-2011, 08:11 PM
No probs, good luck! :)

alvarofer0021
07-09-2011, 09:03 PM
Very nice i got one little question regarding the fire interval Lets say i want to let the player weapon Refire even if the fire animation hasnt end but dont let the weapon shoot faster than the fire interval So basicly let the player shoot even if the weapon fire animation hasnt end but not faster than the fire intervaL

i have been triying to do this and i have no luck So Do you have any clue or possible hint at doing this?

seenooh
07-09-2011, 09:27 PM
Like you want to have intervals, within the fire interval? I'm not sure I understand your goal.

alvarofer0021
07-10-2011, 09:52 AM
Let me explain better

The default weapon fire implementation in UTWeapon works like this

Plays the fire animation and deals the damage And it doesnt let the player refire Till the Fire Animation lenght is complete

What im triying to do is Let the player Refire his weapon EVEN if the Fire Animation hasnt end

seenooh
07-10-2011, 10:38 AM
There is a function called TimeWeaponFiring(), I think you should have that edited so that it doesn't depend on the fire interval for shooting, create some new variables and play around.

Hellclown
07-10-2011, 11:09 PM
What if I have various animations that are different lengths? i.e. three different melee animations, is there a way to create multiple delays and then tell the script which to run based on which animation is running?

seenooh
07-11-2011, 10:55 AM
I think if you want to quickly achieve that without having to modify the super class, you can set the value of DelayFireTime(0) in your pawn class when you randomize between those three animations.

Psuedo-code:




AnimIndex = RandomizeBetweenMyAnims();

Switch (AnimIndex)
{
Case 0:
myweap.DelayFireTime(0) = 0.5;
PlayAnim1();

Case 1:
myweap.DelayFireTime(0) = 0.3;
PlayAnim2();

...

}

Slesh
04-09-2012, 01:06 PM
Hi, I think I'm doing something wrong.. because for some reason the delay applies to the whole firing process (it delays the animation too not just the hit).

here is my weapon code :


class MeleeWeaponSword extends OCWeapon;

var() const name SwordHiltSocketName;
var() const name SwordTipSocketName;
var() const name SwordAnimationName;
var() const SoundCue SwordClank;

var array<Actor> SwingHitActors;
var array<int> Swings;
var const int MaxSwings;

reliable client function ClientGivenTo(Pawn NewOwner, bool bDoNotActivate)
{
local MeleeWeaponPawn MWPawn;

super.ClientGivenTo(NewOwner, bDoNotActivate);

MWPawn = MeleeWeaponPawn(NewOwner);

if (MWPawn != none && MWPawn.Mesh.GetSocketByName(MWPawn.SwordHandSocket Name) != none)
{
Mesh.SetShadowParent(MWPawn.Mesh);
Mesh.SetLightEnvironment(MWPawn.LightEnvironment);
MWPawn.Mesh.AttachComponentToSocket(Mesh, MWPawn.SwordHandSocketName);
}
}

function Vector GetSwordSocketLocation(Name SocketName)
{
local Vector SocketLocation;
local Rotator SwordRotation;
local SkeletalMeshComponent SMC;

SMC = SkeletalMeshComponent(Mesh);

if (SMC != none && SMC.GetSocketByName(SocketName) != none)
{
SMC.GetSocketWorldLocationAndRotation(SocketName, SocketLocation, SwordRotation);
}

return SocketLocation;
}

function bool AddToSwingHitActors(Actor HitActor)
{
local int i;

for (i = 0; i < SwingHitActors.Length; i++)
{
if (SwingHitActors[i] == HitActor)
{
return false;
}
}

SwingHitActors.AddItem(HitActor);
return true;
}

function TraceSwing()
{
local Actor HitActor;
local Vector HitLoc, HitNorm, SwordTip, SwordHilt, Momentum;
local int DamageAmount;

SwordTip = GetSwordSocketLocation(SwordTipSocketName);
SwordHilt = GetSwordSocketLocation(SwordHiltSocketName);
DamageAmount = FCeil(InstantHitDamage[CurrentFireMode]);

foreach TraceActors(class'Actor', HitActor, HitLoc, HitNorm, SwordTip, SwordHilt)
{
if (HitActor != self && AddToSwingHitActors(HitActor))
{
Momentum = Normal(SwordTip - SwordHilt) * InstantHitMomentum[CurrentFireMode];
HitActor.TakeDamage(DamageAmount, Instigator.Controller, HitLoc, Momentum, class'DamageType');
PlaySound(SwordClank);
}
}
}

function RestoreAmmo(int Amount, optional byte FireModeNum)
{
Swings[FireModeNum] = Min(Amount, MaxSwings);
}

function ConsumeAmmo(byte FireModeNum)
{
if (HasAmmo(FireModeNum))
{
Swings[FireModeNum]--;
}
}

simulated function bool HasAmmo(byte FireModeNum, optional int Ammount)
{
return Swings[FireModeNum] > Ammount;
}

simulated function FireAmmunition()
{
StopFire(CurrentFireMode);
SwingHitActors.Remove(0, SwingHitActors.Length);

if (HasAmmo(CurrentFireMode))
{
if (MaxSwings - Swings[0] == 0) {
MeleeWeaponPawn(Owner).SwingAnim.PlayCustomAnim('S wordSwing', 1.0);
} else {
MeleeWeaponPawn(Owner).SwingAnim.PlayCustomAnim('S wingTwo', 1.0);
}

PlayWeaponAnimation(SwordAnimationName, GetFireInterval(CurrentFireMode));

super.FireAmmunition();
}
}

simulated state Swinging extends WeaponFiring
{
simulated event Tick(float DeltaTime)
{
super.Tick(DeltaTime);
TraceSwing();
}

simulated event EndState(Name NextStateName)
{
super.EndState(NextStateName);
SetTimer(GetFireInterval(CurrentFireMode), false, nameof(ResetSwings));
}
}

function ResetSwings()
{
RestoreAmmo(MaxSwings);
}

DefaultProperties
{
MaxSwings=2
Swings(0)=2

bMeleeWeapon=true;
bInstantHit=true;
bCanThrow=false;

FiringStatesArray(0)="Swinging"

WeaponFireTypes(0)=EWFT_Custom

Begin Object Class=SkeletalMeshComponent Name=SwordSkeletalMeshComponent
bCacheAnimSequenceNodes=false
AlwaysLoadOnClient=true
AlwaysLoadOnServer=true
CastShadow=true
BlockRigidBody=true
bUpdateSkelWhenNotRendered=false
bIgnoreControllersWhenNotRendered=true
bUpdateKinematicBonesFromAnimation=true
bCastDynamicShadow=true
RBChannel=RBCC_Untitled3
RBCollideWithChannels=(Untitled3=true)
bOverrideAttachmentOwnerVisibility=true
bAcceptsDynamicDecals=false
bHasPhysicsAssetInstance=true
TickGroup=TG_PreAsyncWork
MinDistFactorForKinematicUpdate=0.2f
bChartDistanceFactor=true
RBDominanceGroup=20
Scale=1.f
bAllowAmbientOcclusion=false
bUseOnePassLightingOnTranslucency=true
bPerBoneMotionBlur=true
End Object
Mesh=SwordSkeletalMeshComponent

DelayFireTime(0) = 1; //By default, no delay for the main fire.
DelayFireTime(1) = 0; //By default, no delay for the alt fire.
}



I'm still a noob with unrealscript xD

seenooh
04-15-2012, 06:43 PM
Oh man lol didn't expect a bump after all this time.

Anyways, I'm not exactly sure, but there is probably a change in the original classes given that with each UDK release there is in alot of minor changes to the scripts, so since 2010, I think there are too many differences by now and that causes issues.

But a better alternative is to use AnimNotify. When I wrote that tutorial I didn't know about it probably. Look it up in google and you should find alot of examples.

Slesh
04-16-2012, 07:03 AM
Well.. everything I could find so far regarding Fire Delay on google are some tutorials from 2010 , about AnimNotify so far only some tutorials about its usage on attaching particle effects to weapons and such.

seenooh
04-16-2012, 12:39 PM
What I meant was specifically, AnimNotify_Script. You can define an event of that type in the Animset editor, say "DoAttack". You can set when you can fire it on the timeline. Then you go to your Pawn subclass and write a function with the exact name:


function DoAttack()
{

//etc

}


If you want more in depth examples I highly recommend downloading Dungen Defense source which is available on the UDN. This technique has been used alot on their enemy characters like the goblin.

aerosk
12-28-2012, 11:06 AM
Great job! It works pretty good at the first person, but when i try the third person, everything is delayed, even the animation... I think the problem comes from the attachment class of my weapon, do you have any idea to help me?:confused:
This is the code of my weapon class:


class WP_Hache extends OCWeapon;

defaultproperties
{
bInstantHit=true
bMeleeWeapon=true

Begin Object class=AnimNodeSequence Name=MeshSequenceA
bCauseActorAnimEnd=true
End Object

Begin Object Name=FirstPersonMesh
SkeletalMesh=SkeletalMesh'WP_Hache.Mesh.hache'
AnimSets(0)=AnimSet'WP_Hache.AnimSet.K_Hache'
Animations=MeshSequenceA
Scale=0.95
End Object
PlayerViewOffset=(X=45,Y=0,Z=-35.0)
Mesh=FirstPersonMesh

WeaponFireTypes(0)=EWFT_InstantHit
InstantHitDamage(0)=30
InstanthitDamageTypes(0)=UTDmgType_Telefrag

WeaponFireTypes(1)=EWFT_InstantHit
InstantHitDamage(1)=100
InstanthitDamageTypes(1)=UTDmgType_Telefrag

WeaponRange(0)=150

FireInterval(0)=+0.4
DelayFireTime(0) = 0.1;
ShouldFireOnRelease(0)=0
FireInterval(1)=+0.9
DelayFireTime(1) = 0.6;
ShouldFireOnRelease(1)=0

WeaponFireAnim(0)=WeaponFire
WeaponFireAnim(1)=WeaponAltFire

ShotCost(0)=0
ShotCost(1)=0
AmmoCount=1
MaxAmmoCount=1

AimingHelpRadius=180

WeaponFireSnd[0]=SoundCue'WP_Hache.Effects.A_Coup2'
WeaponFireSnd[1]=SoundCue'WP_Hache.Effects.A_Attaque'
WeaponEquipSnd=SoundCue'A_Weapon_ShockRifle.Cue.A_ Weapon_SR_RaiseCue'
WeaponPutDownSnd=SoundCue'A_Weapon_ShockRifle.Cue. A_Weapon_SR_LowerCue'
PickupSound=SoundCue'A_Pickups.Weapons.Cue.A_Picku p_Weapons_Shock_Cue'

Begin Object Name=PickupMesh
SkeletalMesh=SkeletalMesh'WP_Hache.Mesh.hache'
Scale=0.7
End Object

AttachmentClass=class'zGame.WP_Hache_AT'

EffectSockets[0]=MuzzleFlashSocket
}


and the code of the attachment class:


class WP_Hache_AT extends UTWeaponAttachment;


defaultproperties
{
Begin Object class=AnimNodeSequence Name=MeshSequenceA
bCauseActorAnimEnd=true
End Object

Begin Object Name=SkeletalMeshComponent0
SkeletalMesh=SkeletalMesh'WP_Hache.Mesh.hache'
AnimSets(0)=AnimSet'WP_Hache.AnimSet.K_Hache'
Animations=MeshSequenceA
Scale=0.6
Rotation=(Yaw=16384)
End Object
WeaponClass=class'WP_Hache'

FireAnim=WeaponFire
AltFireAnim=WeaponAltFire

}


PS: I just started to use unrealscript, so I may have made very basic mistakes...