This tutorial assumes you have successfully set up the skin and known monster issuse as well as the RangedAttack function for your monster.
If your monster is attacking too fast then this might help. There are 2 main ways to control when the monsters attack speed. Using AnimNotifies or a State. This tutorial will assume the monster is firing projectiles too fast. This code can easily be used to work with any kind of monster though.
If your monster is firing too slow, then play around with where you set bShotAnim=true; This what is causing the monster to pause otherwise it may spam none stop!
The first way is to use a state. States are special and allow you to time things better. Ok so using the RangedAttack function from the “How to make your Monster fire projectiles” tutorial we will modify it slightly to go into a state when the monster wants to fire.
If you have been using the Magdalena skin you will notice that the firing animation we chose does not match up right with the spawning of the projectile. The state will fix that.We need to also add a new global variable that will ensure our code is only called once until the spawning is complete. Add the following to your monster class.
var bool bAlreadyAttacking; var class<Projectile> ProjectileClass; var int AimError; function RangedAttack(Actor A) { if ( bShotAnim ) { return; } bShotAnim=true; if(bAlreadyAttacking == false) { if ( Physics == PHYS_Swimming ) { SetAnimAction(IdleSwimAnim); } else if ( Velocity == vect(0,0,0) ) { bAlreadyAttacking = true; GoToState('MyAttackingState'); } } } state MyAttackingState { Begin: SetAnimAction('Gesture_Taunt01'); Sleep(1.0); FireProjectile(); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); bAlreadyAttacking = false; } function FireProjectile() { local vector FireStart,X,Y,Z; if ( Controller != None ) { FireStart = GetFireStart(X,Y,Z); Spawn(ProjectileClass,,,FireStart,Controller.AdjustAim(SavedFireProperties,FireStart,AimError)); PlaySound(FireSound,SLOT_Interact); } }
So what is going on here? Well the RangedAttack function tells the monster to go into the MyAttackingState (as long as bAlreadyAttacking = false, it is false by default). At the same time the bool bAlreadyAttacking is set to true. This code cannot be called again until bAlreadyAttacking is set back to false. Now in the state code, the attack animation is played (change this to match your chosen animation). Then Sleep(1.0); is called. This special Sleep() which can only be used in states pauses the code for 1 second. After which the FireProjectile function is called and bAlreadyAttacking is set back to false. Allowing all this to happen again. After a few tests I discovered that setting Sleep to 1 second was perfect for the Magdalena animation. You will have to tweak this time.
Now we just need to add the defaultproperties.
defaultproperties { ProjectileClass=class'SkaarjPack.KrallBolt' AimError=600 FireSound=Sound'WeaponSounds.ShockRifleAltFire' }
Excellent. It has been fixed! The second way to control attack speed is to use anim notifies. Anim notifes can be used with SkeletalMeshes very easy.The only thing is, it will alter the animation (.ukx) file. And so if you use this you must remember to re-name the .ukx file to something unique.
Anim notifies are set at specific times during the animations in the animation browser. These notifies call special events. Read this page to grasp the basics and then come back where I will walk you through setting up 1 script notify. AnimNotifies – UDN
Ok, so fire up the UnrealEd and open the animation browser. Locate the chosen animation file. In this case the Magdalena.ukx file. First lets RENAME this file. Still in the browser window go to File>Save>Save as unique name. *Warning* The name must be 100% unique, you cannot name it the same as your package file. MyMagdalena.u is the same as MyMagdalena.ukx. I saved my new animation file as MyMagdalenaAnims.ukx. To make our monster use this new file we need to change its defaults to point to this new file. The textures for Magdalena are still from a seperate .utx file (MagdalenaTextures.utx) so we do not need to change them. If they were being referenced in our old aniamtion file we would need to update the defaultproperties accordingly.
defaultproperties { Mesh=SkeletalMesh'MyMagdalenaAnims.Magdalena' }
Be sure to have your new animation file opened in the animation browser. Go to File>Open> open your new file. Next find the animation you want your monster to use when it fires its projectile. In this case for Magdalena we chose the “Gesture_Taunt01” animations.
Move the slider at the bottom until it is at the point when you want it to fire a projectile. In this case around frame 26 seems good. When this is at the correct place click on the Notify tab on the right of the window.
Click “Add” at the top, in the Notify tab. This will add a new notify at the current frame.
Expand the new Notify button, select AnimNotify_Script in the “New” section and hit “New” on the right hand side.
Under NotifyName put the name of the function in our monsters.uc file that we want it to call. In this case input “FireProjectile” without the quotation marks.
Ok, now save the file. Go to File>Save, hit Yes to overwrite. Close the Editor, delete the old .u file ready for a recompile. We just need to make sure the RangedAttack function is set up properly. All we have to do is call the animation we added the notify too. It should look like this.
function RangedAttack(Actor A) { if ( bShotAnim ) { return; } bShotAnim=true; if ( Physics == PHYS_Swimming ) { SetAnimAction(IdleSwimAnim); } else if ( Velocity == vect(0,0,0) ) { SetAnimAction('Gesture_Taunt01'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); } }
So now whenever that animation is played it will try to call the FireProjectile function at the frame we specified. If the function doesn’t exist nothing will happen. Your monster class code should look like this.
class MagdalenaMonster extends Monster; var class<Projectile> ProjectileClass; var int AimError; function RangedAttack(Actor A) { if ( bShotAnim ) { return; } bShotAnim=true; if ( Physics == PHYS_Swimming ) { SetAnimAction(IdleSwimAnim); } else if ( Velocity == vect(0,0,0) ) { SetAnimAction('Gesture_Taunt01'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); } } function FireProjectile() { local vector FireStart,X,Y,Z; if ( Controller != None ) { FireStart = GetFireStart(X,Y,Z); Spawn(ProjectileClass,,,FireStart,Controller.AdjustAim(SavedFireProperties,FireStart,AimError)); PlaySound(FireSound,SLOT_Interact); } } simulated function PlayDirectionalDeath(Vector HitLoc) { local float Decision; Decision = fRand(); if(Decision < 0.25) { PlayAnim('DeathF',, 0.1); } else if ( Decision > 0.25 && Decision < 0.50) { PlayAnim('DeathB',, 0.1); } else if ( Decision > 0.50 && Decision < 0.75) { PlayAnim('DeathL',, 0.1); } else { PlayAnim('DeathR',, 0.1); } } simulated function PlayDirectionalHit(Vector HitLoc) { local float Decision; Decision = fRand(); if(Decision < 0.25) { PlayAnim('TurnL',, 0.1); } else if ( Decision > 0.25 && Decision < 0.50) { PlayAnim('TurnR',, 0.1); } else if ( Decision > 0.50 && Decision < 0.75) { PlayAnim('TurnL',, 0.1); } else { PlayAnim('TurnR',, 0.1); } } defaultproperties { ProjectileClass=class'SkaarjPack.KrallBolt' AimError=600 FireSound=Sound'WeaponSounds.ShockRifleAltFire' Mesh=SkeletalMesh'MyMagdalenaAnims.Magdalena' Skins(0)=Texture'MagdalenaTextures.magdalena_body' Skins(1)=Texture'MagdalenaTextures.magdalena_head' IdleWeaponAnim="Idle_Biggun" IdleHeavyAnim="Idle_Biggun" IdleRifleAnim="Idle_Rifle" TurnRightAnim="TurnR" TurnLeftAnim="TurnL" CrouchAnims(0)="CrouchF" CrouchAnims(1)="CrouchB" CrouchAnims(2)="CrouchL" CrouchAnims(3)="CrouchR" CrouchTurnRightAnim="Crouch_TurnR" CrouchTurnLeftAnim="Crouch_TurnL" AirStillAnim="Jump_Mid" AirAnims(0)="JumpF_Mid" AirAnims(1)="JumpB_Mid" AirAnims(2)="JumpL_Mid" AirAnims(3)="JumpR_Mid" TakeoffStillAnim="Jump_Takeoff" TakeoffAnims(0)="JumpF_Takeoff" TakeoffAnims(1)="JumpB_Takeoff" TakeoffAnims(2)="JumpL_Takeoff" TakeoffAnims(3)="JumpR_Takeoff" LandAnims(0)="JumpF_Land" LandAnims(1)="JumpB_Land" LandAnims(2)="JumpL_Land" LandAnims(3)="JumpR_Land" DodgeAnims(0)="DodgeF" DodgeAnims(1)="DodgeB" DodgeAnims(2)="DodgeL" DodgeAnims(3)="DodgeR" DoubleJumpAnims(0)="DoubleJumpF" DoubleJumpAnims(1)="DoubleJumpB" DoubleJumpAnims(2)="DoubleJumpL" DoubleJumpAnims(3)="DoubleJumpR" MovementAnims(0)="RunF" MovementAnims(1)="RunB" MovementAnims(2)="RunL" MovementAnims(3)="RunR" SwimAnims(0)="SwimF" SwimAnims(1)="SwimB" SwimAnims(2)="SwimL" SwimAnims(3)="SwimR" WalkAnims(0)="WalkF" WalkAnims(1)="WalkB" WalkAnims(2)="WalkL" WalkAnims(3)="WalkR" WallDodgeAnims(0)="WallDodgeF" WallDodgeAnims(1)="WallDodgeB" WallDodgeAnims(2)="WallDodgeL" WallDodgeAnims(3)="WallDodgeR" IdleRestAnim="Idle_Rest" IdleCrouchAnim="Crouch" IdleSwimAnim="Swim_Thread" IdleChatAnim="idle_chat" FireHeavyRapidAnim="Biggun_Aimed" FireHeavyBurstAnim="Biggun_Burst" FireRifleRapidAnim="Rifle_Aimed" FireRifleBurstAnim="Rifle_Burst" HitSound(0)=Sound'SkaarjPack_rc.injur1sk' HitSound(1)=Sound'SkaarjPack_rc.injur1sk' HitSound(2)=Sound'SkaarjPack_rc.injur1sk' HitSound(3)=Sound'SkaarjPack_rc.injur1sk' DeathSound(0)=Sound'SkaarjPack_rc.death1br' DeathSound(1)=Sound'SkaarjPack_rc.death1br' DeathSound(2)=Sound'SkaarjPack_rc.death1br' DeathSound(3)=Sound'SkaarjPack_rc.death1br' ChallengeSound(0)=Sound'SkaarjPack_rc.scuttle1pp' ChallengeSound(1)=Sound'SkaarjPack_rc.scuttle1pp' ChallengeSound(2)=Sound'SkaarjPack_rc.scuttle1pp' ChallengeSound(3)=Sound'SkaarjPack_rc.scuttle1pp' }
Good job!
TIP – AnimNotifies can be used for many useful things like playing sounds and spawning effects.
Recent Comments