Particle emitter

Discussions about Coding and Scripting
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Particle emitter

Post by >@tack!< »

for the SDK is not yet complete i made a actor-based emitter, it works but now i want to recycle the particles so that the Spawn functions wont be used anymore at a certain point. This works offline but online the SetLocation function wont apply to them, they just reappear again where they disappeared and drift further, this is probs replication shit and im not a pro in that yet so maybe you know it?

I give you the code:

Code: Select all

//=============================================================================
// DeeltjesUitzender.
//=============================================================================
class DeeltjesUitzender expands Triggers;

var() bool bRepeat;
var() Texture ParticleTexture;
var() float ParticleSpeed,Radius,ParticleDuration,ParticleSize,ParticleSpawnRate;
var() int NumParticles;
var() enum EAreaType
{
	AT_Square,
	AT_Sphere,
	AT_Circle,
}
AreaType;

var bool bRecycling;
var vector X,Y,Z;


Function PostBeginPlay()
{
	super.postbeginplay();
	GetAxes(Rotation,X,Y,Z);
	SetTimer(ParticleSpawnRate,bRepeat);
}

simulated function SetRecycle(Deeltjuh D)
{
local int i;
local float t,t2;
local vector RandLoc;

		bRecycling = true;

		if(AreaType == AT_Circle)
		{
			t = FRand()*2*pi;
			t2 = Sqrt(FRand());
			RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
		}
		else if(AreaType == AT_Square)
		RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

		else if (AreaType == AT_Sphere)
		D.Velocity = VRand() * ParticleSpeed;

		D.SetLocation(Location+RandLoc);
		D.SetRotation(Rotation);
}

Simulated Function Timer()
{

local int i;
local vector RandLoc;
local Deeltjuh B;
local mover M;
local float t,t2;

	if(bRecycling)
	return;
	For(i=0;i<NumParticles;i++)
	{
		if(AreaType == AT_Circle)
			{
			t = FRand()*2*pi;
			t2 = Sqrt(FRand());
			RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
			}
		if(AreaType == AT_Square)
		RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

	B = Spawn(class'Deeltjuh',self,,Location + RandLoc,Rotation);

	if(AttachTag != '')
	ForEach AllActors(class'Mover',M,AttachTag)
	B.SetBase(M);

	if (AreaType == AT_Sphere)
	B.Velocity = VRand() * ParticleSpeed;

	else
	B.Velocity = Vector(Rotation) * ParticleSpeed;

	B.DrawScale = ParticleSize;
	B.ParticleDuration = ParticleDuration;
	B.Texture = ParticleTexture;
	}
}

Code: Select all

//=============================================================================
// Deeltjuh.
//=============================================================================
class Deeltjuh expands Actor;

var float ParticleDuration,Count;

replication
{
	reliable if(Role==Role_Authority)
		ParticleDuration,Count;
}

simulated function Tick( float DeltaTime )
{
	Count += DeltaTime;
	ScaleGlow = (ParticleDuration-Count)/ParticleDuration;

	if(Count >= ParticleDuration)
	{
		ScaleGlow = 0;
		Count = 0;
		DeeltjesUitzender(Owner).SetRecycle(self);
	}
}

i guess the whole SetRecycle code doesnt get called or something? Some advice plz :)
User avatar
Feralidragon
Godlike
Posts: 5493
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Particle emitter

Post by Feralidragon »

Show the default properties please, they're vital for the full understandment of any replication problem.

Btw, particles are generally effects only, something you can only see in clients, in the server they're useless most of the times.
If you consider in making them work client-side only, you won't need replication blocks for the particles and the only replication worries you will have is to ensure you're working client-side, as once you are client-side only, everything will work the same as offline play and you won't have that sort of problems. The only thing you have to worry is to pass the info about if something was triggered on the server then.

Plus, doing it that way will have the advantage of not using almost any bandwidth at all and relief the server from extra processing, giving that processing to the client instead.

But well, first things first, I'm aware you may not know how this is done, so bring those default properties for a better check :wink:
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Particle emitter

Post by JackGriffin »

This all sounds very familiar ;) He just helped me work through a similar problem.

Ferali, the undisputed king of all things relevance. You ought to get paid per-post :tu:
So long, and thanks for all the fish
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Re: Particle emitter

Post by >@tack!< »

Feralidragon wrote:Show the default properties please, they're vital for the full understandment of any replication problem.

Btw, particles are generally effects only, something you can only see in clients, in the server they're useless most of the times.
If you consider in making them work client-side only, you won't need replication blocks for the particles and the only replication worries you will have is to ensure you're working client-side, as once you are client-side only, everything will work the same as offline play and you won't have that sort of problems. The only thing you have to worry is to pass the info about if something was triggered on the server then.

Plus, doing it that way will have the advantage of not using almost any bandwidth at all and relief the server from extra processing, giving that processing to the client instead.

But well, first things first, I'm aware you may not know how this is done, so bring those default properties for a better check :wink:
yup i dont know how to that and tbh i sometimes just change stuff of which i dont even know what they exactly do.

So here are the scripts with defaultproperties:

Code: Select all

//=============================================================================
// DeeltjesUitzender.
//=============================================================================
class DeeltjesUitzender expands Triggers;

var() bool bRepeat;
var() Texture ParticleTexture;
var() float ParticleSpeed,Radius,ParticleDuration,ParticleSize,ParticleSpawnRate;
var() int NumParticles;
var() enum EAreaType
{
	AT_Square,
	AT_Sphere,
	AT_Circle,
}
AreaType;

var bool bRecycling;
var vector X,Y,Z;


Function PostBeginPlay()
{
	super.postbeginplay();
	GetAxes(Rotation,X,Y,Z);
	SetTimer(ParticleSpawnRate,bRepeat);
}

function SetRecycle(Deeltjuh D)
{
local int i;
local float t,t2;
local vector RandLoc;

		bRecycling = true;

		if(AreaType == AT_Circle)
		{
			t = FRand()*2*pi;
			t2 = Sqrt(FRand());
			RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
		}
		else if(AreaType == AT_Square)
		RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

		else if (AreaType == AT_Sphere)
		D.Velocity = VRand() * ParticleSpeed;

		D.SetLocation(Location+RandLoc);
		D.SetRotation(Rotation);
}

Simulated Function Timer()
{

local int i;
local vector RandLoc;
local Deeltjuh B;
local mover M;
local float t,t2;

	if(bRecycling)
	return;
	For(i=0;i<NumParticles;i++)
	{
		if(AreaType == AT_Circle)
			{
			t = FRand()*2*pi;
			t2 = Sqrt(FRand());
			RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
			}
		if(AreaType == AT_Square)
		RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

	B = Spawn(class'Deeltjuh',self,,Location + RandLoc,Rotation);

	if(AttachTag != '')
	ForEach AllActors(class'Mover',M,AttachTag)
	B.SetBase(M);

	if (AreaType == AT_Sphere)
	B.Velocity = VRand() * ParticleSpeed;

	else
	B.Velocity = Vector(Rotation) * ParticleSpeed;

	B.DrawScale = ParticleSize;
	B.ParticleDuration = ParticleDuration;
	B.Texture = ParticleTexture;
	}
}

defaultproperties
{
     bRepeat=True
     ParticleTexture=Texture'GenFX.LensFlar.lens1'
     ParticleSpeed=128.000000
     Radius=64.000000
     ParticleDuration=1.000000
     ParticleSize=0.100000
     ParticleSpawnRate=0.200000
     Numparticles=2
     bDirectional=True
}

Code: Select all

//=============================================================================
// Deeltjuh.
//=============================================================================
class Deeltjuh expands Actor;

var float ParticleDuration,Count;

replication
{
	reliable if(Role==Role_Authority)
		ParticleDuration,Count;
}

simulated function Tick( float DeltaTime )
{
	Count += DeltaTime;
	ScaleGlow = (ParticleDuration-Count)/ParticleDuration;

	if(Count >= ParticleDuration)
	{
		ScaleGlow = 0;
		Count = 0;
		DeeltjesUitzender(Owner).SetRecycle(self);
	}
}

defaultproperties
{
     Physics=PHYS_Projectile
     RemoteRole=ROLE_SimulatedProxy
     Style=STY_Translucent
     bUnlit=True
}
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Particle emitter

Post by JackGriffin »

>@tack!< wrote: yup i dont know how to that and tbh i sometimes just change stuff of which i dont even know what they exactly do.
We all do. It's the best way to learn.
So long, and thanks for all the fish
User avatar
Shadow
Masterful
Posts: 743
Joined: Tue Jan 29, 2008 12:00 am
Personal rank: Mad Carpenter
Location: Germany
Contact:

Re: Particle emitter

Post by Shadow »

Cool thread.

Your idea of a recycling method is very laudable, since spawning actors is "very sophisticated". As feralidragon said, try to keep your particle mechanics client-side only. If you need interaction between clients or triggering I recommend to create special network emitters and triggers (subclasses of your existing emitter types).
Image
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Re: Particle emitter

Post by >@tack!< »

Yeah im still waiting for feralis advice I dno how to do stuff client side
User avatar
Shadow
Masterful
Posts: 743
Joined: Tue Jan 29, 2008 12:00 am
Personal rank: Mad Carpenter
Location: Germany
Contact:

Re: Particle emitter

Post by Shadow »

Well first off, you can determine when an actor is either running on client or server. Simply use the Checks available by the Netmode of the Levelinfo.

Here we have:
  • - NM_Standalone = level is running in offline single/multiplayer
    - NM_DedicatedServer = level is running on online dedicated server
    - NM_ListenServer = level is running on an online non-dedicated server (the host)
    - NM_Client = level is running on a client in the network
Next thing is to clear up which role/remoterole an actors takes on netplay. Here we have:
  • - ROLE_None - nothing of the actor is replicated in any way (used for actors like gameinfo, explosions, decals etc.)
    - ROLE_DumbProxy - only the most important stuff is replicated, most mechanics rely on server-side
    - ROLE_SimulatedProxy - the actor is replicated, it may execute simulated functions on it's own (mostly used for projectiles)
    - ROLE_AutonomousProxy - actor is nearly fully replicated, here the client has control over the actor, the actor doesn't rely on simulated functionality (mostly used for pawns and players)
    - ROLE_Authority - all functionality is maintained by the client
nargh unfortunately I don't have more time right now to clear things more up.. a good link is: http://unreal.jall.org/tutorials/replication.html
Image
User avatar
Feralidragon
Godlike
Posts: 5493
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Particle emitter

Post by Feralidragon »

Sorry for the time taken (was working in a weapon for my mod, also dealing with replication and stuff), but here you have them: scripts 100% working both offline and online, and relative online play the particles are only spawned in the client like they should.

They're ready to apply and compile, but I commented everything I did and explained briefly "why" so you can understand replication a bit better (and also a few more concepts):

Code: Select all

//=============================================================================
// DeeltjesUitzender.
//=============================================================================
class DeeltjesUitzender expands Triggers;

//When using external utx packages for default properties, load it up during compile
#exec OBJ LOAD FILE=GenFX.utx

var() bool bRepeat;
var() Texture ParticleTexture;
var() float ParticleSpeed,Radius,ParticleDuration,ParticleSize,ParticleSpawnRate;
var() int NumParticles;
var() enum EAreaType
{
   AT_Square,
   AT_Sphere,
   AT_Circle,
}
AreaType;

var bool bRecycling;

//Since we're going spawn particles client-side, the new variables have to be replicated (even
// when edited in the UEd in maps, the values have to be replicated, otherwise they won't replicate)
// reliable means to ensure the data arrives the client, and arrives ok
// Role == ROLE_Authority means to replicate variables from server to client
// bNetInitial means to replicate only once when the actor got spawned (sort of)
replication
{
	reliable if (Role == ROLE_Authority && bNetInitial)
		ParticleDuration, ParticleSize, ParticleSpawnRate, Radius, ParticleSpeed, 
		bRepeat, ParticleTexture, NumParticles, AreaType;
}

//All the functions from here onwards have to be "simulated" as they run client-side

simulated function PostBeginPlay()
{
	Super.postbeginplay();
	
	//New call to initialize the trigger for proper replication
	initTriggerParticles();
	
	//Client-side only check
	//Moved GetAxis to Timer and Recycle, ever imagined having a rotating particle emitter? ;)
        //On another hand, even that initTriggerParticles was called now, the Rotation variable won't be updated imediatelly
	if (Level.NetMode != NM_DedicatedServer)
		SetTimer(ParticleSpawnRate,bRepeat);
}

//New function to hide this actor in another way so it keeps relevant but hidden at the same time
// bHidden=True won't do since with it this actor would become irrelevant to clients, so particles
// would never spawn this way
// This actor has to turn into a valid mesh as well so the Rotation can be replicated to the client (Actor replication rules)
simulated function initTriggerParticles()
{
	Mesh = LodMesh'UnrealShare.CandleM';
	DrawType = DT_Mesh;
	DrawScale = 0.001;
	Style = STY_Translucent;
	ScaleGlow = 0.0;
}

simulated function SetRecycle(Deeltjuh D)
{
local int i;
local float t,t2;
local vector RandLoc;
local vector X,Y,Z;

	  GetAxes(Rotation,X,Y,Z);
	  bRecycling = true;
	  if(AreaType == AT_Circle)
	  {
		 t = FRand()*2*pi;
		 t2 = Sqrt(FRand());
		 RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
	  }
	  else if(AreaType == AT_Square)
	  RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

	  else if (AreaType == AT_Sphere)
	  D.Velocity = VRand() * ParticleSpeed;

	  D.SetLocation(Location+RandLoc);
	  D.SetRotation(Rotation);
}

simulated function Timer()
{

local int i;
local vector RandLoc;
local Deeltjuh B;
local mover M;
local float t,t2;
local vector X,Y,Z;

   //If it's a server, return as well (took advantage of the bRecycling check to avoid extra code)
   if(bRecycling || Level.NetMode == NM_DedicatedServer)
   return;
   
   GetAxes(Rotation,X,Y,Z);
   For(i=0;i<NumParticles;i++)
   {
	  if(AreaType == AT_Circle)
		 {
		 t = FRand()*2*pi;
		 t2 = Sqrt(FRand());
		 RandLoc = radius*t2*cos(t)*Z + radius*t2*sin(t)*Y;
		 }
	  if(AreaType == AT_Square)
	  RandLoc = (Radius - 2*FRand()*Radius) * Z + (Radius - 2*FRand()*Radius) * Y;

   B = Spawn(class'Deeltjuh',self,,Location + RandLoc,Rotation);

   if(AttachTag != '')
   ForEach AllActors(class'Mover',M,AttachTag)
   B.SetBase(M);

   if (AreaType == AT_Sphere)
   B.Velocity = VRand() * ParticleSpeed;

   else
   B.Velocity = Vector(Rotation) * ParticleSpeed;

   B.DrawScale = ParticleSize;
   B.ParticleDuration = ParticleDuration;
   B.Texture = ParticleTexture;
   }
}

defaultproperties
{
	bRepeat=True
	ParticleTexture=Texture'GenFX.LensFlar.lens1'
	ParticleSpeed=128.000000
	Radius=64.000000
	ParticleDuration=1.000000
	ParticleSize=0.100000
	ParticleSpawnRate=0.200000
	Numparticles=2
	bDirectional=True
	
	//Key default properties: 
	//	SimulatedProxy so simulated functions can be called on the client
	//	bHidden has to be false so the trigger is relevant to the client
	RemoteRole=ROLE_SimulatedProxy
	bHidden=False
}

Code: Select all

//=============================================================================
// Deeltjuh.
//=============================================================================
class Deeltjuh expands Actor;

var float ParticleDuration, Count;

//Removed that replication block, as it's useless from now on since these will only be spawned in the client only

//Simulated
simulated function Tick( float DeltaTime )
{
   Count += DeltaTime;
   ScaleGlow = (ParticleDuration-Count)/ParticleDuration;

   if(Count >= ParticleDuration)
   {
	  ScaleGlow = 0;
	  Count = 0;
	  DeeltjesUitzender(Owner).SetRecycle(self);
   }
}

defaultproperties
{
	Physics=PHYS_Projectile
	RemoteRole=ROLE_None //Changed to None since they're client only, no replication of any kind needed
	Style=STY_Translucent
	bUnlit=True
}
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Re: Particle emitter

Post by >@tack!< »

Wow FeraliDragon thanks, this is really perfect, im glad people like you exist in this world :)
TBH it was my intention to use rotating particleemitters :p, i didnt pay attention that i put the GetAxes function in the postbeginplay() function :p

Again THANK YOU!
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Re: Particle emitter

Post by >@tack!< »

OI! there is one thing, the particles slow down in water, i guess its cuz they have Phys=PHYS_Projectile? is there any way to counter this without changing its Phys?
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Particle emitter

Post by JackGriffin »

It's probably happening in the ZoneInfo:

Code: Select all

// When an actor enters this zone.
event ActorEntered( actor Other )
{
	local actor A;
	local vector AddVelocity;

	if ( bNoInventory && Other.IsA('Inventory') && (Other.Owner == None) )
	{
		Other.LifeSpan = 1.5;
		return;
	}

	if( Pawn(Other)!=None && Pawn(Other).bIsPlayer )
		if( ++ZonePlayerCount==1 && ZonePlayerEvent!='' )
			foreach AllActors( class 'Actor', A, ZonePlayerEvent )
				A.Trigger( Self, Pawn(Other) );

	if ( bMoveProjectiles && (ZoneVelocity != vect(0,0,0)) )
	{
		if ( Other.Physics == PHYS_Projectile )
			Other.Velocity += ZoneVelocity;
		else if ( Other.IsA('Effects') && (Other.Physics == PHYS_None) )
		{
			Other.SetPhysics(PHYS_Projectile);
			Other.Velocity += ZoneVelocity;
		}
	}
}
So long, and thanks for all the fish
User avatar
Feralidragon
Godlike
Posts: 5493
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Particle emitter

Post by Feralidragon »

Nope, that's not from the zone entering/exiting events from ZoneInfo at UScript level. The only change in speed any actor gets in ZoneInfo is when ZoneInfo has a ZoneVelocity assigned, like those tunnels in CTF-HallOfGiants which transport you and your projectiles.

However, Zones and speeds have native stuff going on (for instance, the Projectile class doesn't have the same speed all the time, it actually has an acceleration, which will make the projectile go gradually from the first velocity assigned (generally "speed") to the end velocity (MaxSpeed, not used anywhere but in native code afaik in the projectile class), and in ZoneInfo you have some native properties like: ZoneGroundFriction and ZoneFluidFriction.

The first one (ZoneGroundFriction) is only related to "walking" stuff, like pawns (mostly), and is used to create icy surfaces in some maps (this makes me think if there's a "slippery surface" mutator already like there's a gravity one lol).
The second (ZoneFluidFriction) will affect anything that enters the zone, since it's not a surface setting, but the volumetric zone or space itself. This one should be the culprit of the decrease in speed in your particles.

However, I am not entirelly sure if it's indeed ZoneFluidFriction or bWaterZone the settings responsible for this: the logical one would be ZoneFluidFriction, but considering that Epic made many flaws and "hammered" some bits in code itself, it wouldn't surprise me if they defined this with bWaterZone as well.

Either ways, you can force a constant speed on your particles, without repercussions for online play since they're now working client-side only.
Therefore, what I advice is you to do the following:
- In the particles class create a new variable called mySpeed (or something like that);
- In your Tick function, add something like:

Code: Select all

if (Region.Zone.bWaterZone)
      Velocity = Normal(Velocity) * mySpeed;
- In your generator class, after you spawned the particle, just add:

Code: Select all

B.mySpeed = ParticleSpeed;
After this, the zone will constantly trying to decrease the projectile speed, but by enforcing it in each Tick, the particle should not loose most of its speed.
JackGriffin
Godlike
Posts: 3774
Joined: Fri Jan 14, 2011 1:53 pm
Personal rank: -Retired-

Re: Particle emitter

Post by JackGriffin »

Interesting Ferali, I'll have to bear that in mind to compensate for zones in the future. I wonder if it does the same with a pressure zone, etc. Actually I think the effect is pretty nice overall, and makes water seem more 'water'.
So long, and thanks for all the fish
User avatar
>@tack!<
Adept
Posts: 338
Joined: Sat Apr 17, 2010 4:51 pm
Personal rank: lol?

Re: Particle emitter

Post by >@tack!< »

what a useful topic isnt it xD
Thanks Ferali for your detailed explanation, it worked :)
Post Reply