|
Post by keymaster on Sept 20, 2017 18:44:49 GMT
gyazo.com/9645d11dc541ea57a3d560279b2c779bmy 'knockback ability code' public class KnockbackAbilityEffect : BaseAbilityEffect {
#region Public public override int Predict(Tile target) { Unit attacker = GetComponentInParent<Unit>(); Unit defender = target.content.GetComponent<Unit>();
// Get the attackers base attack stat considering // mission items, support check, status check, and equipment, etc int attack = GetStat(attacker, defender, GetAttackNotification, 0);
// Get the targets base defense stat considering // mission items, support check, status check, and equipment, etc int defense = GetStat(attacker, defender, GetDefenseNotification, 0);
// Calculate base damage int damage = attack - (defense / 2); damage = Mathf.Max(damage, 1);
// Get the abilities power stat considering possible variations int power = GetStat(attacker, defender, GetPowerNotification, 0);
// Apply power bonus damage = power * damage / 100; damage = Mathf.Max(damage, 1);
// Tweak the damage based on a variety of other checks like // Elemental damage, Critical Hits, Damage multipliers, etc. damage = GetStat(attacker, defender, TweakDamageNotification, damage);
// Clamp the damage to a range damage = Mathf.Clamp(damage, minDamage, maxDamage); return -damage; } #endregion protected override int OnApply(Tile target) { Unit defender = target.content.GetComponent<Unit>(); Unit attacker = GetComponentInParent<Unit>(); Point endPos; // Start with the predicted damage value int value = Predict(target);
// Add some random variance value = Mathf.FloorToInt(value * UnityEngine.Random.Range(0.9f, 1.1f));
// Clamp the damage to a range value = Mathf.Clamp(value, minDamage, maxDamage);
// Apply the Knockback to the target Stats s = defender.GetComponent<Stats>(); Movement m = defender.GetComponent<Movement>(); Tile t = defender.GetComponent<Tile>(); //defender.tile.pos.x
//calculate the Pos of tile to be knoockedbacked to switch (attacker.dir) { case Directions.North: endPos = new Point(defender.tile.pos.x, defender.tile.pos.y + 1); break; case Directions.East: endPos = new Point(defender.tile.pos.x+1, defender.tile.pos.y); break; case Directions.South: endPos = new Point(defender.tile.pos.x, defender.tile.pos.y - 1); break; default: // West endPos = new Point(defender.tile.pos.x=1, defender.tile.pos.y); break; }
//update target tile t.pos = endPos; //get knockbacked? m.Traverse(t); //dmg s[StatTypes.HP] += value; return value; } }
thank you again for the quality work and any response yo might give. my media isnt loading.... fail ability gyazo.com/9645d11dc541ea57a3d560279b2c779bknockback gyazo.com/86d31a4237e3602e40c7c0b5765451d4knockback damage component gyazo.com/8593f583b32ee938ee928a52bc3fe063
|
|
|
Post by Admin on Sept 21, 2017 14:35:47 GMT
It looks like you're getting the general idea. Briefly looking at your code snippet I notice that you have grabbed the tile that the defender stands on, then set the tile's position to be the knockback location. What you probably wanted to do is to calculate the knockback location, then grab a new tile from the board based on that location, and finally place the defender on that new tile.
|
|
|
Post by keymaster on Sept 21, 2017 17:42:04 GMT
this is the part I'm failing to understand. how can i reference the board in the ability class? I'm combing though the framework looking for an example where you reference the board (and maybe use that handy GetTile function) all i can find is functions that pass the board and use that but abilityEffect passes a Tile not the board...
thank you for responding.
|
|
|
Post by Admin on Sept 21, 2017 23:02:09 GMT
When playing the battle scene, check out the hierarchy of GameObjects. Everything (important) is ultimately a child of the Battle Controller, and the Battle Controller has a direct reference to most everything else. So for example, you could do something like:
var board = GetComponentInParent<BattleController> ().board; var pushBackTile = board.GetTile (endPos);
Keep in mind that there may not be a tile at the new position, or that the tile may be already occupied, and you would need to solve for those kinds of scenarios - perhaps by just not doing anything regarding the actual knockback.
|
|
|
Post by keymaster on Sept 22, 2017 1:03:23 GMT
thank you for helping me see the forest for the trees so to speak, is there more i have to do then call Traverse (or Unit.Place(tile)) to move a unit? because even with the proper code (endPos is calculated correctly) the unit remains unknockedbacked...
i moved the script to the damage portion of the prefab since it was apparently doing nothing on the base, it still ONLY does damage and my breakpoints and debugs both say endPos is correctly +1 move away from defender.tile.pos.
|
|
|
Post by Admin on Sept 22, 2017 13:51:01 GMT
There are two parts to consider:
1st is the background data that defines a game model (this is data that can potentially be saved to disk, or that an AI could use to plan its attack etc). You are correct that you can update the model with something like "unit.Place(tile)". Note that in the game model, a unit only occupies a single tile at a time, there is never a concept of a transition from one tile to another.
2nd Is how you display that data to the user in a "view", such as a GameObject hierarchy representing a hero. Visually, we would not want a unit to just "snap" from one tile location to another, but would rather see the unit animate from one tile to another over time. The view has its own data separate from the game model. The data defining where a unit appears in the scene is on the Transform component. Once we have updated the game model we need to update the view data to match. You can see this pattern in the "Pathfinding" tutorial. For example, in the "Walk Movement" we first "place" the unit on the target end tile (this is updating the game model), and then we animate the view to match when we do the "Walk" coroutine which is really just applying a "transform.MoveTo" operation.
|
|
|
Post by keymaster on Sept 22, 2017 14:30:39 GMT
thank youapparently the other part to consider is to use a coroutine for movement always (still not 100% on multythreading but thats because i never made a project that required it...) but i got the hint after taking another walk though movement like you suggested, i still need to put in all the checks for walls and roadblocks and probably figure out how to force the unit to 'walk' over using its own movement mode but after breaking my head against a simple mechanic for 4 days i want to move onto TRAPS! if you are still willing to respond... from what I've read i THINK i got the hint to use a Tile as the base for the trap component (or ON the tile, like a unit...that i still need to figure out how to passthough) my (probably first...) question is would a trigger collider in unity be enough (and then have the trigger apply the ability/status when something enters) or does the framework demand some explicit check every tile? should i start looking at your CCG and board game for ideas for 'reactive' components? thanks again, its small but im really happy i made a knockback.
|
|
|
Post by Admin on Sept 22, 2017 17:59:01 GMT
Thats great, I'm glad you got it working. Making small goals like this is a great way to learn!
Just as a quick tip, Unity (for the most part) is not multi-threaded. While co-routines allow you a programming architecture that is asynchronous, it is all still occurring on the main thread. If none of that made any sense, then you can ignore it for now.
For traps, you "could" rely on trigger colliders, but I would imagine that you will want more control. For example, when pathfinding you will need to make sure that a character "thinks" it can navigate through that tile so some changes will need to be made there. Also, you may want to "interrupt" movement sequences when encountering a trap, so you will need to modify that code as well. My personal preference would be that I would know about a trap because of the "model" where I would view the existence of a collider as part of the "view".
While I would definitely recommend you to follow along with my other two projects, I would also point out that the architecture styles of each are very different from the Tactics RPG. The Tactics RPG relies heavily on MonoBehaviour to make it easier for new programmers to experiment with. The other projects are a bit more advanced because they handle data in more flexible (but perhaps less easy) ways.
|
|
|
Post by vivere on May 30, 2021 11:16:55 GMT
So, rather than make a new thread, I figure I'll ask my question here since it's over the same topic. I'm also trying to make a knockback ability effect, but without forcing the defender to move with its Movement component, because I don't want the knockback to look like you hit the enemy, then they fly away or walk away or teleport away. Instead, I'm knocking the defender back by forcing it along a path with a Tweener in an IEnumerator function within the knockback ability effect class that knocks back the defender to a transform position in the center of a destination tile. I'm storing each tile along this path in a List of Tiles so I can do a bunch of checks on the relationships between two adjacent tiles such as height differences for running into walls or falling down a drop, null checks for edges of the map, tile.content checks for if a Unit or obstacle is on the tile, etc. If such things are found, the path List shrinks in size so the destination tile is closer than the total displacement the ability could have by default. Once all those checks are done and the final path is known, I use that path to knockback the defender, and after the yield statement in the Knockback IEnumerator function, I do defender.Place(destinationTile) and defender.Match() to ensure the defender is on the exact spot on the tile to avoid floating point errors.
My problem is that I'm finding that the whole class is being ran twice. I discovered this due to Debug.Log statements printing out twice for the Predict function before applying the effect (before OnApply happens, so the Debug.Log statements printing again is not due to OnApply calling Predict to get the predicted damage). The function that gets called runs once all the way through, then another copy of the function runs afterward (rather than interweaved Debug.Log statments). I'm really confused on what to do or where the problem is coming from. Whatever is causing it, it seems like it is introducing tons of bugs. Any help is appreciated.
EDIT: Also, sometimes everything will work ok for a bit, then it seems like subsequent knockback attempts on other turns will have some or all of their functions not actually run (The Debug.Log statements, which aren't within any if statements or anything, just sometimes don't print out as though sections, or all, of the Predict function just don't run. The same has happened with the OnApply function where sometimes parts of it don't work, too. I have changed everything so much that I feel like I've lost the "best" version of this code that I've had now at this point. Every time I try to fix it, it feels like I break it worse.
|
|
|
Post by vivere on Jun 1, 2021 5:00:50 GMT
So, I based my knockback ability effect script off the DamageAbilityEffect script and then modified it. Looking at the DamageAbilityEffect script on its own, I began testing it with simple Attack commands against units with Debug.Log statements throughout the Predict function and discovered that the DamageAbilityEffect script also executes twice for the Predict function, and then a third time when actually doing the ability (since it called Predict from OnApply). Is there a way to change this system to where it won't run the Predict function twice before OnApply?
|
|
|
Post by Admin on Jun 1, 2021 14:34:58 GMT
I tried to reproduce the issue you are seeing in the sample project. When I trigger a round of attack, I clear the debug console to make sure I am starting fresh. Then move, select attack, and see the first "Predict" which is needed to show the estimated amount of damage that could be done from applying the action. Next I apply the action, and only see one more "Predict" used as I would expect - this time used to help calculate the actual amount of damage done. Have you by chance modified the code that was in the sample as well?
You may also need to verify that you don't have a duplicate script somewhere that could be causing the code to run more than once.
|
|
|
Post by vivere on Jun 2, 2021 14:15:41 GMT
Thankfully, I finally figured this out. It was a case of a function that calls another function that calls another function that calls...... disguising the fact that that last function in the line was actually calling the Predict function a second time. Essentially, I had an extra SetTarget(0) in my code accidentally. And, that line was something accidentally left in, or perhaps accidentally duplicated with ctrl+D after trying something and removing it where I had deleted everything but that line (I tend to use ctrl+X to delete things sometimes which is a bad habit because I sometimes fat finger ctrl+D, lol).
There are still unrelated bugs with my knockback, but I'm getting close to having a knockback ability effect where I can specify any amount of displacement I want a unit to have and it will knock back through all of those tiles or stop short if it falls far, hits a wall, hits an obstacle on a tile, hits the edge of the map, or hits another unit. It will also damage both units if 2 are involved, but only if the collaterally damaged unit isn't a large height distance difference from the unit colliding into it. I'm getting close to being happy with it all!
|
|