Prototyping: RPG Style Damage Numbers
by Dan Dias · 12/29/2008 (11:20 am) · 8 comments
Download Code File
1/7/2008 - Added a deleteDamage() and schedule the emitters to get deleted 2.5 seconds after they are created.
1/16/2008 - Added links to screenshot and video.
For a quick look, you should be able to drop the attached file into a clean starter.rts kit.
Please keep in mind, this is NOT the best way to implement this feature, but is the quickest. This was ripped out of my original code and applied to the starter.fps.
1. Setting up the datablocks
Create a file in the server/scripts directory called damageText.cs. You will need to add two datablocks for each number.
The last thing you need to do with this file is to set the NodeData properties.
I found these were the best settings for my game. Play around with them a bit! The ParticleEmitterData block should remain the same unless you know what you're doing. This make the particle fire only once. With my limited knowledge of the particle system, it was a pain to get that to work. Ok, got 0-9 blocks set up? Good!
2. Parsing
Make a server/scripts/damage.cs file.
To get the numbers to letters, I had to write a small function to parse the damage numbers. A lot of languages I know don't allow numbers as the first character. I suppose I could have swapped them and done Text1 and Emitter1 and save a little processing time. I might still do that later and change it here.
3. Emitting
Now we want to be able to actually use these particles. So let's make a showDamage function after the numToText function.
4. Tying it together
In server/scripts/game.cs
In the onServerCreated function add
These are examples from my implementation of it.
Screenshot
Video
1/7/2008 - Added a deleteDamage() and schedule the emitters to get deleted 2.5 seconds after they are created.
1/16/2008 - Added links to screenshot and video.
For a quick look, you should be able to drop the attached file into a clean starter.rts kit.
Please keep in mind, this is NOT the best way to implement this feature, but is the quickest. This was ripped out of my original code and applied to the starter.fps.
1. Setting up the datablocks
Create a file in the server/scripts directory called damageText.cs. You will need to add two datablocks for each number.
datablock ParticleData(zeroText)
{
textureName = "~/data/particles/battleText/zero";
// Move with the wind
dragCoefficient = 2.0;
// Float up
gravityCoefficient = -0.1;
// Last awhile
lifetimeMS = 2000;
// Change from fully opaque to semi transparent over lifetime
colors[0] = "0.50 0.0 0.0 1.0";
colors[1] = "0.50 0.0 0.0 0.5";
// Shrink to under half the size over lifetime
sizes[0] = 1.0;
sizes[1] = 0.4;
};
datablock ParticleEmitterData(zeroEmitter)
{
thetaMin = 180;
thetaMax = 180;
particles = zeroText;
lifetimeMS = 100;
ejectionPeriodMS = 500;
periodVarianceMS = 0;
};The next blocks would be ParticleData(oneText) and ParticleEmitterData(oneEmitter) and so on. This is a bit of a pain to set up. I would like to have a single image that has all the numbers, but I think that is something for later.The last thing you need to do with this file is to set the NodeData properties.
datablock ParticleEmitterNodeData(TextEmitterNodeData)
{
timeMultiple = 1;
isLooping = false;
};If you are not familiar with particles... join the club. I still haven't found a great resource/tutorial. Admittedly, I have not had the time or motivation yet to look that hard. I found these were the best settings for my game. Play around with them a bit! The ParticleEmitterData block should remain the same unless you know what you're doing. This make the particle fire only once. With my limited knowledge of the particle system, it was a pain to get that to work. Ok, got 0-9 blocks set up? Good!
2. Parsing
Make a server/scripts/damage.cs file.
To get the numbers to letters, I had to write a small function to parse the damage numbers. A lot of languages I know don't allow numbers as the first character. I suppose I could have swapped them and done Text1 and Emitter1 and save a little processing time. I might still do that later and change it here.
function numToText(%damage)
{
// Loop through each character
for(%i=0;%i <= strlen(%damage);%i++)
{
switch$(getSubStr(%damage,%i,1))
{
case "0":
%damages = setWord(%damages,%i,"zero");
case "1":
%damages = setWord(%damages,%i,"one");
case "2":
%damages = setWord(%damages,%i,"two");
case "3":
%damages = setWord(%damages,%i,"three");
case "4":
%damages = setWord(%damages,%i,"four");
case "5":
%damages = setWord(%damages,%i,"five");
case "6":
%damages = setWord(%damages,%i,"six");
case "7":
%damages = setWord(%damages,%i,"seven");
case "8":
%damages = setWord(%damages,%i,"eight");
case "9":
%damages = setWord(%damages,%i,"nine");
}
}
return %damages;
}Just in case this isn't clear to everyone; if you pass in 60, it comes out "six zero." Simple enough right? Great, let's move on!3. Emitting
Now we want to be able to actually use these particles. So let's make a showDamage function after the numToText function.
function showDamage(%object,%damage)
{
// Give us the text for the datablocks
%damages = numToText(%damage);
// Loop through each word and display them
for(%i = 0;%i<getWordCount(%damages);%i++)
{
%pDam = new ParticleEmitterNode()
{
// Places it over the object and spaces the particles out (there has to be a better way)
position = setWord(setWord(%object.getTransform(),2,getWord(%object.getTransform(),2)+2),0,getWord(%object.getTransform(),0)+(%i*0.5));
rotation = "1 0 0 180";
scale = "1 1 1";
dataBlock = TextEmitterNodeData;
//choose the emitter
emitter = getWord(%damages,%i)@"Emitter";
velocity = 0.5;
};
//Throw off numbers
%object.emitter[%i] = %pDam;
// schedule the emitter to die in 2.5 seconds (just after the lifetime of it) This is definitely not the best way to do it... but it works.
schedule(2500,"deleteDamage",%obj.emitter[%i]);
}
}
function deleteDamage(%obj)
{
%obj.delete();
}4. Tying it together
In server/scripts/game.cs
In the onServerCreated function add
exec("./damage.cs");
exec("./damageText.cs");right after...
exec("./crossbow.cs");
exec("./environment.cs");I added the call to showDamage in the Armor::damage function. However, there are a few places you can add it. So something like: function Armor::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)
{
if (%obj.getState() $= "Dead")
return;
%obj.applyDamage(%damage);
showDamage(%obj,%damage);And that's it. Chase Kork down and ram an arrow into him and wait for the smoke to clear. The numbers will not follow him as he moves but for my purposes (a turn based RPG) this wasn't necessary.These are examples from my implementation of it.
Screenshot
Video
#2
This is a great little memory leak at the moment... =P
12/30/2008 (6:11 pm)
Decent hack for prototyping, I'll give you that, but... yeah, I would not want this in release code! Creating new ParticleEmitterNode objects every time you want to display some damage? And you're never deleting them... (O.o) This is a great little memory leak at the moment... =P
#3
Wow, you are right. As I mentioned this was ripped from the original implementation in my game to fit in with the default starter.fps. In my original game, I have a function that cleans them up after the player's turn is over. I totally forgot to implement that into it.
As I recall, making a new particle emitter was the only way I could get it to retrigger. If I'm completely missing something like a %object.emitter.start()... or something, let me know. I would love to give the object it's own emitters and just fire them again with a different emitter datablock, if possible.
01/06/2009 (11:03 pm)
@KevinWow, you are right. As I mentioned this was ripped from the original implementation in my game to fit in with the default starter.fps. In my original game, I have a function that cleans them up after the player's turn is over. I totally forgot to implement that into it.
As I recall, making a new particle emitter was the only way I could get it to retrigger. If I'm completely missing something like a %object.emitter.start()... or something, let me know. I would love to give the object it's own emitters and just fire them again with a different emitter datablock, if possible.
#4
But try out ParticleEmitterNode.setEmitterDataBlock(datablock), ParticleData.reload(), and ParticleEmitterData.reload(). I haven't tried it, but you should be able to set a new datablock and then use the reload() method to restart the emitter. ("Should" being the keyword there!)
Hope that helps.
01/07/2009 (3:20 pm)
Yeah, at the very least, delete your emitter node object each time...But try out ParticleEmitterNode.setEmitterDataBlock(datablock), ParticleData.reload(), and ParticleEmitterData.reload(). I haven't tried it, but you should be able to set a new datablock and then use the reload() method to restart the emitter. ("Should" being the keyword there!)
Hope that helps.
#5
If I were you, I'd not delete the nodes, but reuse them instead and delete them only when the parent is removed. Object creation tends to bog down resources for a bit of time. When there'd be many of these numbers flying in the middle of some battle, you'd probably feel the impact creating and deleting nodes would have on the framerate, but I might be wrong at that.
Very good job still, keep it up!
01/11/2009 (5:04 am)
Dan, I've been playing with your resource, and I've found a few things. One is that the wind affects the particles, which is really funny. Another, that disturbs me more is that the correct reading of the number is only one way.. so if I score 30 and look from the other way around, it will display 03, or 0 and 3 over each other from the sides. Nevertheless, I really like the effect, it just needs a little more polish!If I were you, I'd not delete the nodes, but reuse them instead and delete them only when the parent is removed. Object creation tends to bog down resources for a bit of time. When there'd be many of these numbers flying in the middle of some battle, you'd probably feel the impact creating and deleting nodes would have on the framerate, but I might be wrong at that.
Very good job still, keep it up!
#6
I understand your concern about the numbers. It was a design choice. With my implementation of this, the camera is fixed in one position much like a Final Fantasy style battle. Perhaps I should post video of this... So the one sided thing suits my purposes. Once I get this to a point where it's worth it to spend a lot of time on it I'll be creating one particle with the already combined texture. I haven't figured out a scripting solution, so it probably is going to have to be done engine side. With this combined texture, you could just make it rotate as it travels upward to display it from all angles. I will most likely post that as a resource when I do it.
I think you are right about object creation bogging down the system. When I get time, I'll have to look into doing it properly. Right now, I have two AIPlayers that create and destroy the particle emitters every 5 seconds on average. So I wasn't too worried about the framerate drops but I'd still like to reuse them rather than create and delete constantly.
Thanks for the constructive criticism guys! It's allowing me to learn from my first resource so hopefully my future ones will be a bit more comprehensive!
01/11/2009 (11:10 am)
Regarding the wind affecting particles, this is intended behavior. I wanted a sweeping motion to my particles like they are falling away. This can be removed by simply getting rid of:// Move with the wind dragCoefficient = 2.0;in the particle data.
I understand your concern about the numbers. It was a design choice. With my implementation of this, the camera is fixed in one position much like a Final Fantasy style battle. Perhaps I should post video of this... So the one sided thing suits my purposes. Once I get this to a point where it's worth it to spend a lot of time on it I'll be creating one particle with the already combined texture. I haven't figured out a scripting solution, so it probably is going to have to be done engine side. With this combined texture, you could just make it rotate as it travels upward to display it from all angles. I will most likely post that as a resource when I do it.
I think you are right about object creation bogging down the system. When I get time, I'll have to look into doing it properly. Right now, I have two AIPlayers that create and destroy the particle emitters every 5 seconds on average. So I wasn't too worried about the framerate drops but I'd still like to reuse them rather than create and delete constantly.
Thanks for the constructive criticism guys! It's allowing me to learn from my first resource so hopefully my future ones will be a bit more comprehensive!
#7
01/15/2009 (12:09 pm)
The code seems pretty promising, do you happen to have any screenshots?
#8
01/16/2009 (8:24 am)
Updated! Added links to a screenshot and video hosted on my site. Thanks for the suggestion, Assassini. These are from the implementation in my project; not the starter.fps. This was the best way to display the actual effect. With the FPS implementation, I found the particles weren't as prominent because they originate from the same area as the bolt explosion particles. 
Associate Konrad Kiss
Bitgap Games