diff --git a/ModelMultiParticlePersistFX.cs b/ModelMultiParticlePersistFX.cs
index a8d1697..44dc157 100644
--- a/ModelMultiParticlePersistFX.cs
+++ b/ModelMultiParticlePersistFX.cs
@@ -70,9 +70,12 @@ public class ModelMultiParticlePersistFX : EffectBehaviour
[Persistent]
public double logarithmicGrowth = 0.0;
- // Whether to nudge particles in order to alleviate the dotted smoke effect.
- // Set this to true (default) when using 'Simulate World Space' in Unity,
- // false otherwise.
+
+ ///
+ /// Whether to nudge particles in order to alleviate the dotted smoke effect.
+ /// Set this to true (default) when using 'Simulate World Space' in Unity,
+ /// false otherwise.
+ ///
[Persistent]
public bool fixedEmissions = true;
@@ -85,18 +88,15 @@ public class ModelMultiParticlePersistFX : EffectBehaviour
public float randomInitalVelocityOffsetMaxRadius = 0.0f;
#endregion Persistent fields
+ public MultiInputCurve emission = new MultiInputCurve("emission");
+ public MultiInputCurve energy = new MultiInputCurve("energy");
+ public MultiInputCurve speed = new MultiInputCurve("speed");
+ public MultiInputCurve grow = new MultiInputCurve("grow", true);
+ public MultiInputCurve scale = new MultiInputCurve("scale");
+ public MultiInputCurve size = new MultiInputCurve("size");
+ public MultiInputCurve offset = new MultiInputCurve("offset", true);
- public static readonly string[] CurveKeys = { "power", "density", "mach" };
-
- public MultiInputCurve emission = new MultiInputCurve("emission", CurveKeys);
- public MultiInputCurve energy = new MultiInputCurve("energy", CurveKeys);
- public MultiInputCurve speed = new MultiInputCurve("speed", CurveKeys);
- public MultiInputCurve grow = new MultiInputCurve("grow", CurveKeys, true);
- public MultiInputCurve scale = new MultiInputCurve("scale", CurveKeys);
- public MultiInputCurve size = new MultiInputCurve("size", CurveKeys);
- public MultiInputCurve offset = new MultiInputCurve("offset", CurveKeys, true);
-
- public MultiInputCurve logGrow = new MultiInputCurve("loggrow", CurveKeys, true);
+ public MultiInputCurve logGrow = new MultiInputCurve("loggrow", true);
// Those 2 curve are related to the angle and distance to cam
public FXCurve angle = new FXCurve("angle", 1f);
@@ -104,8 +104,7 @@ public class ModelMultiParticlePersistFX : EffectBehaviour
private List persistentEmitters;
- // The time between Update()s (in seconds).
- private float variableDeltaTime;
+ private float lastPower;
private float minEmissionBase;
private float maxEmissionBase;
@@ -136,8 +135,10 @@ public class ModelMultiParticlePersistFX : EffectBehaviour
private void OnDestroy()
{
if (persistentEmitters != null)
+ {
for (int i = 0; i < persistentEmitters.Count; i++)
persistentEmitters[i].Detach(0);
+ }
}
public override void OnEvent()
@@ -157,6 +158,8 @@ public override void OnEvent(float power)
if (persistentEmitters == null)
return;
+ lastPower = power;
+
if (power > 0f)
{
UpdateEmitters(power);
@@ -182,8 +185,8 @@ public override void OnEvent(float power)
private Dictionary colidersName;
- public static uint physicPass = 4;
- public static uint activePhysicPass = 0;
+ public static uint physicsPass = 4;
+ public static uint activePhysicsPass = 0;
public void FixedUpdate()
{
@@ -235,12 +238,14 @@ public void FixedUpdate()
for (int j = 0; j < particles.Length; j++)
{
// Check if we need to cull the number of particles
-
if (particuleDecimate != 0 && particles.Length > decimateFloor)
{
particleCounter++;
- if ((particuleDecimate > 0 && (particleCounter % particuleDecimate) == 0) || (particuleDecimate < 0 && (particleCounter % particuleDecimate) != 0))
+ if ((particuleDecimate > 0 && (particleCounter % particuleDecimate) == 0)
+ || (particuleDecimate < 0 && (particleCounter % particuleDecimate) != 0))
+ {
particles[j].energy = 0; // energy set to 0 remove the particle, as per Unity doc
+ }
}
if (particles[j].energy > 0)
@@ -248,18 +253,23 @@ public void FixedUpdate()
Vector3d pPos = persistentEmitters[i].pe.useWorldSpace ? particles[j].position : persistentEmitters[i].pe.transform.TransformPoint(particles[j].position);
Vector3d pVel = (persistentEmitters[i].pe.useWorldSpace ? particles[j].velocity : persistentEmitters[i].pe.transform.TransformDirection(particles[j].velocity))
+ Krakensbane.GetFrameVelocity();
+
// try-finally block to ensure we set the particle velocities correctly in the end.
try
{
// Fixed update is not the best place to update the size but the particles array copy is slow so doing each frame would be worse
particles[j].size = Mathf.Min(particles[j].size, sizeClamp);
+
+ var inputs = this.GetInputs(lastPower);
+
+ float lGrowth = logGrow.Value(inputs);
+
// No need to waste time doing a division if the result is 0.
- if (logarithmicGrowth != 0.0)
+ if (lGrowth != 0.0)
{
// Euler integration of the derivative of Log(logarithmicGrowth * t + 1) + 1.
- // TODO(robin): We use minSize rather than keeping the initial size.
// This might look weird.
- particles[j].size += (float)(((TimeWarp.fixedDeltaTime * logarithmicGrowth) / (1 + (particles[j].startEnergy - particles[j].energy) * logarithmicGrowth)) * persistentEmitters[i].pe.minSize);
+ particles[j].size += (float)(((TimeWarp.fixedDeltaTime * lGrowth) / (1 + (particles[j].startEnergy - particles[j].energy) * lGrowth)) * 0.5 * (persistentEmitters[i].pe.minSize + persistentEmitters[i].pe.maxSize));
}
if (particles[j].energy == particles[j].startEnergy)
@@ -267,7 +277,7 @@ public void FixedUpdate()
if (fixedEmissions)
{
// Uniformly scatter newly emitted particles along the emitter's trajectory in order to remove the dotted smoke effect.
- pPos -= (hostPart.rb.velocity + Krakensbane.GetFrameVelocity()) * UnityEngine.Random.value * variableDeltaTime;
+ pPos -= (hostPart.rb.velocity + Krakensbane.GetFrameVelocity()) * UnityEngine.Random.value * TimeWarp.fixedDeltaTime;
}
if (randomInitalVelocityOffsetMaxRadius != 0.0)
{
@@ -301,43 +311,42 @@ public void FixedUpdate()
}
}
- if (physical && (j % physicPass == activePhysicPass))
+ if (physical && (j % physicsPass == activePhysicsPass))
{
// TODO(robin): using pe.minSize is probably a bad idea, as above.
// There must be a way to keep the actual initial volume,
// but I'm lazy.
- pVel = ParticlePhysic(particles[j].size, persistentEmitters[i].pe.minSize, pPos, pVel);
+ pVel = ParticlePhysics(particles[j].size, 0.5 * (persistentEmitters[i].pe.minSize + persistentEmitters[i].pe.maxSize), pPos, pVel);
}
if (collide && particles[j].energy != particles[j].startEnergy // Do not collide newly created particles (they collide with the emitter and things look bad).
- && (j % physicPass == activePhysicPass)
- && pad != null && (pad.transform.position - pPos).sqrMagnitude < 25f * 25f) // Dont colide if the particule is far from the pad
+ && (j % physicsPass == activePhysicsPass))
+ //&& pad != null && (pad.transform.position - pPos).sqrMagnitude < 25f * 25f) // Dont colide if the particule is far from the pad
{
pVel = ParticleCollision(pPos, pVel, hit, mask);
}
}
finally
{
- particles[j].velocity = (persistentEmitters[i].pe.useWorldSpace ?
- (Vector3)(pVel - Krakensbane.GetFrameVelocity()) :
- persistentEmitters[i].pe.transform.InverseTransformDirection(pVel - Krakensbane.GetFrameVelocity()));
- particles[j].position = (persistentEmitters[i].pe.useWorldSpace ? (Vector3)pPos : persistentEmitters[i].pe.transform.InverseTransformPoint(pPos));
+ particles[j].velocity = (persistentEmitters[i].pe.useWorldSpace ? (Vector3)pVel : persistentEmitters[i].pe.transform.InverseTransformDirection(pVel))
+ - Krakensbane.GetFrameVelocity();
+ particles[j].position = persistentEmitters[i].pe.useWorldSpace ? (Vector3)pPos : persistentEmitters[i].pe.transform.InverseTransformPoint(pPos);
}
}
}
- activePhysicPass = ++activePhysicPass % physicPass;
+ activePhysicsPass = ++activePhysicsPass % physicsPass;
persistentEmitters[i].pe.pe.particles = particles;
activeParticles += persistentEmitters[i].pe.pe.particleCount;
}
}
- private Vector3 ParticlePhysic(double radius, double initialVolume, Vector3d pPos, Vector3d pVel)
+ private Vector3 ParticlePhysics(double radius, double initialRadius, Vector3d pPos, Vector3d pVel)
{
// N.B.: multiplications rather than Pow, Pow is slow,
// multiplication by .5 rather than division by 2 (same
// reason).
CelestialBody mainBody = FlightGlobals.currentMainBody;
- double estimatedInitialVolume = 0.75 * Math.PI * initialVolume * initialVolume * initialVolume;
+ double estimatedInitialVolume = 0.75 * Math.PI * initialRadius * initialRadius * initialRadius;
double currentVolume = 0.75 * Math.PI * radius * radius * radius;
double volumeChange = currentVolume - estimatedInitialVolume;
double atmosphericDensity = FlightGlobals.getAtmDensity(FlightGlobals.getStaticPressure(pPos, mainBody));
@@ -349,36 +358,33 @@ private Vector3 ParticlePhysic(double radius, double initialVolume, Vector3d pPo
Vector3d geeForce = mainBodyDist.normalized * (mainBody.gMagnitudeAtCenter / mainBodyDist.sqrMagnitude);
Vector3d acceleration = (1 - (atmosphericDensity / density)) * geeForce;
- //Vector3d acceleration = (1 - (atmosphericDensity / density)) * FlightGlobals.getGeeForceAtPosition(pPos);
// Drag. TODO(robin): simplify.
acceleration += -0.5 * atmosphericDensity * pVel * pVel.magnitude * dragCoefficient * Math.PI * radius * radius / mass;
+
// Euler is good enough for graphics.
- return pVel + acceleration * TimeWarp.fixedDeltaTime * (float)physicPass;
+ return pVel + acceleration * TimeWarp.fixedDeltaTime * (float)physicsPass;
}
private Vector3 ParticleCollision(Vector3d pPos, Vector3d pVel, RaycastHit hit, int mask)
{
- if (Physics.Raycast(pPos, pVel, out hit, (float)pVel.magnitude * TimeWarp.fixedDeltaTime * (float)physicPass, mask))
+ if (Physics.Raycast(pPos, pVel, out hit, (float)pVel.magnitude * TimeWarp.fixedDeltaTime * (float)physicsPass, mask))
{
- //colidersName[hit.collider.name] = true;
+ //// colidersName[hit.collider.name] = true;
- if (hit.collider.name != "Launch Pad Grate")
+ if (hit.collider.name != LaunchPadGrateColliderName)
{
- //Vector3 unitTangent = (hit.normal.x == 0 && hit.normal.y == 0) ? new Vector3(1, 0, 0) : Vector3.Exclude(hit.normal, new Vector3(0, 0, 1)).normalized;
-
- // Gerer le cas ou hVel est null et utiliser le calcul de unitTangent pour outflow dans ce cas.
+ Vector3 unitTangent = (hit.normal.x == 0 && hit.normal.y == 0) ? new Vector3(1, 0, 0) : Vector3.Exclude(hit.normal, new Vector3(0, 0, 1)).normalized;
Vector3 hVel = Vector3.Exclude(hit.normal, pVel);
Vector3 reflectedNormalVelocity = hVel - pVel;
float residualFlow = reflectedNormalVelocity.magnitude * (1 - collideRatio);
+
// An attempt at a better velocity change; the blob collides with some
// restitution coefficient collideRatio << 1 and we add a random tangential term
// for outflowing particles---randomness handwaved in through fluid dynamics:
- //float randomAngle = UnityEngine.Random.value * 360.0f;
- float randomAngle = 0;
- //Vector3d outflow = Quaternion.AngleAxis(randomAngle, hit.normal) * unitTangent * residualFlow;
- Vector3d outflow = Quaternion.AngleAxis(randomAngle, hit.normal) * hVel.normalized * residualFlow;
- pVel = hVel + collideRatio * reflectedNormalVelocity + outflow * (1 - stickiness);
+ float randomAngle = UnityEngine.Random.value * 360.0f;
+ Vector3d outflow = Quaternion.AngleAxis(randomAngle, hit.normal) * unitTangent * residualFlow;
+ pVel = hVel + collideRatio * reflectedNormalVelocity + outflow * (1 - stickiness);
}
else
{
@@ -390,9 +396,10 @@ private Vector3 ParticleCollision(Vector3d pPos, Vector3d pVel, RaycastHit hit,
return pVel;
}
- private const string LaunchPadColliderName = "LaunchPadColliderSmokeScreen";
// The whole pad object is named "ksp_pad_launchPad"
-
+ private const string LaunchPadGrateColliderName = "Launch Pad Grate";
+ private const string LaunchPadColliderName = "LaunchPadColliderSmokeScreen";
+
private void AddLaunchPadColliders(RaycastHit hit)
{
// the Grate Collider size is (37.70, 20.22, 3.47). Way larger that the actual grate
@@ -400,9 +407,9 @@ private void AddLaunchPadColliders(RaycastHit hit)
Transform parentTransform = hit.collider.gameObject.transform;
- //print("AddLaunchPadColliders col name = " + hit.collider.gameObject.name);
+ ////print("AddLaunchPadColliders col name = " + hit.collider.gameObject.name);
- //print("AddLaunchPadColliders parent col name = " + hit.collider.gameObject.transform.parent.gameObject.name);
+ ////print("AddLaunchPadColliders parent col name = " + hit.collider.gameObject.transform.parent.gameObject.name);
// Are the collider already here ?
if (parentTransform.FindChild(LaunchPadColliderName))
@@ -466,9 +473,9 @@ private void AddLaunchPadColliders(RaycastHit hit)
private static float lastTime = 0;
- private void ResetParticleCount()
+ private static void ResetParticleCount()
{
- if (lastTime != Time.fixedTime)
+ if (lastTime < Time.fixedTime)
{
if (activeParticles > maximumActiveParticles)
{
@@ -488,30 +495,55 @@ private void ResetParticleCount()
}
}
-
- public void UpdateEmitters(float power)
+ private Dictionary GetInputs(float power)
{
float atmDensity = 1;
float surfaceVelMach = 1;
- if (hostPart != null && hostPart.vessel != null)
+ float partTemp = 1;
+ float externalTemp = 1;
+ if (hostPart != null)
{
- Vessel vessel = hostPart.vessel;
- atmDensity = (float)vessel.atmDensity;
+ partTemp = hostPart.temperature;
+
+ if (hostPart.vessel != null)
+ {
+ Vessel vessel = hostPart.vessel;
+ atmDensity = (float)vessel.atmDensity;
- // does not work
- //float p = (float)FlightGlobals.getStaticPressure(vessel.altitude, vessel.mainBody);
- //float speedOfSound = Mathf.Sqrt(1.4f * p / atmDensity);
+ externalTemp = vessel.flightIntegrator.getExternalTemperature();
- // Cheating for now
- double speedOfSound = 343.2f;
+ // does not work
+ //float p = (float)FlightGlobals.getStaticPressure(vessel.altitude, vessel.mainBody);
+ //float speedOfSound = Mathf.Sqrt(1.4f * p / atmDensity);
- surfaceVelMach = (float)(vessel.srf_velocity.magnitude / speedOfSound);
+ // Cheating for now
+ const double speedOfSound = 343.2f;
+
+ //double speedOfSound = Math.Sqrt(externalTemp * currentBodyAtm.x)
+
+ surfaceVelMach = (float)(vessel.srf_velocity.magnitude / speedOfSound);
+ }
+ else
+ {
+ // TODO atmDensity & mach when not attached to a vessel
+ atmDensity = (float)FlightGlobals.getAtmDensity(FlightGlobals.getStaticPressure(hostPart.transform.position, FlightGlobals.currentMainBody));
+ }
}
- Dictionary inputs = new Dictionary(CurveKeys.Length, StringComparer.Ordinal);
- inputs["power"] = power;
- inputs["density"] = atmDensity;
- inputs["mach"] = surfaceVelMach;
+ var inputs = new Dictionary(5);
+ inputs[MultiInputCurve.Inputs.power] = power;
+ inputs[MultiInputCurve.Inputs.density] = atmDensity;
+ inputs[MultiInputCurve.Inputs.mach] = surfaceVelMach;
+ inputs[MultiInputCurve.Inputs.parttemp] = partTemp;
+ inputs[MultiInputCurve.Inputs.externaltemp] = externalTemp;
+
+ return inputs;
+ }
+
+
+ public void UpdateEmitters(float power)
+ {
+ var inputs = this.GetInputs(power);
for (int i = 0; i < persistentEmitters.Count; i++)
{
@@ -540,13 +572,12 @@ public void UpdateEmitters(float power)
persistentEmitters[i].go.transform.localPosition = Vector3d.forward * offset.Value(inputs);
- //print(atmDensity.ToString("F2") + " " + offset.Value(power).ToString("F2") + " " + offsetFromDensity.Value(atmDensity).ToString("F2") + " " + offsetFromMach.Value(surfaceVelMach).ToString("F2"));
+ ////print(atmDensity.ToString("F2") + " " + offset.Value(power).ToString("F2") + " " + offsetFromDensity.Value(atmDensity).ToString("F2") + " " + offsetFromMach.Value(surfaceVelMach).ToString("F2"));
}
}
public void Update()
{
- variableDeltaTime = Time.deltaTime;
if (persistentEmitters == null)
{
return;
@@ -570,7 +601,7 @@ public override void OnInitialize()
// If you need a surface shader, or vertex/pixel shaders, you'll need to create shader asset in the editor and use that."
// But importing the same shader that the one used in the editor seems to work
string filename = KSPUtil.ApplicationRootPath + "GameData/" + shaderFileName;
- if (shaderFileName != String.Empty && System.IO.File.Exists(filename))
+ if (shaderFileName != string.Empty && System.IO.File.Exists(filename))
{
try
{
@@ -607,7 +638,9 @@ public override void OnInitialize()
}
if (shader != null)
+ {
templateKspParticleEmitter.material.shader = shader;
+ }
// TODO : move those in PersistentKSPParticleEmitter
@@ -688,12 +721,9 @@ public override void OnSave(ConfigNode node)
speed.Save(node);
grow.Save(node);
scale.Save(node);
-
size.Save(node);
-
offset.Save(node);
-
angle.Save(node);
distance.Save(node);
}
@@ -725,7 +755,7 @@ void windowGUI(int ID)
collide = GUILayout.Toggle(collide, "collide");
physical = GUILayout.Toggle(physical, "physical");
- GUILayout.Label("activePhysicPass " + activePhysicPass);
+ GUILayout.Label("activePhysicPass " + activePhysicsPass);
GUILayout.Space(10);
@@ -740,6 +770,17 @@ void windowGUI(int ID)
GUILayout.Space(10);
+ double externalTemp = FlightGlobals.ActiveVessel.flightIntegrator.getExternalTemperature();
+ GUILayout.Label("External Temperature : " + externalTemp.ToString("F2"));
+
+
+ // FAR use a nice config file to get the atmo info for each body.
+ // For now I'll just use Air for all.
+ const double magicNumeberFromFAR = 1.4 * 8.3145 * 1000 / 28.96;
+ double speedOfSound = Math.Sqrt((externalTemp + 273.15) * magicNumeberFromFAR);
+
+ GUILayout.Label("Speed of Sound : " + speedOfSound.ToString("F2"));
+
GameObject pad = GameObject.Find("ksp_pad_launchPad");
if (pad != null)
diff --git a/MultiInputCurve.cs b/MultiInputCurve.cs
index 146656d..d0d168f 100644
--- a/MultiInputCurve.cs
+++ b/MultiInputCurve.cs
@@ -3,19 +3,24 @@
using System.Linq;
using System.Text;
-
+using UnityEngine;
public class MultiInputCurve
{
private string name;
- private Dictionary curves;
+ private Dictionary curves;
+ private Dictionary logCurves;
+
+ private bool hasLog = false;
bool additive;
- public enum Input
+ public enum Inputs
{
- power = 0,
+ power,
density,
- mach
+ mach,
+ parttemp,
+ externaltemp
}
public MultiInputCurve(string name, bool additive = false)
@@ -23,11 +28,12 @@ public MultiInputCurve(string name, bool additive = false)
this.name = name;
this.additive = additive;
- int count = Enum.GetValues(typeof(Input)).Length;
+ int count = Enum.GetValues(typeof(Inputs)).Length;
- curves = new Dictionary(count);
+ curves = new Dictionary(count);
+ logCurves = new Dictionary(count);
- foreach (Input key in Enum.GetValues(typeof(Input)))
+ foreach (Inputs key in Enum.GetValues(typeof(Inputs)))
{
curves[key] = new FXCurve(key.ToString(), additive ? 0f : 1f);
}
@@ -37,39 +43,57 @@ public void Load(ConfigNode node)
{
// For backward compat load the power curve as the one with the same name
// it will get overwritten if a power is defined in the subnode
- curves[Input.power].Load(name, node);
+ curves[Inputs.power].Load(name, node);
if (node.HasNode(name))
{
- foreach (Input key in Enum.GetValues(typeof(Input)))
+ foreach (Inputs key in Enum.GetValues(typeof(Inputs)))
{
curves[key].Load(key.ToString(), node.GetNode(name));
+
+ string logKey = "log" + key;
+ if (node.GetNode(name).HasValue(logKey))
+ {
+ hasLog = true;
+ logCurves[key] = new FXCurve(logKey, additive ? 0f : 1f);
+ logCurves[key].Load(logKey, node.GetNode(name));
+ }
}
}
}
- public float Value(Dictionary inputs)
+ public float Value(Dictionary inputs)
{
float result = additive ? 0f : 1f;
- foreach (Input key in Enum.GetValues(typeof(Input)))
- if (additive)
- result += curves[key].Value(inputs[key]);
- else
- result *= curves[key].Value(inputs[key]);
+ foreach (Inputs key in Enum.GetValues(typeof(Inputs)))
+ {
+ float input = inputs[key];
+
+ result = additive ? result + curves[key].Value(input) : result * curves[key].Value(input);
+ FXCurve logCurve;
+ if (hasLog && logCurves.TryGetValue(key, out logCurve))
+ {
+ result = additive ? result + logCurve.Value(Mathf.Log(input)) : result * logCurve.Value(Mathf.Log(input));
+ }
+ }
return result;
}
public void Save(ConfigNode node)
{
- if (node.HasNode(name))
- foreach (Input key in Enum.GetValues(typeof(Input)))
- {
- ConfigNode subNode = new ConfigNode(name);
- curves[key].Save(subNode);
- node.AddNode(subNode);
- }
+ foreach (FXCurve curve in curves.Values)
+ {
+ ConfigNode subNode = new ConfigNode(name);
+ curve.Save(subNode);
+ node.AddNode(subNode);
+ }
+ foreach (FXCurve curve in logCurves.Values)
+ {
+ ConfigNode subNode = new ConfigNode(name);
+ curve.Save(subNode);
+ node.AddNode(subNode);
+ }
}
-
}
diff --git a/PersistentEmitterManager.cs b/PersistentEmitterManager.cs
index cd26ffe..720a8ed 100644
--- a/PersistentEmitterManager.cs
+++ b/PersistentEmitterManager.cs
@@ -53,13 +53,12 @@ private void OnSceneChange(GameScenes scene)
persistentEmitters = new List();
}
-
void FixedUpdate()
{
- List persistentEmittersCopy = new List(persistentEmitters);
+ var persistentEmittersCopy = new List(persistentEmitters);
for (int i = 0; i < persistentEmittersCopy.Count; i++)
{
- if (persistentEmittersCopy[i].timer != 0 && persistentEmittersCopy[i].timer < Time.fixedTime)
+ if (persistentEmittersCopy[i].timer > 0 && persistentEmittersCopy[i].timer < Time.fixedTime)
persistentEmittersCopy[i].EmissionStop();
if (persistentEmittersCopy[i].go.transform.parent == null && persistentEmittersCopy[i].pe.pe.particles.Count() == 0)
@@ -71,7 +70,7 @@ void FixedUpdate()
}
}
- private void print(String s)
+ private void Print(string s)
{
MonoBehaviour.print(this.GetType().Name + " : " + s);
}