| Home Page | Recent Changes

KActor Replication

How it works

The code is based on the replication technique found in KCar. Every tick, the server checks the location, linear and angular velocity of the actor, and if it differs much from when the last replication occured or if more time than MaxNetUpdateInterval has elapsed, the state of the actor on the server will be stored in a variable that is replicated to clients. On recieving the new state, the actor on the client will apply the new state to itself.

Note that RemoteRole is set to ROLE_SimulatedProxy, bNetNotify is set to true (so that PostNetReceive is called on the client) and the KarmaParams property bClientOnly is set to false (so Karma is simulated on the server as well as the client).

The code

class LawDogsKActor extends KActor
    placeable;

var() float MaxNetUpdateInterval;
var float NextNetUpdateTime;

var KRigidBodyState KState, KRepState;
var bool bNewKState;
var int StateCount, LastStateCount;

replication
{
    unreliable if(Role == ROLE_Authority)
        KRepState, StateCount;
}

function Tick(float Delta)
{
    PackState();
}

//Pack current state to be replicated
function PackState()
{
    local bool bChanged;

    if(!KIsAwake())
        return;

    KGetRigidBodyState(KState);

    bChanged = Level.TimeSeconds > NextNetUpdateTime;
    bChanged = bChanged || VSize(KRBVecToVector(KState.Position) - KRBVecToVector(KRepState.Position)) > 5;
    bChanged = bChanged || VSize(KRBVecToVector(KState.LinVel) - KRBVecToVector(KRepState.LinVel)) > 1;
    bChanged = bChanged || VSize(KRBVecToVector(KState.AngVel) - KRBVecToVector(KRepState.AngVel)) > 1;

    if(bChanged)
    {
        NextNetUpdateTime = Level.TimeSeconds + MaxNetUpdateInterval;
        KRepState = KState;
        StateCount++;
    }
    else
        return;
}

//New state recieved.
simulated event PostNetReceive()
{
    if(StateCount == LastStateCount)
        return;
}

//Apply new state.
simulated event bool KUpdateState(out KRigidBodyState newState)
{
    //This should never get called on the server - but just in case!
    if(Role == ROLE_Authority || StateCount == LastStateCount)
        return false;
        
    //Apply received data as new position of actor.
    newState = KRepState;
    StateCount = LastStateCount;

    return true;
}

defaultproperties
{
    MaxNetUpdateInterval=0.5

    StaticMesh=StaticMesh'MiscPhysicsMeshes.Barrels.Barrel'

    Begin Object Class=KarmaParams Name=KarmaParams0
        KMass=0.5
        bHighDetailOnly=False
        bKAllowRotate=True
        KFriction=0.2
        KRestitution=0.5
        KImpactThreshold=1000.0
        bClientOnly=False
        Name="KarmaParams0"
    End Object
    KParams=KarmaParams'KarmaParams0'

    RemoteRole=ROLE_SimulatedProxy
    bNetNotify=True
}

Related Topics

Foxpaw: I think this page could be called something else.. KActor is just a class that you can use to make karma physics objects.. kind of like staticmeshactor. Any actor can have a static mesh, and any actor can use Karma physics.. so I think this should have a more general name seeing as this applies to more than just KActors.

Kaoh: Yeah but the replication technique is special for karma actors. Its not needed for other actors, So I feel it applies.

Foxpaw: What I'm saying is, KActor is just a class that happens to use Karma physics. There are many karma actors that aren't KActors, so I think a more general name would be better.

UsAaR33: Just wondering.. what is the point of that PostNetReceive() event?

Foxpaw: Err.. hmm. I hate to pick holes in someone's work, but you're right. In fact, that code is going to produce slide-show-esque physics client side, (as the physics never actually simulate client side, they get reset to the most recent server version every time the physics is simulated) and err, yeah. It's not doing half the stuff that it looks like it should be doing. However, if it was tested on the same machine, (so no latency or bandwidth limits) it would APPEAR as though it was working perfectly.

Here's how I would have done it: (note that this code has not been tested or compiled in any way, I'm going on experience here. Some tweaks may be necessary. I also removed the "threshold" stuff, but you could put that back in if you really felt it was necessary.)

class ReplicatedKActor extends KActor placeable;

struct RepState
{
  var KRigidBodyState State;
  var byte        Timestamp;
};

var bool     bUpdateState;
var byte     LastTimeStamp;
var RepState RepKState;

replication
{
  unreliable if(Role == ROLE_Authority)
    RepKState;
}

function Tick(float Delta)
{
  Super.Tick( Delta );
  
  PackState();
}

//Pack current state to be replicated
function PackState()
{
  local bool bChanged;
  local KRigidBodyState KState;

  if(!KIsAwake())
    return;

  KGetRigidBodyState(KState);
  if ( KState == RepKState.State )
    return;

  RepKState.State = KState;
  RepKState.TimeStamp++;
}

//New state recieved.
simulated event PostNetReceive()
{
  Super.PostNetRecieve();
  
  if ( RepKState.TimeStamp != LastTimeStamp )
  {
    LastTimeStamp = RepKState.TimeStamp;
    bUpdateState = true;
  }
}

//Apply new state.
simulated event bool KUpdateState(out KRigidBodyState newState)
{
  Super.KUpdateState( newState );
 
  if ( bUpdateState )
  {
    newState = KRepState;
    bUpdateState = false;
    return true;
  }

  return false;
}

defaultproperties
{
  StaticMesh=StaticMesh'MiscPhysicsMeshes.Barrels.Barrel'

  Begin Object Class=KarmaParams Name=KarmaParams0
    KMass=0.5
    bHighDetailOnly=False
    bKAllowRotate=True
    KFriction=0.2
    KRestitution=0.5
    KImpactThreshold=1000.0
    bClientOnly=False
    Name="KarmaParams0"
  End Object
  KParams=KarmaParams'KarmaParams0'

  RemoteRole=ROLE_SimulatedProxy
  bNetNotify=True
}

CIpen: Well I don't mean to break anyone's hearts but KCar isn't a KActor. KCar extends from KVehicle, and KVehicle extends from Vehicle. Lastly, Vehicle extends from Pawn.

Even though it's not in KCar, I found this code that might help in SCar:

cpptext
{
#ifdef WITH_KARMA
    // Actor interface.
    virtual UBOOL Tick(FLOAT DeltaTime, enum ELevelTick TickType);
    virtual void PostNetReceive();

    // SVehicle interface.
    virtual void UpdateVehicle(FLOAT DeltaTime);

    // SCar interface.
    virtual void ProcessCarInput();
    virtual void ChangeGear(UBOOL bReverse);
    virtual void PackState();
#endif
}

I find this interesting because I Have tried to make a rock that you could shoot and blow up(had health).

Solid Snake: This was written before SCar existed, aka UT2003. The above solution allowed for Karma to be replicated over the network thus allowing it to be used in network games for whatever reason you needed it for.

ShrapnelMagnet: I was hoping to replicate a simple box-like KActor in Unreal Tournament 2004. I'd like players to be able to roll boxes around in a little game, but I'm having trouble getting the KActor to replicate across the network. I see a lot of code here, but not being a big coder myself, I'm not sure what works or what doesn't. Any help would be appreciated, thanks.

EricBlade: Well, I have tested the above 2 sets of code, as well as the code from a package I found called "GoodKarma".. the "GoodKarma" developer appears to have disappeared off the face of the planet about a year or so ago, along with the small handful of users who claimed they'd seen it work. Not a bit of this seems to actually function, at least on engine build 2226. As far as I can tell, when running as dedicated server, karma is completely not simulated on the server. Using all three of the codes that I mentioned above, placing the modified kactor in a level, and using the following:

simulated function DoPush() {
    local vector StartTrace, EndTrace, HitLocation, HitNormal;
    local Actor hit;

    StartTrace = Location + EyePosition();
    EndTrace = StartTrace + 196 * vector(Controller.GetViewRotation());
    hit = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);

    Debug(self$": I hit a "$hit);
    if(hit != None) {
        hit.KWake();
       hit.KAddImpulse(34000 * (-hitnormal), location);
       bumpfactor = 7; // we add several wait-ticks before next bump, so we aren't adding that to it too
    }
}

results in my log file on the server saying that my Pawn hit the "ball", accompanied by absolutely nothing. Same thing works in single player, with any of those codes. Logging the "ball"'s location property every tick, tells me that it's not moving anywhere, either. If I shoot it, it starts moving on the client, but not on the server.

... and right after posting that, I had an apparently brilliant idea. Setting bClientOnly = false in DefaultProps doesn't work, but it does work if done in PreBeginPlay(). This results in the physics being simulated completely seperately on both ends. The code from the LawDogsKActor seems to get them following each other for the first tick or so, but very quickly you realise that the server has your KActor in a totally different place than the client put it. Foxpaw's seems to lock the KActor in place on the server, while making the client simulate somewhat slowly. The "GoodKarma" code has no effect over and above LawDogs.

EricBlade: Well, I spent all night playing with this, and thought I'd put up what I have, and see if anyone had any further suggestions. It's sort of a combination of all the code i've seen for dealing with this.. I was hoping to get something forgiveably accurate enough that I could do a little one-on-one soccer with, but unfortunatly, I don't think it's possible to get anywhere near that accurate.. but I will definitely take suggestions. The following code does not have a horribly lagging simulation, but at reasonably decent speeds using my test-ball (the severed head mesh from Land of the Dead), a moment or two after kicking it, the ball will snap into the place the server simulated it at, which is usually fairly close to where it came to rest on the client, however it can sometimes be quite a bit away. I also just now tested this on a test map that had about 6 of these on it. Then with only 2. Having more than one of these on a level causes absolute chaos, with the objects seemingly randomly warping about in the level.

class Ball extends Actor;

var float MaxNetUpdateInterval;
var float NextNetUpdateTime;
var float LastUpdateTime;

var KRigidBodyState KState;
var bool bChanged;

var int counter;


struct repinfo {
    var KRigidBodyState state;
    var float timestamp;
};

var repinfo KRepState;

replication
{
    unreliable if(Role == ROLE_Authority)
        KRepState, bChanged;
}

function Tick(float Delta)
{
    if(Role == ROLE_Authority) {
        if(KIsAwake()) {
            counter = 30;
        } else {
            if(counter > 0) counter--;
        }
        PackState();
    }
}

function PackState()
{
    if(counter < 1 && !KIsAwake())
        return;
    bChanged = false;
    KGetRigidBodyState(KState);

    if(VSize(KRBVecToVector(KState.Position) - KRBVecToVector(KRepState.state.Position)) > 10) {
        //log(self@"Position Change");
        bChanged = true;
    }
    if(VSize(KRBVecToVector(KState.LinVel) - KRBVecToVector(KRepState.state.LinVel)) > 10) {
        //log(self@"LinVel Change");
        bChanged = true;
    }
    if(VSize(KRBVecToVector(KState.AngVel) - KRBVecToVector(KRepState.state.AngVel)) > 10) {
        //log(self@"AngVel");
        bChanged = true;
    }
    if(bChanged == false && Level.TimeSeconds > NextNetUpdateTime) {
        //log(self@"MaxNetUpdateInterval reached");
        bChanged = true;
    }
    if(bChanged == true) {
        KRepState.state = KState;
        NextNetUpdateTime = Level.TimeSeconds + MaxNetUpdateInterval;
        KRepState.timestamp = Level.TimeSeconds;
    }

}

simulated event bool KUpdateState(out KRigidBodyState newState)
{
    if(Role == ROLE_Authority)
        return false;
    if(bChanged == false) return false;
    if(LastUpdateTime > KRepState.timestamp) return false;
    newState = KRepState.state;
    LastUpdateTime = KRepState.timestamp;
    bChanged = false;
    return true;
}

simulated event PreBeginPlay() {
    KarmaParams(KParams).bClientOnly = false;
}


DefaultProperties
{
    MaxNetUpdateInterval=0.2
    bBlockZeroExtentTraces = true
    bBlockNonZeroExtentTraces = true
    //NetUpdateFrequency = 1
    //bUpdateSimulatedPosition=true
    //bGameRelevant=true
    bAlwaysRelevant=true
    bCollideWorld=true
    bNetNotify=true
    RemoteRole=ROLE_SimulatedProxy
    DrawScale=2.75
    Physics=PHYS_Karma
    Begin Object Class=KarmaParams Name=KParams0
        bHighDetailOnly         =   False
        bKNonSphericalInertia   =   False
        KActorGravScale         =   0.55
        KAngularDamping         =   0.2
        KBuoyancy               =   1.01
        KLinearDamping          =   0.2
        KMass                   =   0.45
        KStartEnabled           =   False
        KFriction               =   0.40
        KRestitution            =   0.50
        KImpactThreshold        =   5
    End Object
    KParams=KarmaParams'KParams0'
    StaticMesh=StaticMesh'UZGMeshes.Body.Head'
    DrawType=DT_StaticMesh
//   bNoDelete=True
     bCollideActors=True
     bBlockActors=True
     bBlockPlayers=True
     bProjTarget=True
     bBlockKarma=True
}

Category Custom Class
Category Tutorial
Refactor Me
Category To Do – If this really is stuff unique to Karma and Replication then Replication/Karma?, Karma/Replication? or [Karma Replication]? might be a better location for this. Otherwise this should probably be folded into the grand topic of Replication. See Replication/Discussing

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

FAQs

Help Desk

Mapping Topics

Mapping Lessons

UnrealEd Interface

UnrealScript Topics

UnrealScript Lessons

Making Mods

Class Tree

Modeling Topics

Chongqing Page

Log In