@BHoltzman
I think almost everyone here does programming with AppGameKit as a hobby. And I know the job and the family have priority.
I thank all those who support this project.
Ok, here the last part of this Tutorial.
>How to use the emitters from the particle editor<.
Part 3 - Using the Particle Effect File
In a C# / AgkSharp project
Using the effect file in C# with AgkSharp is a very easy task. When the AgkSharp templates are installed.
After creating a new AgkSharp project, all existing media can be deleted. Copy the particle image(s) and the effect file(s) from the media folder of the ParticleEditor, in the media folder of the project.
The CEmiter class (CEmitter.cs) can be copied from the particle editor into your project. The namespace entry in CEmitter.cs must be adjusted according to the project.
namespace _2DParticleEditor
{
Becomes for example …
namespace ParticleUsage
{
Since we want to load a JSON file here, the Newtonsoft-Json reference must be added via NuGet again. How this works has already been described in part 2.
In the file
Program.cs you can delete the unnecessary lines, in
Core.CreateWin32Window you can also change the title and add these entries at the beginning of the file.
using System.Collections.Generic;
using Newtonsoft.Json;
using System.IO;
Before the AppGameKit loop ( while (Core.LoopAGK()) ), you can insert these lines. A list object is required for all emitters in the effect. The effect file is loaded into the json string. Then the string is deserialized to the list object MyEffect.
In the for loop, all emitters are started to play the effect.
List<CEmitter> MyEffect;
string json = File.ReadAllText(".\\media\\TestEffect.pfx");
MyEffect = JsonConvert.DeserializeObject<List<CEmitter>>(json);
for(int i=0; i<MyEffect.Count; i++)
{
MyEffect[i].Create((uint)i+1, 160, 240, Agk.GetReadPath() + "media/");
}
That's it.
Program.cs now looks like this.
using System;
using AgkSharp;
using AGKCore;
using System.Collections.Generic; // we need aditional classes eg List
using Newtonsoft.Json;
using System.IO;
namespace ParticleUsage
{
static class Program
{
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
static void Main()
{
Core.CreateWin32Window("AGK-Title", 640, 480, false);
if (!Core.InitAGK())
return;
Agk.SetVirtualResolution(320, 480);
List<CEmitter> MyEffect;
string json = File.ReadAllText(".\\media\\TestEffect.pfx");
MyEffect = JsonConvert.DeserializeObject<List<CEmitter>>(json);
for(int i=0; i<MyEffect.Count; i++)
{
MyEffect[i].Create((uint)i+1, 160, 240, Agk.GetReadPath() + "media/");
}
while (Core.LoopAGK())
{
Agk.Sync();
}
Core.CleanUp();
}
}
}
For a more elegant solution and better reusability, a new class should be written that loads and plays the effect. A new class will be created via "Project->Add Class ...". I choose CEffect as the class name.
The three using references described above are now added to this file.
using Newtonsoft.Json;
using System.IO;
using AgkSharp;
The class needs a list object - to manage the emitters, a variable pointing to a directory where the images of the particles are located, and the start id in which ID space the effect is created.
List<CEmitter> Effect; // store all emitters for the effect
string ParticleFolder { get; set; } // folder for all particle images
uint StartId { get; set; } // start id for the particles and images
Note that this is only a simple implementation (particle id is equal to image ID).
The image path and the StartID are set via the constructor.
// constructor set folder and start id
public CEffect(string folder, uint start)
{
// force start id above zero
if (start < 1)
{
start = 1;
// set start id and image folder for particles
ParticleFolder = folder;
StartId = start;
}
A method to load the effect is required.
// load the effect-file path is independent from ParticleFolder.
public bool Load(string path)
{
// check if file exists
if (!File.Exists(path))
return false;
// read and deserialize the effect file
string json = File.ReadAllText(path);
Effect = JsonConvert.DeserializeObject<List<CEmitter>>(json);
return true;
}
Just as playing and stopping an effect.
// play the effect. creates all emitters.
public void Play()
{
// start all emitters
for (int i = 0; i < Effect.Count; i++)
{
Effect[i].Create(StartId + (uint)i, 160, 240, ParticleFolder);
}
}
The code could be something like this.
CEffect.cs
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using System.IO;
using AgkSharp;
namespace ParticleUsage
{
public class CEffect
{
List<CEmitter> Effect; // store all emitters for the effect
string ParticleFolder { get; set; } // folder for all particle images
uint StartId { get; set; } // start id for the particles and images
// constructor set folder and start id
public CEffect(string folder, uint start)
{
// force start id above zero
if (start < 1)
start = 1;
// set start id and image folder for particles
ParticleFolder = folder;
StartId = start;
}
// load the effect-file path is independent from ParticleFolder.
public bool Load(string path)
{
// check if file exists
if (!File.Exists(path))
return false;
// read and deserialize the effect file
string json = File.ReadAllText(path);
Effect = JsonConvert.DeserializeObject<List<CEmitter>>(json);
return true;
}
// play the effect. creates all emitters.
public void Play()
{
// start all emitters
for (int i = 0; i < Effect.Count; i++)
{
Effect[i].Create(StartId + (uint)i, 160, 240, ParticleFolder);
}
}
// stop the effect.
public void Stop()
{
// delete all emitters.
for (int i = 0; i < Effect.Count; i++)
{
Agk.DeleteParticles(StartId+(uint)i);
}
}
}
}
And the main program changes to this.
Program.cs
using System;
using AgkSharp;
using AGKCore;
namespace ParticleUsage
{
static class Program
{
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
static void Main()
{
Core.CreateWin32Window("AGK-Title", 640, 480, false);
if (!Core.InitAGK())
return;
Agk.SetVirtualResolution(320, 480);
// create a new effect object with the given path and start id
CEffect MyEffect = new CEffect(Agk.GetReadPath() + "media/", 1000);
// load the effect
MyEffect.Load(".\\media\\TestEffect.pfx");
// play the effect.
MyEffect.Play();
while (Core.LoopAGK())
{
Agk.Sync();
}
Core.CleanUp();
}
}
}
In a AGK-Tier 1 / AGK-Basic project
Using the particle effect file in an AGK-Basic project is a bit more complex. Here, all structures and classes must be transformed into UDTs. Note that int, uint and bool become integer. This is what these structures look like.
// for storing a list of colorkeys
Type SColorKey
time As Float
color As Integer
EndType
// for storing a list of scalekeys
Type SScaleKey
time As Float
scale As Float
EndType
// for storing a list of forcekeys
Type SForceKey
start_time As Float
end_time As Float
force_x As Float
force_y As Float
EndType
Only the data components are taken from the classes. As you can see here.
Type CEmitter
// creation
Image As String
DirectionVX As Float
DirectionVY As Float
Angle As Float
Size As Float
Transparency As Integer
Interpolation As Integer
// count
Frequency As Integer
Life As Float
Max As Integer
// zone
X1 As Float
Y1 As Float
X2 As Float
Y2 As Float
// rotation
RotationMin As Float
RotationMax As Float
FaceDirection As Integer
// velocity
VelocityMin As Float
VelocityMax As Float
// frame keys
ColorKeys As SColorKey[]
ScaleKeys As SScaleKey[]
ForceKeys As SForceKey[]
// position data of the emitter. only for the editor
PositionX As Float
PositionY As Float
EndType
Type CEffect
Effect As CEmitter[] // store all emitters for the effect
ParticleFolder As String // folder for all particle images
StartId As Integer // start id for the particles and images
EndType
The methods of the classes must now be rewritten to AppGameKit functions. It starts with the method CEmitter.Create together with the methods CEmitter.SetColorKeys , CEmitter.SetScaleKeys and CEmitter.SetForceKeys.
Function CreateEmitter(emitter Ref As CEmitter, id as Integer, posx As Float, posy As Float, path As String)
// check whether an image is set. if so, load the image into
// the same id as the particle id.
if Len(emitter.Image) > 0) Then LoadImage(id, "raw:" + imagepath + Image)
// create an emitter
CreateParticles(id, posx, posy)
// set the image for the particles.
if GetImageExists(id) = 1 Then SetParticlesImage(id, id)
// set all stored data from CEmitter to agk-emitter
SetParticlesSize(id, emitter.Size)
SetParticlesAngle(id, emitter.Angle)
SetParticlesColorInterpolation(id, emitter.Interpolation)
SetParticlesDirection(id, emitter.DirectionVX, emitter.DirectionVY)
SetParticlesFaceDirection(id, emitter.FaceDirection)
SetParticlesFrequency(id, emitter.Frequency)
SetParticlesLife(id, emitter.Life)
SetParticlesMax(id, emitter.Max)
SetParticlesRotationRange(id, emitter.RotationMin, emitter.RotationMax)
SetParticlesStartZone(id, emitter.X1, emitter.Y1, emitter.X2, emitter.Y2)
SetParticlesTransparency(id, emitter.Convert.ToInt32(Transparency))
SetParticlesVelocityRange(id, emitter.VelocityMin, emitter.VelocityMax)
// set frame keys.
SetEmitterColorKeys(id)
SetEmitterScaleKeys(id)
SetEmitterForceKeys(id)
EndFunction
// set stored color keys.
Function SetEmitterColorKeys(emitter Ref As CEmitter, id As Integer)
ClearParticlesColors(id)
If emitter.ColorKeys.Length > -1
i As Integer
For i=0 To emitter.ColorKeys.Length
AddParticlesColorKeyFrame(id, emitter.ColorKeys[i].time, (emitter.ColorKeys[i].color && 0x00ff0000) >> 16, (emitter.ColorKeys[i].color && 0x0000ff00) >> 8, (emitter.ColorKeys[i].color && 0x000000ff), (emitter.ColorKeys[i].color && 0xff000000) >> 24)
Next
EndIf
EndFunction
// set strored scale keys.
Function SetEmitterScaleKeys(emitter Ref As CEmitter, id As Integer)
ClearParticlesScales(id);
If emitter.ScaleKeys.Count > -1)
i As Integer
For i=0 To emitter.ScaleKeys.Length
AddParticlesScaleKeyFrame(id, emitter.ScaleKeys[i].time, emitter.ScaleKeys[i].scale)
Next
EndIf
EndFunction
// set stored force keys
Function SetEmitterForceKeys(emitter Ref As CEmitter, id As Integer)
ClearParticlesForces(id);
If emitter.ForceKeys.Length > -1)
i As Integer
For i=0 To emitter.ForceKeys.Length
AddParticlesForce(id, emitter.ForceKeys[i].start_time, emitter.ForceKeys[i].end_time, emitter.ForceKeys[i].force_x, emitter.ForceKeys[i].force_y)
Next
EndIf
EndFunction
The effect class must also be rewritten.
// constructor set folder and start id
Function EffectCreate(folder As String, startid As Integer)
If startid < 1 then startid = 1
effect as CEffect
effect.StartId = startid
effect.ParticleFolder = folder
EndFunction effect
// load the effect-file path is independent from ParticleFolder.
Function EffectLoad(fx Ref As CEffect, path as String)
If GetFileExists(path) = 0 Then ExitFunction 0
file As Integer
json as String
file = OpenToRead(path)
json = ReadString(file)
CloseFile(file)
fx.Effect.FromJSON(json)
EndFunction 1
// play the effect. creates all emitters.
Function EffectPlay(fx Ref As CEffect)
i As Integer
For i=0 To fx.Effect.Length
CreateEmitter(fx.Effect[i], fx.StartId + i, 160, 240, fx.ParticleFolder)
Next i
EndFunction
// stop the effect. delete all emitters.
Function EffectStop(fx Ref As CEffect)
i As Integer
For i=0 To fx.Effect.Length
DeleteParticles(StartId + i)
Next i
EndFunction
Share your knowledge. It\'s a way to achieve immortality.
(Tenzin Gyatso)