伤害处理

Hi there, last time we’ve learned how to create weapons. 

However, we still don’t know how to make a weapon that hurts. Yet.

I’ll cover this area in 3 parts. First, We’ll have a look at how what tools UDK gives us to handle damage.

 Then we’ll see how to take damage. And finally, we’ll see how to inflict damage.

Hit/Damage-related classes and structures

Unreal Engine’s take on damage dealing is ruled by the following philosophy: 

There should be little to no interdependency between the damage dealer and its victim. 

So when Actor A hurts Actor B, Actor A gathers as much information as possible about the hit, 

and then give it all to Actor B that will handle all by itself all the consequences of receiving that damage.

UDK has built-in support for damage dealing, and offers several functions to handle it. However,

 NO concept of health is supported at Actor level. The Pawn class however does implement some

 management of health (we’ll have a look at it further down). But if you want health management for

 a very simple actor, you’ll have to implement it yourself. Or subclass Pawn, which sounds to me like overkill, and as such a very bad idea.

Several structures and classes have been defined to regroup information.

TraceHitInfo structure

This structure holds several optional information about what has been hit and where, such as

 the Material (to trigger different sounds or define a different behaviour (e.g. bouncing on specific surfaces)) 

or the bone hit (for localized damage). Below is the structure definition taken from Actor.uc (UDK Oct 2010):

1
2
3
4
5
6
7
8
9
struct native transient TraceHitInfo
{
    var Material Material; // Material we hit.
    var PhysicalMaterial PhysMaterial; // The Physical Material that was hit
    var int Item; // Extra info about thing we hit.
    var int LevelIndex; // Level index, if we hit BSP.
    var name BoneName; // Name of bone if we hit a skeletal mesh.
    var PrimitiveComponent HitComponent; // Component of the actor that we hit.
};


ImpactInfo structure

Stores what has been hit, where and from which direction. Below is the structure definition taken from Actor.uc (UDK Oct 2010):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Hit definition struct. Mainly used by Instant Hit Weapons. */
struct native transient ImpactInfo
{
    /** Actor Hit */
    var Actor HitActor;
 
    /** world location of hit impact */
    var vector HitLocation;
 
    /** Hit normal of impact */
    var vector HitNormal;
 
    /** Direction of ray when hitting actor */
    var vector RayDir;
 
    /** Start location of trace */
    var vector StartTrace;
 
    /** Trace Hit Info (material, bonename...) */
    var TraceHitInfo HitInfo;
};

DamageType Class

As the comment says at the top of the file, this static class is an information holder. Each time damage is dealt, you must specify what kind of damage is dealt, regardless of how the damage has been dealt. Note that DamageType is an abstract class, which means you MUST create a subclass of it for your game.

The concept of Damage Type has several upsides:

  • Different weapons can have common damage effects.
  • Conversely, one weapon can have the same behaviour but different effects (e.g. Elemental damage)
  • By knowing the kind of damage that hit an actor, you can set up immunities.
  • It’s useful if you need to get stats from the damage taken.

The only downside I can think of is that you might end up writing lots of DamageType subclasses (UDK’s UTGame has 20, and there are only 3 “real” weapons).

In the base DamageType class, the most noteworthy properties are the following:

  • bool bArmorStops: tells if this damage type ignores armour (note that this one might be a bit out of place as the concept of armour only exists in UTGame).
  • bCausedByWorld: Set to true if the damage hasn’t been caused by an Actor.
  • float KDamageImpulse: used to move KActors upon impact.
  • float RadialDamageImpulse: used to move KActors upon impact, in the event of an area effect.
  • float VehicleDamageScaling: Percentage of damage taken by a vehicle (1.0 = 100%).
  • bool bCausesFracture: if set to true, this damage type can break Fracture Meshes
  • float FracturedMeshDamage: tells how much it affects fractured meshes. Defaults to 1.0.

Chances are you’ll be mostly using your own variables. 

The Engine Package already defines Damage Types for being crushed or telefragged, falling, or killing one’s self.

Taking Damage

The Actor class defines a few functions that take care of receiving damage. 

Yes, the Actor class. Which means nearly ANYTHING in the game can potentially take damage,

 even weapons (Bethesda’s games, anyone?). Let’s have a look at those functions.

TakeDamage

The obvious one. It’s called by the Actor that causes the damage. Has the following parameters:

  • int DamageAmount: self-explanatory. Note that this is an int.
  • Controller EventInstigator: who (player) hit me?
  • vector HitLocation: where have I been hit?
  • vector Momentum: with how much force have I been hit? (for physics-related effects, mainly)
  • class<DamageType> DamageType: self-explanatory
  • optional TraceHitInfo HitInfo: additional info on the hit.
  • optional Actor DamageCauser: what hit me?

Default implentation does nothing but notify Kismet that the Actor has been hit. So if you override this function,

 don’t forget to call super.TakeDamage.

TakeRadiusDamage

This is a wrapper that, as the function’s description says “by default scales damage based on distance from HurtOrigin to Actor’s location.

 [...] This then calls TakeDamage() to go through the same damage pipeline” .


1
simulated function TakeRadiusDamage ( Controller InstigatedBy, float BaseDamage,
float DamageRadius, class DamageType, float Momentum, vector HurtOrigin, 
bool bFullDamage, Actor DamageCauser, optional float DamageFalloffExponent=1.f )



Nothing much to say here. Just note that here the DamageCauser is not optional this time, 

and the Momentum is only a float as the vector is created according to the position of the actor and the impact point of origin.

HealDamage

While I probably would have used TakeDamage with a negative amount to heal damage, 

there is a specific function for that (whose default implementation does nothing). 

The upside of the HealDamage function is that it requires much less information.

 The only parameters are the amount of healing, the  healer’s controller, 

and the “Damage” Type (if for instance you can heal life and armor separately).

Example: Spinning box

Here’s a very simple example of implementation of damage taking. I have a box that when shot at starts to spin.

 The more damage it takes, the faster it turns. The rotation speed decreases overt time, so if I stop shooting,

 it will eventually stop. Nothing exciting, although I can imagine this kind of mechanic used for weapon activated switches.

Here’s the code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class SandboxSpinningBox extends Actor placeable;
 
var float RotatingSpeed;
var float MaxSpeed;
var float SpeedFade;
 
auto state Spinning
{
    event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, 
vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
    {
        super.TakeDamage(DamageAmount,EventInstigator, HitLocation,Momentum,DamageType,HitInfo,DamageCauser);
 
        WorldInfo.Game.Broadcast(self,"Damage Taken:"@DamageAmount); RotatingSpeed += DamageAmount*100;
    }
 
    event Tick(float DeltaTime)
    {
        local Rotator final_rot;
        final_rot = Rotation;
 
        RotatingSpeed = FMax(RotatingSpeed - SpeedFade* DeltaTime,0);
        final_rot.Yaw = final_rot.Yaw + RotatingSpeed*DeltaTime;
        SetRotation(final_rot);
    }
}
 
DefaultProperties
{
    Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
        bEnabled=TRUE
    End Object
 
    LightEnvironment=MyLightEnvironment
 
    Components.Add(MyLightEnvironment)
 
    begin object Class=StaticMeshComponent Name=BaseMesh
        StaticMesh=StaticMesh'EngineMeshes.Cube'
        LightEnvironment=MyLightEnvironment
    end object
    Components.Add(BaseMesh)
    CollisionComponent=BaseMesh
 
    bCollideActors=true
    bBlockActors=true
 
    RotatingSpeed=0.0
    SpeedFade=5000.0
    MaxSpeed= 10000
}



And here’s the result:

Pawns taking damage

Pawns being a bit more complex Actors, the Pawn class has some additional stuff to do in order to deal with damage.

 It has a basic implementation of an Health System. Default Health value (and max health) is 100. 

The Pawn class overrides the TakeDamage function. It does mainly physics stuff, but here are a few things we should keep in mind:

  • It ignores negative damage amount (considers it as 0). So don’t try to heal someone this way.
  • It lets lots of things adjust the amount of damage actually taken. In order:
    • The driven vehicle (if there is any)
    • The GameInfo (mostly to give the mutators a chance to affect damage (InstaGib, for instance))
    • The pawn itself, through the AdjustDamage function (in case the pawn has some inventory items that can affect its resistance to damage).

After the actual damage has been taken, it is subtracted from the pawn’s health. If it doesn’t reach 0,

 things that need to know that the pawn has been hit (the driven vehicle and the pawn’s controller) are notified. 

Pawns also store which Controller was the last to hit them (to figure out who got the frag).

If the Health is down to 0, it means (at least in the default implementation) that the pawn is dead.

 The funeral process starts by calling the Died function, which does lots of stuff. Mainly cleanly destroying the pawn,

 triggering Kismet events, sending obituaries to whoever cared about the poor chap:

  • Its weapon via Weapon.HolderDied (so it stops firing).
  •  If the weapon must be dropped, ThrowWeaponOnDeath is called right after.
  • The vehicle he was driving via DrivenVehicle.DriverDied.
  • The GameInfo via GameInfo.Killed (e.g.: if that was the hostage you were supposed to save).
  • Its inventory manager via InvManager.OwnerDied (e.g.: dropping loot).

Then it calls PlayDying which will send the Pawn in the Dying state, where dying anims, sounds and ragdolls should be handled.

NOTE: Although it doesn’t seem to be used anywhere, 

it’s good to know that the Pawn class defines a function called TakeRadiusDamageOnBones. 

Quite self explanatory, and proably quite useful for localized damage.

Below is a small video where I trace the damage taken by a pawn.

Inflicting damage

Basics

Inflicting damage, in its most basic form, is actually plain simple.

 All you need to do is to call the soon-to-be damaged actor’s TakeDamage function (or TakeRadiusDamage).

 The less plain simple part is finding all the information we need pass to the function. Let’s have a look at how it works in the case of weapons.

Instant hit shots

We’ve seen in the tutorial on weapons that the InstantFire function makes a Trace to find everything the shot has hit, 

put the information together in an ImpactInfo structure, and then calls ProcessInstantHit for every ImpactInfo it created.

The defaultImplementation of ProcessInstantHit does a few things:

  1. It computes the total damage (base damage * number of hits).
  2. It checks if the hit actor is a static mesh that can become dynamic (one of the engine’s quite recent features).
  3. Regardless of the outcome of the previous step, it calls the hit actor’s TakeDamage.

Looking at the code, we see it’s using some firemode dependant arrays:

  • InstantHitDamage: The amount of damage for the fire mode.
  • InstantHitMomentum: The force applied by a shot of this fire mode upon impact. Note that these are floats that are multiplied by the hit’s direction.
  • InstantHiDamageTypes: The DamageType class for the given fire mode.

Why isn’t there similar arrays for projectiles? Simply because this information is stored within the projectile instance.

Inflict Radius Damage

There’s also an application a function for that. It’s called HurtRadius, 

and is a member of the Actor class. Meaning that as well as taking damage,

 pretty much everything you place in the game can potentially inflict radius damage. The function takes the following parameters:

  • float BaseDamage: In default implementation, it’s the damage at the epicentre of the “explosion”.
  • float DamageRadius: Radius of the “explosion” in Unreal Units.
  • class<DamageType> DamageType: you should know what that is by now.
  • float Momentum: same for that one.
  • vector HurtOrigin: The point of origin of the “explosion”. You’ll most likely want to use the actor’s location.
  • optional Actor IgnoredActor: an actor that should ignore the effects of the “explosion”.
  • optional Controller InstigatedByController: who started to blow things up.
  • optional bool bDoFullDamage: you know that one from TakeRadiusDamage.

The default implementation of HurtRadius gets all actors in the radius, and for each eligible actor, it calls its TakeRadiusDamage.

“But what the hell makes an actor eligible?” Well, firstly, not being the explosion’s instigator.

 Secondly, not being World Geometry. Thirdly, and most importantly, having the bCanBeDamaged property set to true.

 We haven’t heard about that one before, because it’s only tested in HurtRadius. 

For consistency’s sake, it might be a good idea to check its value in TakeDamage as well.

In the event of one of the actors caught in the radius damage triggers itself radius damage, 

a variable (bHurtEntry) prevents an actor from being affected more than once by one chain reaction.

 It’s important to keep that if you override the function, as all this is done in the same frame.

Example of Radius Damage

I’ve set up a simple actor that goes from green to red according to the damage it has taken. and when it “dies”, it damages actors around it.

Here’s the actor’s code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class SandboxExplodingCylinder extends Actor placeable;
 
var float Health;
var float MaxHealth;
 
var StaticMeshComponent ColorMesh;
 
var MaterialInstanceConstant mat;
 
simulated function PostBeginPlay()
{
    mat = ColorMesh.CreateAndSetMaterialInstanceConstant(0);
}
 
event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum,
class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
    super.TakeDamage(DamageAmount,EventInstigator, HitLocation,Momentum,DamageType,HitInfo,DamageCauser);
    Health = FMax(Health-DamageAmount,0);
    WorldInfo.Game.Broadcast(self,Name$": Health:"@Health);
 
    mat.SetScalarParameterValue('Damage',(MaxHealth - Health)/100);
 
    if (Health == 0)
    {
        HurtRadius ( 75, 128, DamageType, 0, Location, None, EventInstigator, false );
        GotoState('Dead');
    }
}
 
auto state Alive
{
}
 
state Dead
{
    ignores TakeDamage,TakeRadiusDamage;
 
begin:
    ColorMesh.SetHidden(true); //Note that it's only hidden, it's still there and colliding.
}
 
DefaultProperties
{
    Begin Object class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
        bEnabled=TRUE
    End Object
 
    LightEnvironment=MyLightEnvironment
    Components.Add(MyLightEnvironment)
 
    begin object class=StaticMeshComponent Name=BaseMesh
        StaticMesh=StaticMesh'SandboxContent.Meshes.SM_ExplodingCylinder'
        LightEnvironment=MyLightEnvironment
    end object
 
    ColorMesh=BaseMesh
    Components.Add(BaseMesh)
    CollisionComponent=BaseMesh
 
    bCanBeDamaged=true
    bCollideActors=true
    bBlockActors=true
    Health=100
    MaxHealth=100
}


And here’s the result:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值