伤害处理
来源:互联网 发布:tensorflow c api 编辑:程序博客网 时间:2024/04/29 15:39
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):
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.
};
Stores what has been hit, where and from which direction. Below is the structure definition taken from Actor.uc (UDK Oct 2010):
/** 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;
};
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” .
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:
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:
- It computes the total damage (base damage * number of hits).
- It checks if the hit actor is a static mesh that can become dynamic (one of the engine’s quite recent features).
- 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:
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:
- 伤害处理
- 伤害
- 敌人受伤处理:受伤时伤害的显示---HUDText
- 无心伤害
- 判断目标是否在角色正前方一个扇形区域内,一般战斗系统中用作伤害处理
- 当心儿童意外伤害
- 细节伤害,五一回家
- 不要再来伤害我
- 无知总是伤害你
- 熬夜伤害身体
- 不要再来伤害我
- 不要伤害自己
- 伤害别人更难受
- 不让伤害再次发生
- 不让伤害再次发生
- 微波炉的致命伤害
- Physx范围伤害检测
- 辐射伤害知多少?
- 计算几何-多边形的重心
- 家居生活新趋势 善用清洁能源更环保
- java 文件读写操作
- Android OpenGL库加载过程源码分析
- 对xdcms1.0的审计学习
- 伤害处理
- 来来来, 建个自己的小站
- 关于二叉树的几种遍历方法
- overdraw优化小结
- 06-SQLite之update、delete
- Openstack Nova(六)----Instance 创建(CLI RESTful请求)
- sql之left join、right join、inner join的区别
- java.lang.OutOfMemoryError: PermGen space的解决方法
- 树的深度优先与广度优先遍历