|
Post by Arghonaut on Jul 7, 2018 9:21:18 GMT
Have been busy wrapping my head around the viewer side of things and learning your animation tools and have started implementing an EffectView class that displays projectiles for damage actions. So far so good, for a sequence of projectiles at different targets its working well, I also have a damage indicator that spawns after the projectile hit thanks to your keyframe setup. I'm not that familiar with IEnumerators and i cant for the life of me figure out how to get it to shoot multiple projectiles at different targets simultaneously. Code is below, for it to shoot simultaneously in the DoProjectile method that accepts a list i can see the yield / MoveNext code (or equivalent) needs to sit outside the foreach loop, but damned if i can figure how to make that work! I'm sure its something really obvious! Any tips?
void OnPrepareDamage(object sender, object args) { var action = args as DamageAction; action.perform.viewer = OnPerformDamage; }
IEnumerator OnPerformDamage(object sender, object args) { var dam = args as DamageAction; var source = board.GetCardView(dam.source); var targets = new List<Transform>(); foreach (IDestructable target in dam.targets){ targets.Add(board.GetCardView(target as Card).transform); } var projectiles = DoProjectile(source.transform, targets); while (projectiles.MoveNext()) yield return null;
yield return true; }
IEnumerator DoProjectile(Transform source, List<Transform> targets) { foreach (Transform target in targets) { var projectile = DoProjectile(source, target); while (projectile.MoveNext()) yield return null; } }
IEnumerator DoProjectile(Transform source, Transform target){ var projectile = Instantiate(testProjectile); projectile.transform.position = source.position; var tweener = projectile.transform.MoveTo(target.position, 0.5f, EasingEquations.EaseInExpo); while (tweener != null) yield return null; Destroy(projectile); }
|
|
|
Post by Admin on Jul 8, 2018 2:02:15 GMT
Here's some (NOT TESTED - may not compile) code I wrote that should give the general idea of something that I think will work for you:
Tweener tweener = nil; List<GameObject> projectiles = new List<GameObject>(); for (target in targets) { var projectile = Instantiate(testProjectile); projectiles.Add(projectile); projectile.transform.position = source.position; tweener = projectile.transform.MoveTo(target.position, 0.5f, EasingEquations.EaseInExpo); } while (tweener != null) yield return null; for (int i = projectiles.Count - 1; i >= 0; --i) Destroy(projectiles[i]);
You only need to maintain a reference to the last created tweener, all the first ones will have completed by the time the last one does since you use the same duration for all of them. You also should keep track of all of the objects you instantiated so you can destroy them all when you are done (or for bonus points use a pooler instead). Good luck!
|
|
|
Post by Arghonaut on Jul 8, 2018 6:45:35 GMT
You only need to maintain a reference to the last created tweener That was the key, knew it would be something simple, thankyou! all working now with some minor tweaks Will look into a pooler once i have all the basic functionality working properly.
|
|
|
Post by Admin on Jul 8, 2018 17:05:22 GMT
Yay! Glad to hear you got it working!
|
|
|
Post by Legend on Jul 16, 2018 19:52:50 GMT
wow this is something i am doing right now - i finished working on a popup text effect which just pops up the damage on the characters after attack - pretty much the same idea except i just spawn it ontop- and i had to create an oncompletedamage notification within the destructable system because of i added dmg types to my game and the actual amount is changed depending on the dmg types....anyways i have a bunch of prefabs that i will have to put in a pooler though for the different FX - ill have a EffectNAME within the json file to pull the appropriate prefab name. but i am just starting it out so everything should be fairly simple as far as JSON to cardview property to casting the prefab upon onperformdamage ( or onperformblessing/curse ) in my situation as well...just a matter of figuring out if i HAVE to use a pooler for each prefab FX orrrr if i can have one pooler for a bunch of prefabs that i can call based off of name (like a dictionary or something).
|
|
|
Post by Legend on Jul 16, 2018 20:13:37 GMT
o and looking at the code looks like you would have to add a source in the DamageAction to actually source it from where the card is .... so like....
public Card source
then it looks like in the boardview you created a method that basically did the getmatch functionality except called in getcardview
|
|
|
Post by Admin on Jul 17, 2018 13:22:39 GMT
For things that can appear frequently, like HP damage popups, you could consider making them a part of the card prefab itself - you are not required to make a new pooler for each thing. For things that happen very sporadically, like once a game, you also may not see much benefit in a pooler. Mostly I use poolers because my experience with Unity in the past has been that it is very bad at releasing resources. The memory just keeps rising. By reusing resources, I know that at least I can keep the memory levels balanced.
|
|
|
Post by Legend on Jul 17, 2018 14:58:40 GMT
That is what i read - it is better to get more memory than to have unity release the allocation - obviously to a degree as with anything.
One thing on this code i noticed that will need to be added is a denotation for ALL AOE vs Enemy AOE vs random / single hits - for instance consecration vs arcane missles or w.e shoot random shots out - might be able to use the target for this or a separate variable
shouldnt be to difficult.
|
|
|
Post by Arghonaut on Jul 17, 2018 21:11:40 GMT
Yeah you figured most of it out, GetCardView takes a card and returns its cardview. Damage actions have a public Container source, as when checking for aspects (like "poisonous") it could be part of an ability, or part of a card (if a spell for example).
Yeah my damage indicators are part of the prefab. Pretty simple to do then:
void OnPerformDamageAction (object sender, object args) { var action = args as DamageAction; if (action.targets.Contains (card as IDestructable)) { StartCoroutine(DamageIndicator(action.amount)); Refresh (); } }
IEnumerator DamageIndicator(Damage damage){ if (damageText != null && damageBackground != null) { damageText.text = "-" + damage.TotalDamage().ToString(); damageText.gameObject.SetActive(true); damageBackground.gameObject.SetActive(true); yield return new WaitForSeconds(2f); damageText.gameObject.SetActive(false); damageBackground.gameObject.SetActive(false); } } I also have multiple damage types, i just made a Damage class which things like damage actions and attack values use.
public class Damage { public int physical; public int elemental; public int divine;
public Damage(){}
public Damage(int physical, int elemental, int divine){ this.physical = physical; this.elemental = elemental; this.divine = divine; }
public int TotalDamage(){ return physical + elemental + divine; } Also has a bunch of operator overrides so its easy to add / compare them.
|
|
|
Post by Arghonaut on Jul 17, 2018 21:44:24 GMT
Here is where my projectile code is at present. Wards are what i call secrets. so for secrets and spells want the hero to be the source of the projectile.
IEnumerator OnPerformDamage(object sender, object args) {
var dam = args as DamageAction; BattlefieldCardView source = null; if (dam.source is Ability) { var ability = dam.source as Ability; source = board.GetCardView(ability.card); if (ability.card is Spell || ability.card is Ward) source = board.GetHeroView(ability.card.ownerIndex);
} else if (dam.source is Minion){ source = board.GetCardView(dam.source as Card); } else if (dam.source is Spell){ var spell = dam.source as Spell; source = board.GetHeroView(spell.ownerIndex); }
var targets = new List<Transform>(); foreach (IDestructable target in dam.targets) { targets.Add(board.GetCardView(target as Card).transform); } var projectiles = DoProjectile(source.transform, targets); while (projectiles.MoveNext()) yield return null; yield return true; }
IEnumerator DoProjectile(Transform source, List<Transform> targets) { Tweener tweener = null; List<GameObject> projectiles = new List<GameObject>(); foreach (Transform target in targets) { var projectile = Instantiate(testProjectile); projectiles.Add(projectile); projectile.transform.position = source.position; tweener = projectile.transform.MoveTo(target.position, 0.5f, EasingEquations.EaseInExpo); } while (tweener != null) yield return null; for (int i = projectiles.Count - 1; i >= 0; --i) Destroy(projectiles[i]); }
|
|
|
Post by Arghonaut on Jul 17, 2018 21:50:43 GMT
You will see you need to think through how to handle spells like arcane missiles vs spells like multi shot. I made a "sequential damage action" for spells like arcane missiles, all it does is create a series of damage actions. Then updated the random target selector so it chooses targets appropriately for each scenario:
public bool sequential; // ie arcane missile.
public List<Card> SelectTargets (IContainer game) { var result = new List<Card> (); var system = game.GetAspect<TargetSystem> (); var card = (container as Ability).card; var marks = system.GetMarks (card, mark); if (marks.Count == 0 || (!sequential && marks.Count < count)) return result; if (!sequential){ for (int i = 0; i < count; ++i) { var target = marks.Random(); result.Add(target); marks.Remove(target); } }
else{ for (int i = 0; i < count; ++i) result.Add(marks.Random()); } return result; }
|
|
|
Post by Legend on Jul 18, 2018 20:09:01 GMT
hmm i may eventually want to do what you did with the damage and make it in to a class - i made a damagetypes enum and put the damage calculations in the destructablesystem
i have the source from spells and other things that are not related coming out of the center cause
1. the card is gone by the time the damageaction is called 2. i dont have the same kind of 'hero' scenario as hearthstone
ive got it almost working but im doing it a bit different so i have to tweak the doprojectile - i bought realistic VFX and it came with prefabs for different spells and abilities...so right now it loads it on the source runs the animation which for test purposes is a lightning bolt and comes down from offscreen on the 'source' ....so i gotta figure out how to make it go FROM The source TO the target still - going to have to dive into the premade prefab for that though.
when Bill mentioned about adding it to the prefab i went that route but i am keeping it animated. Although the same thing could be done with the tweener moveto and change the alpha. (basically i just have it going up and changing the alpha till its gone over a few seconds)
for the sequential did you take into consideration the health of the targets so that lets say it chooses 3 targets for 4 damage and 2 are at 1 damage and one is at 2....it may deal 2 damage to the guy with 1 health left then subsequent 1 damage each to the others leaving 1 guy standing (the guy who orinially had 2 health - which is now at 1 health)...i may not understand the sequential method properly though
hard to tell with other code but it sounds like you get the targets randomly and populate sequential damageactions but you may have to either calculate the damage to be done and randomize before hand or carry each tick of damage seperately - randomize deal 1 damage - handle outcome - random - deal 1 damage - handle outcome - rinse repeat.
that being said you can also have it so that it deals more damage to the minion than its current health and that is fine too just a different gameplay. Not sure if a player would be as happy playing a card like that though.
thoughts on this?
------------------- battlecardview
if (action.targets.Contains(card as IDestructable)) { floattext.setText(action.amount.ToString()); floattext.gameObject.SetActive(true); }
floatingtext class:
IEnumerator RunPopupText() { AnimatorClipInfo[] clipinfo = animator.GetCurrentAnimatorClipInfo(0); var continue_time = Time.time + clipinfo[0].clip.length; while (Time.time < continue_time) yield return new WaitForEndOfFrame(); gameObject.SetActive(false);
popupText = animator.GetComponent<Text>();
}
==============================
void OnPerformDamageAction(object sender, object args) { var action = sender as DamageAction; action.prepare.viewer = PerformDamageViewer; } IEnumerator PerformDamageViewer(IContainer game, GameAction action) {
var damageaction = action as DamageAction; var board = GetComponent<BoardView>();
BattlefieldCardView BFsource = null; GameObject CVsource = null;
if (damageaction.source is Ability) { var ability = damageaction.source as Ability;
if (ability.card is Spell || ability.card is Retribution) CVsource = GameObject.Find("centerBoard"); else BFsource = board.GetBattleFieldCardView(ability.card);
}
else if (damageaction.source is Minion) { BFsource = board.GetBattleFieldCardView(damageaction.source as Card); }
else if (damageaction.source is Hero) { BFsource = board.GetBattleFieldCardView(damageaction.source as Card); }
else if (damageaction.source is Spell) { var spell = damageaction.source as Spell; CVsource = CVsource = GameObject.Find("centerBoard");
}
var targets = new List<Transform>(); foreach (IDestructable target in damageaction.targets) { targets.Add(board.GetMatch(target as Card).transform); } Debug.Log(targets.Count);
IEnumerator projectiles;
if (BFsource !=null) projectiles = DoProjectile(BFsource.transform, targets); else projectiles = DoProjectile(CVsource.transform, targets);
while (projectiles.MoveNext()) yield return null; }
|
|
|
Post by Arghonaut on Jul 19, 2018 0:05:07 GMT
Yeah you were correct it will continue to hit a minion once its hp is 0 or less. Did some refactoring to correct. Took the sequential part out of the random target selector. Added in a check to remove targets in the GetMarks method with hp <=0. Updated the sequential damage system to be as follows:
public class SequentialDamageActionSystem : Aspect, IObserve { List<SequentialDamageAction> inProgress = new List<SequentialDamageAction>();
public void Awake() { this.AddObserver(OnPerformSequentialDamageAction, Global.PerformNotification<SequentialDamageAction>()); this.AddObserver(OnPerformDamageAction, Global.PerformNotification<DamageAction>()); }
public void Destroy() { this.RemoveObserver(OnPerformSequentialDamageAction, Global.PerformNotification<SequentialDamageAction>()); this.RemoveObserver(OnPerformDamageAction, Global.PerformNotification<DamageAction>()); }
void OnPerformSequentialDamageAction(object sender, object args) { var action = args as SequentialDamageAction;
if (action.count > 0) { DoDamageAction(action.source, action.amount); action.count--; inProgress.Add(action); } }
void OnPerformDamageAction(object sender, object args) { var action = args as DamageAction;
for (int i = inProgress.Count - 1; i >= 0; i--){ if (inProgress[i].source == action.source){ if (inProgress[i].count > 0) { DoDamageAction(action.source, action.amount); inProgress[i].count--; if (inProgress[i].count == 0) inProgress.Remove(inProgress[i]); } } } }
void DoDamageAction(Container source, Damage amount) { var selector = source.GetAspect<ITargetSelector>(); var cards = selector.SelectTargets(container); var targets = new List<IDestructable>(); foreach (Card card in cards) targets.Add(card as IDestructable); var action = new DamageAction(targets, amount, source); container.AddReaction(action); } }
Working as intended for spells like arcane missiles now. Might play around with something like a sequential AOE spell next!
|
|
|
Post by Admin on Jul 19, 2018 15:38:17 GMT
Keep in mind that it isn't necessarily a problem to hit a minion with hitpoints less than zero. For example, what if you use a card that does six points of sequential random damage and there are only 2 single hitpoint minions on the board? I would want to see the overkill where it just kept hitting them. In Hearthstone this is also fine because the targets are merely considered "mortally wounded" and are not actually "dead" - the actual death and reaping of the cards occurs at a later point in the action system, and comes with its own set of events that can be responded to.
|
|
|
Post by Legend on Jul 19, 2018 17:15:26 GMT
ya thats the thing - you could go either way - as far as making a card like that though is you have to keep in mind play functionality. So will the player want to play the card and is it worth having in the deck. It eventually comes down to balancing the card in the end as well. i mean you could do both but would need to denote the different types of damaging there otherwise the player would be confused on what to do...personally i would pick a card that deals direct damage over one that may deal overdamage in most cases to add to my deck - the cost would obviously be a factor though.
|
|