Status icons, skill/character refactoring

This commit is contained in:
Regalis
2015-09-25 12:42:42 +03:00
parent bf7619bcc4
commit 3587b4a4bb
20 changed files with 483 additions and 188 deletions
+89 -96
View File
@@ -124,6 +124,16 @@ namespace Subsurface
get { return cursorPosition; }
}
public Character ClosestCharacter
{
get { return closestCharacter; }
}
public Character SelectedCharacter
{
get { return selectedCharacter; }
}
//public AITarget AiTarget
//{
// get { return aiTarget; }
@@ -152,7 +162,7 @@ namespace Subsurface
{
get { return oxygen; }
set
{
{
oxygen = MathHelper.Clamp(value, 0.0f, 100.0f);
if (oxygen == 0.0f) Kill();
}
@@ -163,27 +173,25 @@ namespace Subsurface
get
{
return health;
//float totalHealth = 0.0f;
//foreach (Limb l in animController.limbs)
//{
// totalHealth += (l.MaxHealth - l.Damage);
//}
//return totalHealth/animController.limbs.Count();
}
set
{
health = MathHelper.Clamp(value, 0.0f, maxHealth);
if (health==0.0f) Kill();
if (health == 0.0f) Kill();
}
}
public float MaxHealth
{
get { return maxHealth; }
}
public float Bleeding
{
get { return bleeding; }
set
{
if (float.IsNaN(value) || float.IsInfinity(value)) return;
if (MathUtils.IsValid(value)) return;
bleeding = Math.Max(value, 0.0f);
}
}
@@ -287,6 +295,8 @@ namespace Subsurface
public Character(string file, Vector2 position, CharacterInfo characterInfo = null, bool isNetworkPlayer = false)
{
keys = new Key[Enum.GetNames(typeof(InputType)).Length];
keys[(int)InputType.Select] = new Key(false);
keys[(int)InputType.ActionHeld] = new Key(true);
@@ -513,22 +523,55 @@ namespace Subsurface
if (controlled == this)
{
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition));
closestItem = FindClosestItem(mouseSimPos);
if (closestItem != null)
closestCharacter = FindClosestCharacter(mouseSimPos);
if (closestCharacter != null)
{
closestItem.IsHighlighted = true;
if (GetInputState(InputType.Select) && closestItem.Pick(this, forcePick))
if (closestCharacter != selectedCharacter) selectedCharacter = null;
if (!closestCharacter.isHumanoid) closestCharacter = null;
}
closestItem = FindClosestItem(mouseSimPos);
if (closestCharacter != null && closestItem != null)
{
if (Vector2.Distance(closestCharacter.SimPosition, mouseSimPos) < Vector2.Distance(closestItem.SimPosition, mouseSimPos))
{
new NetworkEvent(NetworkEventType.PickItem, ID, true, closestItem.ID);
if (selectedConstruction!=closestItem) closestItem = null;
}
else
{
closestCharacter = null;
}
}
closestCharacter = FindClosestCharacter(mouseSimPos);
if (closestCharacter != selectedCharacter) selectedCharacter = null;
if (closestCharacter!=null)
if (selectedCharacter==null)
{
if (closestItem != null)
{
closestItem.IsHighlighted = true;
if (GetInputState(InputType.Select) && closestItem.Pick(this, forcePick))
{
new NetworkEvent(NetworkEventType.PickItem, ID, true, closestItem.ID);
}
}
}
else
{
if (GetInputState(InputType.Select)) selectedCharacter = (selectedCharacter == null) ? closestCharacter : null;
if (Vector2.Distance(selectedCharacter.SimPosition, SimPosition) > 2.0f) selectedCharacter = null;
}
if (GetInputState(InputType.Select))
{
if (selectedCharacter!=null)
{
selectedCharacter = null;
}
else if (closestCharacter!=null && closestCharacter.isDead && closestCharacter.isHumanoid)
{
selectedCharacter = closestCharacter;
}
}
}
@@ -686,7 +729,11 @@ namespace Subsurface
return;
}
if (controlled == this) ControlLocalPlayer(cam);
if (controlled == this)
{
CharacterHUD.Update(deltaTime,this);
ControlLocalPlayer(cam);
}
Control(deltaTime, cam);
@@ -711,15 +758,11 @@ namespace Subsurface
PressureProtection -= deltaTime*100.0f;
}
//foreach (Limb limb in animController.limbs)
//{
Health = health - bleeding * deltaTime;
//}
Health = health - bleeding * deltaTime;
}
private void UpdateSightRange()
{
aiTarget.SightRange = 0.0f;
@@ -747,6 +790,11 @@ namespace Subsurface
// ConvertUnits.ToDisplayUnits(animController.targetMovement.X, animController.targetMovement.Y), Color.Green);
}
public void DrawHUD(SpriteBatch spriteBatch, Camera cam)
{
CharacterHUD.Draw(spriteBatch, this, cam);
}
public void DrawFront(SpriteBatch spriteBatch)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(AnimController.Limbs[0].SimPosition);
@@ -772,67 +820,7 @@ namespace Subsurface
}
private static GUIProgressBar drowningBar, healthBar;
public void DrawHud(SpriteBatch spriteBatch, Camera cam)
{
if (drowningBar == null)
{
int width = 100, height = 20;
drowningBar = new GUIProgressBar(new Rectangle(20, GameMain.GraphicsHeight / 2, width, height), Color.Blue, 1.0f);
healthBar = new GUIProgressBar(new Rectangle(20, GameMain.GraphicsHeight / 2 + 30, width, height), Color.Red, 1.0f);
}
drowningBar.BarSize = Controlled.Oxygen / 100.0f;
if (drowningBar.BarSize < 0.95f) drowningBar.Draw(spriteBatch);
healthBar.BarSize = health / maxHealth;
if (healthBar.BarSize < 1.0f) healthBar.Draw(spriteBatch);
if (Controlled.Inventory != null) Controlled.Inventory.DrawOwn(spriteBatch);
Color color = Color.Orange;
if (closestCharacter != null && closestCharacter.isDead && closestCharacter.isHumanoid)
{
Vector2 startPos = Position + (closestCharacter.Position - Position) * 0.7f;
startPos = cam.WorldToScreen(startPos);
Vector2 textPos = startPos;
float stringWidth = GUI.Font.MeasureString(closestCharacter.Info.Name).X;
textPos -= new Vector2(stringWidth / 2, 20);
spriteBatch.DrawString(GUI.Font, closestCharacter.Info.Name, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, closestCharacter.Info.Name, textPos + new Vector2(1, -1), Color.Orange);
if (selectedCharacter==closestCharacter) closestCharacter.inventory.Draw(spriteBatch);
}
else if (closestItem != null && selectedConstruction==null)
{
Vector2 startPos = Position + (closestItem.Position - Position) * 0.7f;
startPos = cam.WorldToScreen(startPos);
Vector2 textPos = startPos;
float stringWidth = GUI.Font.MeasureString(closestItem.Prefab.Name).X;
textPos -= new Vector2(stringWidth / 2, 20);
spriteBatch.DrawString(GUI.Font, closestItem.Prefab.Name, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, closestItem.Prefab.Name, textPos + new Vector2(1, -1), Color.Orange);
textPos.Y += 50.0f;
foreach (ColoredText coloredText in closestItem.GetHUDTexts(Controlled))
{
textPos.X = startPos.X - GUI.Font.MeasureString(coloredText.Text).X / 2;
spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos + new Vector2(1, -1), coloredText.Color);
textPos.Y += 25;
}
}
}
public void PlaySound(AIController.AiState state)
{
@@ -856,20 +844,23 @@ namespace Subsurface
}
}
public virtual AttackResult AddDamage(IDamageable attacker, Vector2 position, Attack attack, bool playSound = false)
public virtual AttackResult AddDamage(IDamageable attacker, Vector2 simPosition, Attack attack, bool playSound = false)
{
return AddDamage(position, attack.DamageType, attack.Damage, attack.BleedingDamage, attack.Stun, playSound);
return AddDamage(simPosition, attack.DamageType, attack.Damage, attack.BleedingDamage, attack.Stun, playSound);
}
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound)
public AttackResult AddDamage(Vector2 simPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound)
{
AnimController.StunTimer = Math.Max(AnimController.StunTimer, stun);
if (controlled == this) CharacterHUD.TakeDamage();
Limb closestLimb = null;
float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs)
{
float distance = Vector2.Distance(position, limb.SimPosition);
float distance = Vector2.Distance(simPosition, limb.SimPosition);
if (closestLimb == null || distance < closestDistance)
{
closestLimb = limb;
@@ -877,12 +868,12 @@ namespace Subsurface
}
}
Vector2 pull = position - closestLimb.SimPosition;
Vector2 pull = simPosition - closestLimb.SimPosition;
if (pull != Vector2.Zero) pull = Vector2.Normalize(pull);
closestLimb.body.ApplyForce(pull*Math.Min(amount*100.0f, 100.0f));
AttackResult attackResult = closestLimb.AddDamage(position, damageType, amount, bleedingAmount, playSound);
AttackResult attackResult = closestLimb.AddDamage(simPosition, damageType, amount, bleedingAmount, playSound);
health -= attackResult.Damage;
bleeding += attackResult.Bleeding;
@@ -938,6 +929,8 @@ namespace Subsurface
private IEnumerable<object> DeathAnim(Camera cam)
{
if (controlled != this) yield return CoroutineStatus.Success;
float dimDuration = 8.0f;
float timer = 0.0f;
@@ -967,9 +960,9 @@ namespace Subsurface
}
float lerpLightBack = 0.0f;
while (lerpLightBack<1.0f)
while (lerpLightBack < 1.0f)
{
lerpLightBack = Math.Min(lerpLightBack+0.05f,1.0f);
lerpLightBack = Math.Min(lerpLightBack + 0.05f, 1.0f);
GameMain.LightManager.AmbientLight = Color.Lerp(Color.DarkGray, prevAmbientLight, lerpLightBack);
yield return CoroutineStatus.Running;
@@ -0,0 +1,142 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Subsurface
{
class CharacterHUD
{
private static Sprite statusIcons;
private static GUIProgressBar drowningBar, healthBar;
private static float pressureTimer;
public static void TakeDamage()
{
healthBar.Flash();
}
public static void Update(float deltaTime, Character character)
{
if (drowningBar != null)
{
drowningBar.Update(deltaTime);
if (character.Oxygen < 10.0f) drowningBar.Flash();
}
if (healthBar != null) healthBar.Update(deltaTime);
pressureTimer += ((character.AnimController.CurrentHull == null) ?
100.0f : character.AnimController.CurrentHull.LethalPressure)*deltaTime;
}
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
{
if (statusIcons==null)
{
statusIcons = new Sprite("Content/UI/statusIcons.png", Vector2.Zero);
}
DrawStatusIcons(spriteBatch, character);
if (character.Inventory != null) character.Inventory.DrawOwn(spriteBatch);
Color color = Color.Orange;
if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory!=null)
{
character.SelectedCharacter.Inventory.Draw(spriteBatch);
//if (Vector2.Distance(selectedCharacter.SimPosition, SimPosition) > 2.0f) selectedCharacter = null;
}
if (character.ClosestCharacter != null && character.ClosestCharacter.IsDead)
{
Vector2 startPos = character.Position + (character.ClosestCharacter.Position - character.Position) * 0.7f;
startPos = cam.WorldToScreen(startPos);
Vector2 textPos = startPos;
float stringWidth = GUI.Font.MeasureString(character.ClosestCharacter.Info.Name).X;
textPos -= new Vector2(stringWidth / 2, 20);
spriteBatch.DrawString(GUI.Font, character.ClosestCharacter.Info.Name, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, character.ClosestCharacter.Info.Name, textPos + new Vector2(1, -1), Color.Orange);
}
else if (character.SelectedCharacter == null && character.ClosestItem != null && character.SelectedConstruction == null)
{
Vector2 startPos = character.Position + (character.ClosestItem.Position - character.Position) * 0.7f;
startPos = cam.WorldToScreen(startPos);
Vector2 textPos = startPos;
float stringWidth = GUI.Font.MeasureString(character.ClosestItem.Prefab.Name).X;
textPos -= new Vector2(stringWidth / 2, 20);
spriteBatch.DrawString(GUI.Font, character.ClosestItem.Prefab.Name, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, character.ClosestItem.Prefab.Name, textPos + new Vector2(1, -1), Color.Orange);
textPos.Y += 50.0f;
foreach (ColoredText coloredText in character.ClosestItem.GetHUDTexts(character))
{
textPos.X = startPos.X - GUI.Font.MeasureString(coloredText.Text).X / 2;
spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos, Color.Black);
spriteBatch.DrawString(GUI.Font, coloredText.Text, textPos + new Vector2(1, -1), coloredText.Color);
textPos.Y += 25;
}
}
}
private static void DrawStatusIcons(SpriteBatch spriteBatch, Character character)
{
if (drowningBar == null)
{
int width = 100, height = 20;
drowningBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 200, width, height), Color.Blue, 1.0f);
new GUIImage(new Rectangle(-27, -7, 20, 20), new Rectangle(17, 0, 20, 24), statusIcons, Alignment.TopLeft, drowningBar);
healthBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 230, width, height), Color.Red, 1.0f);
new GUIImage(new Rectangle(-26, -7, 20, 20), new Rectangle(0, 0, 13, 24), statusIcons, Alignment.TopLeft, healthBar);
}
drowningBar.BarSize = character.Oxygen / 100.0f;
if (drowningBar.BarSize < 0.99f)
{
drowningBar.Draw(spriteBatch);
}
healthBar.BarSize = character.Health / character.MaxHealth;
if (healthBar.BarSize < 1.0f)
{
healthBar.Draw(spriteBatch);
}
int bloodDropCount = (int)Math.Floor(character.Bleeding);
bloodDropCount = MathHelper.Clamp(bloodDropCount, 0, 5);
for (int i = 1; i < bloodDropCount; i++)
{
spriteBatch.Draw(statusIcons.Texture, new Vector2(5.0f + 20 * i, healthBar.Rect.Y - 20.0f), new Rectangle(39, 3, 15, 19), Color.White * 0.8f);
}
float pressureFactor = (character.AnimController.CurrentHull == null) ?
100.0f : Math.Min(character.AnimController.CurrentHull.LethalPressure,100.0f);
if (character.PressureProtection > 0.0f) pressureFactor = 0.0f;
if (pressureFactor>0.0f)
{
float indicatorAlpha = ((float)Math.Sin(pressureTimer * 0.1f) + 1.0f) * 0.5f;
indicatorAlpha = MathHelper.Clamp(indicatorAlpha, 0.1f, pressureFactor/100.0f);
spriteBatch.Draw(statusIcons.Texture, new Vector2(10.0f, healthBar.Rect.Y - 60.0f), new Rectangle(0, 24, 24, 25), Color.White * indicatorAlpha);
}
}
}
}
+2 -26
View File
@@ -7,28 +7,6 @@ using System.Xml.Linq;
namespace Subsurface
{
class Skill
{
string name;
int level;
public string Name
{
get { return name; }
}
public int Level
{
get { return level; }
}
public Skill(string name, int level)
{
this.name = name;
this.level = level;
}
}
class Job
{
@@ -76,11 +54,9 @@ namespace Subsurface
prefab = jobPrefab;
skills = new Dictionary<string, Skill>();
foreach (KeyValuePair<string, Vector2> skill in prefab.Skills)
foreach (SkillPrefab skillPrefab in prefab.Skills)
{
skills.Add(
skill.Key,
new Skill( skill.Key, (int)Rand.Range(skill.Value.X, skill.Value.Y, false)));
skills.Add(skillPrefab.Name, new Skill(skillPrefab));
}
}
+47 -18
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using System.Linq;
namespace Subsurface
{
@@ -30,7 +31,7 @@ namespace Subsurface
public List<string> ItemNames;
public List<bool> EquipItem;
public Dictionary<string, Vector2> Skills;
public List<SkillPrefab> Skills;
public string Name
{
@@ -54,7 +55,7 @@ namespace Subsurface
public JobPrefab(XElement element)
{
name = element.Name.ToString();
name = ToolBox.GetAttributeString(element, "name", "name not found");
description = ToolBox.GetAttributeString(element, "description", "");
@@ -66,7 +67,7 @@ namespace Subsurface
ItemNames = new List<string>();
EquipItem = new List<bool>();
Skills = new Dictionary<string, Vector2>();
Skills = new List<SkillPrefab>();
foreach (XElement subElement in element.Elements())
{
@@ -82,10 +83,15 @@ namespace Subsurface
}
break;
case "skills":
LoadSkills(subElement);
foreach (XElement skillElement in subElement.Elements())
{
Skills.Add(new SkillPrefab(skillElement));
}
break;
}
}
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
}
public static JobPrefab Random()
@@ -93,28 +99,51 @@ namespace Subsurface
return List[Rand.Int(List.Count)];
}
private void LoadSkills(XElement element)
public GUIFrame CreateInfoFrame()
{
foreach (XElement subElement in element.Elements())
int width = 500, height = 400;
GUIFrame frame = new GUIFrame(new Rectangle(GameMain.GraphicsWidth / 2 - width / 2, GameMain.GraphicsHeight / 2 - height / 2, width, height), GUI.Style);
frame.Padding = new Vector4(30.0f, 30.0f, 30.0f, 30.0f);
new GUITextBlock(new Rectangle(0,0,100,20), name, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
var descriptionBlock = new GUITextBlock(new Rectangle(0, 40, 0, 0), description, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, true, GUI.SmallFont);
new GUITextBlock(new Rectangle(0, 40 + descriptionBlock.Rect.Height + 20, 100, 20), "Skills: ", GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
int y = 40 + descriptionBlock.Rect.Height + 50;
foreach (SkillPrefab skill in Skills)
{
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
string skillDescription = Skill.GetLevelName((int)skill.LevelRange.X);
string skillDescription2 = Skill.GetLevelName((int)skill.LevelRange.Y);
if (string.IsNullOrEmpty(skillName) || Skills.ContainsKey(skillName)) continue;
var levelString = ToolBox.GetAttributeString(subElement, "level", "");
if (levelString.Contains(","))
if (skillDescription2!= skillDescription)
{
Skills.Add(skillName, ToolBox.ParseToVector2(levelString, false));
}
else
{
float skillLevel = float.Parse(levelString, CultureInfo.InvariantCulture);
Skills.Add(skillName, new Vector2(skillLevel, skillLevel));
skillDescription += "/"+skillDescription2;
}
new GUITextBlock(new Rectangle(0, y, 100, 20),
" - " + skill.Name + ": " + skillDescription, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont);
y += 20;
}
}
new GUITextBlock(new Rectangle(250, 40 + descriptionBlock.Rect.Height + 20, 0, 20), "Items: ", GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
y = 40 + descriptionBlock.Rect.Height + 50;
foreach (string itemName in ItemNames)
{
new GUITextBlock(new Rectangle(250, y, 100, 20),
" - " + itemName, GUI.Style, Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont);
y += 20;
}
return frame;
}
public static void LoadAll(List<string> filePaths)
{
@@ -0,0 +1,57 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Subsurface
{
class Skill
{
SkillPrefab prefab;
string name;
int level;
static string[] levelNames = new string[] {
"Untrained", "Incompetent", "Novice",
"Adequate", "Competent", "Proficient",
"Professional", "Master", "Legendary" };
public string Name
{
get { return name; }
}
public int Level
{
get { return level; }
}
public Skill(SkillPrefab prefab)
{
this.prefab = prefab;
this.name = prefab.Name;
this.level = (int)Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y);
}
public Skill(string name, int level)
{
this.name = name;
this.level = level;
}
/// <summary>
/// returns the "name" of some skill level (0-10 -> untrained, etc)
/// </summary>
public static string GetLevelName(int level)
{
level = MathHelper.Clamp(level, 0, 100);
int scaledLevel = (int)Math.Floor((level / 100.0f) * levelNames.Length);
return levelNames[Math.Min(scaledLevel, levelNames.Length - 1)];
}
}
}
@@ -0,0 +1,51 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Subsurface
{
class SkillPrefab
{
string name;
string description;
private Vector2 levelRange;
public string Name
{
get { return name; }
}
public string Description
{
get { return description; }
}
public Vector2 LevelRange
{
get { return levelRange; }
}
public SkillPrefab(XElement element)
{
name = ToolBox.GetAttributeString(element, "name", "");
var levelString = ToolBox.GetAttributeString(element, "level", "");
if (levelString.Contains(","))
{
levelRange = ToolBox.ParseToVector2(levelString, false);
}
else
{
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
levelRange = new Vector2(skillLevel, skillLevel);
}
}
}
}
+5 -5
View File
@@ -285,7 +285,7 @@ namespace Subsurface
body.ApplyLinearImpulse((deltaPos - vel * 0.5f) * body.Mass, pullPos);
}
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, bool playSound)
public AttackResult AddDamage(Vector2 simPosition, DamageType damageType, float amount, float bleedingAmount, bool playSound)
{
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
@@ -299,7 +299,7 @@ namespace Subsurface
float mid = (armorLimits.X + armorLimits.Y) / 2.0f;
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(position - SimPosition), mid);
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid);
if (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f)
{
@@ -310,9 +310,9 @@ namespace Subsurface
}
}
if (playSound)
if (playSound && amount>0.0f)
{
AmbientSoundManager.PlayDamageSound(damageSoundType, amount, position);
AmbientSoundManager.PlayDamageSound(damageSoundType, amount, ConvertUnits.ToDisplayUnits(simPosition));
}
//Bleeding += bleedingAmount;
@@ -322,7 +322,7 @@ namespace Subsurface
for (int i = 0; i < bloodAmount; i++)
{
Vector2 particleVel = SimPosition - position;
Vector2 particleVel = SimPosition - simPosition;
if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel);
GameMain.ParticleManager.CreateParticle("blood",