Announcement

Collapse

The Infinity Blade Forums Have Moved

We've launched brand new Infinity Blade forums with improved features and revamped layout. We've also included a complete archive of the previous posts. Come check out the new Infinity Blade forums.
See more
See less

Basic melee combat system(combo's included) *Updated 21/04/11

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

    #16
    ok got my content in, but the weapons first person anims don't play, i'm willing to make a few anims for the 3rd person if we can get it all working

    Comment


      #17
      Yup that was point 3 to be aware of.
      This is because I only start a upper body or full body animation, you'd need to add(or replace) code that starts up first person animations by yourself.

      Comment


        #18
        just a thought, not being a coder, if i put that code into the attachment file would it still work, my thinking is the the attachment file deals with the 3rd person content

        Comment


          #19
          The attachment deals with 3rd person weapon content, not player. Why would you move anything player related there?

          This here:
          Code:
          function StartAttack()
          {
          	// Play the animation
          UTPawn(Instigator).TopHalfAnimSlot.PlayCustomAnim(SwordAttackAnim[ComboIndex], 1.f,,,,,, SwordAttackDuration[ComboIndex]);
          Is more or less where you'll need to be, I don't know how to start playing first person arms mesh animations though, so you'd have to figure that out and try to start it here.

          Comment


            #20
            ok i'l see what i can do, thanks for your help

            Comment


              #21
              This might work via script...

              http://wiki.beyondunreal.com/UE3:Ani...rossfader_(UDK)

              Comment


                #22
                Looks really good man. Thats a HELL of a lot of coding though! How are you handling the contact of the attacks? Is it using a system that checks the frame of the attack animation, so it only inflicts damage if say, the anim is between frame 10-30? That way you only inflict damage if the sword is swinging with enough velocity. Maybe you could even use animation speed to achieve the same thing?

                Comment


                  #23
                  Originally posted by shuriken88 View Post
                  Looks really good man. Thats a HELL of a lot of coding though! How are you handling the contact of the attacks? Is it using a system that checks the frame of the attack animation, so it only inflicts damage if say, the anim is between frame 10-30? That way you only inflict damage if the sword is swinging with enough velocity. Maybe you could even use animation speed to achieve the same thing?
                  Currently it sets a timer to indicate when to start dealing damage(would then be set to a reasonable time during the animation, where as you say after a certain point in the animation it can deal damage), it does this until the attack ends, so as you can tell, not all that great yet, it's also like I said not meant to be for a realistic melee game.

                  Regardless now that I discovered the joy of anim notifies, I'll working on updating the system(and when that's done update this tutorial) to do it with anim notifies, to provide a much better way of handling the damage timing.

                  Comment


                    #24
                    Originally posted by geodav View Post
                    ok got my content in, but the weapons first person anims don't play, i'm willing to make a few anims for the 3rd person if we can get it all working
                    You can use PlayWeaponAnimation to play firstperson animations.

                    Comment


                      #25
                      Right, it's hot and I'm tired, so I'm just gonna plunk the update code which uses anim notifies instead of timers here instead of rewriting the tutorial, that way you can also choose the method you prefer.

                      First up, I've chosen to use an interface, rather than create a base class that might get in the way of your weapon class structure to help implement the change to anim notifies.

                      So first up, create a new class file called something like Custom_WeaponNotifyInterface, plonk this there:
                      Code:
                      interface Custom_WeaponNotifyInterface;
                      
                      /**
                       * Actually perform attack damage
                       */
                      function DoAttack();
                      
                      /**
                       * Actually perform spin attack damage
                       */
                      function DoSpinAttack();
                      
                      /**
                       * Point in time on the animation where a combo can start
                       */
                      function ComboStart();
                      
                      /**
                       * Finishing up the attack
                       */
                      function EndAttack();
                      
                      /**
                       * Start moving during the attack
                       */
                      function StartAttackMovement();
                      
                      /**
                       * Stop previously engaged attack movement
                       */
                      function EndAttackMovement();
                      These are pretty much the much the functions we already had in the sword weapon.

                      Next, since we're using third person animations on the pawn when attacking, this means that anim notifies get done on the pawn and not the weapon, so we need to use the interface to call the functions of the same name on any held weapon that uses these anim notifies in this way.

                      So updated Custom_Pawn.uc with:
                      Code:
                      function DoAttack()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.DoAttack();
                      }
                      
                      function DoSpinAttack()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.DoSpinAttack();
                      }
                      
                      function ComboStart()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.ComboStart();
                      }
                      
                      function EndAttack()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.EndAttack();
                      }
                      
                      function StartAttackMovement()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.StartAttackMovement();
                      }
                      
                      function EndAttackMovement()
                      {
                      	local Custom_WeaponNotifyInterface WeaponNotifyInterface;
                      
                      	WeaponNotifyInterface = Custom_WeaponNotifyInterface(Weapon);
                      	if(WeaponNotifyInterface != None)
                      		WeaponNotifyInterface.EndAttackMovement();
                      }
                      Feel free to remove the interface usage if you so want, just be aware, if you use multiple weapons that work like this, then it'll be more of a pain and more error prone to upkeep, but if you use only class, using the interface is silly too.

                      If you don't use weapons that have somewhat class structures, but all just extend UTWeapon or Weapon, you could create a base class that can work like the interface, I just chose an interface to not get in the way of your classes.

                      ---
                      Now the weapon itself didn't change all that much, I just removed some now unneeded variables and got rid of all the timers, save for the one required to "charge" the spin attack.

                      So code dump will follow, see comments for more info.
                      Code:
                      class Custom_Weapon_SwordAndShield extends UTWeapon
                      	implements(Custom_WeaponNotifyInterface);
                      
                      /**
                       * Normal Sword Attack values
                       */
                      var name SwordAttackAnim[3]; // Names of the sword attack animations
                      
                      var bool bInSwordAttack; // Indicates if we're doing a sword attack at the moment
                      
                      var float SwordAttackHitRange[3]; // Range of a normal sword attack
                      var float SwordAttackHitDamage[3]; // Damage of a normal sword attack if it hits
                      
                      /**
                       * Spin Attack values
                       */
                      var name SpinAttackAnim; // Name of the Spin Attack animation
                      
                      var float SpinAttackChargeTime; // Time needed holding the attack button to charge a spin attack
                      
                      var float SpinAttackRange; // Range of the spin attack
                      var float SpinAttackHitDamage; // Damage of the spin attack if it hits
                      
                      var bool bSpinned; // True if did a spin attack in the same click as click being released.
                      
                      /**
                       * Combo attack Values
                       */
                      var bool bComboGapOpen; // Indicates if we're inside the combo gap, which means we can queue up a new combo by hitting attack again
                      var bool bComboAttackQueued; // Indicates if we've got a next combo attack ready to go
                      
                      var float SwordCooldownTime; // Sword cooldown time after ending an attack
                      var float LastSwordAttackTime; // Time the player stopped attacking
                      
                      var int MaxCombo; // Maximum combo hits
                      var int ComboIndex; // Index of the current combo
                      
                      // Overriden to start a normal attack when the mouse is released(if allowed to)
                      simulated function EndFire(Byte FireModeNum)
                      {
                      	// Handle actual EndFire logic
                      	super.EndFire(FireModeNum);
                      
                      	// Only primary attack is accepted
                      	if(FireModeNum != 0)
                      		return;
                      
                      	// We're doing a normal attack, because we didn't hold attack long enough for a spin attack.
                      	ClearTimer(nameOf(StartSpinAttack));
                      
                      	// If we spinned in the same click before, we should'n do a normal attack
                      	if(bSpinned)
                      	{
                      		// Reset spinned
                      		bSpinned = false;
                      		return;
                      	}
                      
                      	// We've got a combo attack coming up, don't bother trying to attack again
                      	if(bComboAttackQueued)
                      		return;
                      
                      	// If we're not in the last combo
                      	if(ComboIndex < MaxCombo - 1)
                      	{
                      		// If we're inside the combo gap, we can start a combo
                      		if(bComboGapOpen)
                      		{   
                      			// Queue up the combo attack
                      			bComboAttackQueued = true;
                      
                      			// Start a new combo next
                      			ComboIndex++;
                      		}
                      		else
                      		{
                      			// If there's not a normal attack active already
                      			if(!bInSwordAttack)
                      			{
                      				// If we haven't passed the cooldown yet, queue up the attack
                      				if(WorldInfo.TimeSeconds - LastSwordAttackTime < SwordCooldownTime)
                      					SetTimer(WorldInfo.TimeSeconds - LastSwordAttackTime,, nameOf(StartAttack));
                      				else
                      					StartAttack(); // Start up an attack right away
                      			}
                      		}
                      	}
                      }
                      
                      // Shouldn't refire automatically, only register one attack push.
                      simulated function bool ShouldRefire()
                      {
                      	return false;
                      }
                      
                      // Starts up automatic attack movement when attacking
                      function StartAttackMovement()
                      {
                      	local Custom_PlayerController Player;
                      
                      	// If player
                      	if(Instigator.IsPlayerPawn())
                      	{
                      		Player = Custom_PlayerController(Instigator.Controller);
                      
                      		// Enable automatic movement
                      		Player.bInSwordAttackMovement = true;
                      
                      		// Reset so any ignored input attempts are cleared
                      		Player.ResetPlayerMovementInput();
                      	}
                      }
                      
                      // Stops ongoing attack movement and sets player input to be ignored until attack ends
                      function EndAttackMovement()
                      {
                      	local Custom_PlayerController Player;
                      
                      	// If player
                      	if(Instigator.IsPlayerPawn())
                      	{
                      		Player = Custom_PlayerController(Instigator.Controller);
                      
                      		// Disable automatic movement
                      		Player.bInSwordAttackMovement = false;
                      
                      		// Disable player input
                      		Player.IgnoreMoveInput(true);
                      	}
                      }
                      
                      // Start a normal attack
                      function StartAttack()
                      {
                      	local UTPawn Pawn;
                      
                      	// Clear combo queued status
                      	bComboAttackQueued = false;
                      
                      	Pawn = UTPawn(Instigator);
                      
                      	// Stop the previous custom animation
                      	Pawn.TopHalfAnimSlot.StopCustomAnim(0.f);
                      
                      	// Play the new animation
                      	Pawn.TopHalfAnimSlot.PlayCustomAnim(SwordAttackAnim[ComboIndex], 1.f);
                      
                      	// Mark as in attack
                      	bInSwordAttack = true;
                      
                      	// We can launch a combo now
                      	bComboGapOpen = true;
                      }
                      
                      // Apply damage for a normal attack
                      function DoAttack()
                      {
                      	local Vector Direction, StartTrace, EndTrace, HitLocation, HitNormal;
                      	local Actor HitActor;
                      
                      	// Direction is the direction the pawn is looking in
                      	Direction = Vector(Instigator.Rotation);
                      
                      	// Start a little behind the view location
                      	StartTrace = Instigator.GetPawnViewLocation() + -Direction * 10;
                      
                      	// Trace forward by attack range
                      	EndTrace = Instigator.GetPawnViewLocation() + Direction * SwordAttackHitRange[ComboIndex];
                      
                      	// Hit all things that can be hurt in sword range
                      	ForEach TraceActors(class'Actor', HitActor, HitLocation, HitNormal, EndTrace, StartTrace, vect(96, 96, 0))
                      	{
                      		// Skip the pawn using the sword
                      		if(HitActor == Instigator || !HitActor.bCanBeDamaged)
                      			continue;
                      
                      		// Apply damage with a momentum direction from Instigator location to HitActor location
                      		HitActor.TakeDamage( SwordAttackHitDamage[ComboIndex], Instigator.Controller,
                      						HitActor.Location, Normal(HitActor.Location - Instigator.Location) * 50,
                      						InstantHitDamageTypes[CurrentFireMode],, self);
                      	}
                      }
                      
                      function ComboStart()
                      {
                      	// Gap should be closed now
                      	bComboGapOpen = false;
                      
                      	// If we've got an attack queued we can start that attack
                      	if(bComboAttackQueued)
                      		StartAttack();
                      }
                      
                      // Start trying to do a SpinAttack(releasing attack will result in a normal attack if possible)
                      simulated function FireAmmunition()
                      {
                      	local float ChargeTime;
                      
                      	// Can't do a spin attack if pressing secondary attack, at final combo or the combo gap is open.
                      	if(CurrentFireMode != 0 || ComboIndex == MaxCombo - 1 || bComboGapOpen)
                      		return;
                      
                      	// Stop trying to attack or else this will be called multiple times
                      	ClearPendingFire(CurrentFireMode);
                      
                      	// Just set normal charge first
                      	ChargeTime = SpinAttackChargeTime;
                      
                      	// If we haven't passed the cooldown yet, increase the charge time
                      	if(WorldInfo.TimeSeconds - LastSwordAttackTime < SwordCooldownTime)
                      		ChargeTime += WorldInfo.TimeSeconds - LastSwordAttackTime;
                      
                      	// Start up the spin attack
                      	SetTimer(ChargeTime,, nameOf(StartSpinAttack));
                      }
                      
                      // Start a spin attack
                      function StartSpinAttack()
                      {
                      	// Play the animation
                      	UTPawn(Instigator).FullBodyAnimSlot.PlayCustomAnim(SpinAttackAnim, 1.f);
                      
                      	// If player then disable player input
                      	if(Instigator.IsPlayerPawn())
                      		Custom_PlayerController(Instigator.Controller).IgnoreMoveInput(true);
                      
                      	// Mark as having spinned, prevents normal attack from happening when attack key is released
                      	bSpinned = true;
                      }
                      
                      // Apply damage for a spin attack
                      function DoSpinAttack()
                      {
                      	local Actor TouchActor;
                      
                      	// Check all actors within weapon range, apply damage only on things that aren't the instigator
                      	ForEach VisibleCollidingActors(class'Actor', TouchActor, SpinAttackRange, Instigator.Location, true)
                      	{
                      		// Skip the pawn using the sword
                      		if(TouchActor == Instigator || !TouchActor.bCanBeDamaged)
                      			continue;
                      
                      		// Apply damage with a momentum direction from Instigator location to TouchActor location
                      		TouchActor.TakeDamage( SpinAttackHitDamage, Instigator.Controller,
                      						TouchActor.Location, Normal(TouchActor.Location - Instigator.Location) * 50,
                      						InstantHitDamageTypes[CurrentFireMode],, self);
                      	}
                      }
                      
                      // End any ongoing attack
                      function EndAttack()
                      {
                      	local UTPawn Pawn;
                      
                      	// Make sure to close the combo gap, since final combo animation doesn't require a combo start notify.
                      	bComboGapOpen = false;
                      
                      	Pawn = UTPawn(Instigator);
                      	
                      	// Shut off the animations, but with some blend out
                      	Pawn.FullBodyAnimSlot.StopCustomAnim(0.3f);
                      	Pawn.TopHalfAnimSlot.StopCustomAnim(0.3f);
                      
                      	// If player re-enable player  input
                      	if(Instigator.IsPlayerPawn())
                      		Custom_PlayerController(Instigator.Controller).ResetPlayerMovementInput();
                      
                      	// Reset combo
                      	ComboIndex = 0;
                      
                      	// Mark the time we finished the attack
                      	LastSwordAttackTime = WorldInfo.TimeSeconds;
                      
                      	// Attack ended
                      	bInSwordAttack = false;
                      }
                      
                      DefaultProperties
                      {
                      	// Pickup staticmesh
                      	Begin Object Name=PickupMesh
                      		SkeletalMesh=SkeletalMesh'GDC_Materials.Meshes.SK_ExportSword2'
                      	End Object
                      
                      	Begin Object class=AnimNodeSequence Name=MeshSequenceA
                      		bCauseActorAnimEnd=true
                      	End Object
                      
                      	// Weapon SkeletalMesh
                      	Begin Object Name=FirstPersonMesh
                      		SkeletalMesh=SkeletalMesh'GDC_Materials.Meshes.SK_ExportSword2'
                      		//AnimSets(0)=AnimSet''
                      		Animations=MeshSequenceA
                      		Scale=0.9
                      		FOV=60.0
                      	End Object
                      
                      	SwordAttackAnim(0)=Taunt_UB_ComeHere
                      	SwordAttackAnim(1)=Taunt_UB_Slit_Throat
                      	SwordAttackAnim(2)=Taunt_FB_Pelvic_Thrust_A
                      
                      	SwordAttackHitRange(0)=80.f
                      	SwordAttackHitRange(1)=60.f
                      	SwordAttackHitRange(2)=70.f
                      
                      	SwordAttackHitDamage(0)=15.f
                      	SwordAttackHitDamage(1)=25.f
                      	SwordAttackHitDamage(2)=35.f
                      
                      	SpinAttackAnim=Taunt_FB_Hoolahoop
                      
                      	SpinAttackChargeTime=0.75f
                      
                      	SpinAttackRange=200.f
                      	SpinAttackHitDamage=25.f
                      
                      	SwordCooldownTime=0.6f
                      
                      	MaxCombo=3
                      
                      	FireInterval(0)=0.05f // Should be really fast to better register clicks for combo's
                      
                      	InstantHitDamageTypes(0)=class'Custom_DmgType_SwordCut'
                      
                      	DefaultAnimSpeed=0.9f
                      
                      	AttachmentClass=class'Custom_Attachment_SwordAndShield'
                      	       
                      	PivotTranslation=(Y=-25.0)
                      	       
                      	ShotCost(0)=0
                      
                      	MaxAmmoCount=1
                      	AmmoCount=1
                      }
                      Now, if you'd try to run this, you'd find nothing working, well duh you haven't set the anim notifies, so we'll do that now:

                      1. Open up K_AnimHuman_BaseMale's Animation Set or whatever Animation Set you need to add the notifies to in the Unreal Editor.
                      2. From the tabs top left of the screen that popped up pick: "Anim"
                      3. Scroll or search to find the anims you're using for the attacks and select them.
                      4. On the top menu click "Notifies" then "New Notify"
                      5. On the bottom left it should have opened the tab "AnimSequence" and show the settings for the notify, if not open it, open notifies and open [0](note notifies are ordered in the array automatically by time it seems)

                      6. In notify [0], click the down arrow to the right of the field "Notify", choose "AnimNotify_Script" from the dropdown list.

                      7. Open notify if it didn't open already, choose function name to call(see the pawn functions that were added) and if wanted the duration further below)

                      For examples of mine, here's some screenshots:
                      First combo anim:
                      [SHOT]http://i65.photobucket.com/albums/h208/InfernoNL/meleetutnotify1.jpg[/SHOT]

                      Last combo anim:
                      [SHOT]http://i65.photobucket.com/albums/h208/InfernoNL/meleetutnotify2.jpg[/SHOT]

                      Spin attack anim:
                      [SHOT]http://i65.photobucket.com/albums/h208/InfernoNL/meleetutnotify3.jpg[/SHOT]

                      (note the 2nd combo attack isn't screenshotted here)

                      Info:
                      StartAttackMovement:
                      Starts the attack movement, place on the line doesn't matter as long as it's before EndAttackMovement and AttackAttack.

                      If you don't want StartAttackMovement, you'll need EndAttackMovement if you wish to disable all movement entirely(if you leave both out, player can move freely during attack)

                      EndAttackMovement:
                      Stops the attack movement and freezes player movement input entirely(until EndAttack hits), should be in between StartAttackMovement and EndAttack.

                      Script---Script:
                      This is an notify with duration that doesn't call a Start or End function, just calls DoAttack or DoSpinAttack as it's tick function, damage is done inbetween the start of the notify and the ending of the notify.

                      ComboStart:
                      This is the moment from which a combo attack that was queued(done by clicking after the attack any time before this notify during an attack) will be executed.

                      EndAttack:
                      Wraps up the attack, shuts off the animation(with some blend time) and reset player movement input.

                      Note that, the spin attack(hoolahoop) doesn't use StartAttackMovement and EndAttackMovement, it does have movement input disabling hardcoded in the same function that starts the attack(and isn't a notify) called StartSpinAttack();

                      I hope you find this improvement useful, I think it is because it allows for much more precise timing and changes can be tested pretty much instantly by using Play in Editor.

                      It also opens the editing of the timing up to other team members, instead of just the programmer.

                      Comment


                        #26
                        **** that's intense!

                        I'm a bit confused though, do you get animation blend out and in during function StartAttack()

                        Code:
                        	// Stop the previous custom animation
                        	Pawn.TopHalfAnimSlot.StopCustomAnim(0.f);
                        
                        	// Play the new animation
                        	Pawn.TopHalfAnimSlot.PlayCustomAnim(SwordAttackAnim[ComboIndex], 1.f);
                        Like is the one animation getting blended out while the new animation gets 1.f to get to blend in.

                        Comment


                          #27
                          The first float isn't blend, it's rate Increase it to play the animation faster, decrease to play slower.
                          It's not optional either I recall, which is why I left it there.

                          Also the animation there isn't blending out either as it's set to 0.f blend out time, if you'd try blending there, you'd be trying to play a new animation from an existing animation inside an notify, which causes at least warnings, if not severe problems playing the new anim.

                          Comment


                            #28
                            Hmm, but won't that make the character "snap" in between animations, like animation 1.... snap idle pose, animation 2...... snap idle pose, animation 3

                            Comment


                              #29
                              You're right it could cause that, since this is only upper body though, not all that noticable, but once there's proper animations it might indeed be.

                              You can't blend out when stopping the anim though or it'll give you a warning and it will replay the current anim, instead of playing the next anim in the combo.

                              You could do this:
                              Pawn.TopHalfAnimSlot.PlayCustomAnim(SwordAttackAni m[ComboIndex], 1.f, 0.5f);

                              Not sure how much it helps, alternativelly, you could add a boolean parameter to the function, something like bool bIsCombo, if it's the first attack, call the function with false and have it blend, if it's true don't blend because it's called from a notify, you could try placing the notify in such a way that you don't need blend...

                              just blending in when playing the anim might do the trick though...

                              Comment


                                #30
                                Thanks, I'll give it a go at some point.

                                Comment

                                Working...
                                X