How to reverse an If Statement when conditions are no longer met - c#

In my Unity project, I have an if statement set-up where if the conditions are true, my character's box collider will shrink to a specific value when crouching (along with movement speed change, turning speed, etc). And when my character is not crouching anymore, the box collider (along with the other parameters I mentioned) will return to its original size in the else statement.
However, I don't want to keep track of every parameter I change/need to reverse across all my scripts. In the future, my character's base parameters may change, which'll cause me to have to go back and change every else statement I ever make involving changing my character to its default parameters. I can create variables to store my base parameters so I wouldn't have to worry about copy-pasting values if I change them in Unity. But it'll eventually snowball into spaghetti-code territory if I do this method for every parameter I create/change in my scripts.
Is there a simpler way to code in C# where if an If statement is no longer true, it'll reverse all changes it made?
Psuedo code:
float height = 10.42f;
float movementSpeed = 95.89f;
if (condition1==true && condition2==true){
height = 3.5f;
movementSpeed = 40.125f;
}
*code to reverse all changes the If Statement made when it stops being true*

Is there a simpler way to code in C# where if an If statement is no longer true, it'll reverse all changes it made?
No. If you want to revert to an earlier state you need to save that state in advance, e.g. in variables as you propose in the question.

It's look like buff system , it can be todo and undo player status , And it's expandability , So you can design a buffsystem to do it like this:
public interface IBuff
{
void Do(Player player);
void UnDo(Player player);
}
public BuffAddHeight : IBuff
{
public Do(Player player)
{
player.Height += 100;
}
public UnDo(Player player)
{
player.Height -= 100;
}
}
public BuffSeed : IBuff
{
public Do(Player player)
{
player.speed += 100;
}
public UnDo(Player player)
{
player.speed -= 100;
}
}
public class Player
{
// by the way , you can design a buff container to manager your buff.
public void AddBuff(IBuff buff) { buff.Do(this); }
public void RemoveBuff(IBuff buff) { buff.UnDo(this); }
}

Related

How can I execute code if something is true, continue executing the code indefinitely, but stop evaluating the if statement?

I'm just starting out please excuse vast ignorance.
I'm writing a c# script in unity as part of the essentials training. I'm doing the 3d audio module and I thought I'd try and get a little bit fancier than the scope of this particular lesson which is supposed to be having an object fly through a window in a pre-built scene and make a 3d sound as it moves.
I wanted to make the movement of the object conditional upon a player moving close to it in 3d space. I figured out how to trigger the movement of an object in a script with an if statement that changes the transform parameters of the object the script is attached to when a 'distanceFromObject' variable is < 2. It works, however the script runs in the update section of the script which runs once every frame. This means that the object's transform parameters are changed every frame as expected but of course stops doing so when the distance between the object that's moving and the player exceeds 2.
I see the mistake I've made because if the object moves away when the player gets close then it will inevitably eventually move far enough away that the distanceFromObject variable will grow bigger than 2 whereupon it stops and just hovers in place. I don't know how to fix it though.
I need the script to check the distance between the object and the player every frame so that it will trigger the instance the player gets close enough, and when they get close enough, I need the object to move away, however once it has been triggered to move, I need the object to continue moving, but the script to stop checking what the distance is anymore.
The script looks like this
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyOff : MonoBehaviour
{
public Vector3 rotateChange;
public Vector3 positionChange;
public float distanceFromObject;
public GameObject character;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
Use flags instead of writing your logic in the if statement :
public class FlyOff : MonoBehaviour
{
// fields removed for more readability
// use a flag that's set to true/false
private bool isCloseEnough = false;
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
// set the flag to true when player is close enough
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
// even if the player gets far, the flag will remain true
if (isCloseEnough)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
You can even apply the opposite logic to stop the object to move away when it has reach a certain distance :
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
else if (distanceFromObject > SomeValue)
{
isCloseEnough = false;
}
If I understand correctly you could just add a bool flag and set it once you are close enough. Then you can start moving and skip further distance checks but keep moving forever.
private bool flyAway;
void Update()
{
if(!flyAway)
{
distanceFromObject = Vector3.Distance(character.transform.position, transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
flyAway = true;
}
}
else
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
In general: Avoid using print every frame! Even if you user doesn't see the log in a built app it is still causing overhead!

Unity PlayerPrefs is not updating my 'high score'

I'm making a 2D game where you're in the middle of the screen and you move round an endless green (screen) world and white cubes spawn randomly around you, and I have finished the game mechanics and a main menu and game over screens. The one thing I'm trying to add now is a high score. I did a bit of research and found PlayerPrefs is probably the way to do it. I have a seperate scene for my main menu and my gameplay level (which includes the game over screen). I have no error messages. I have created a HSSetter (High Score Setter) script on the high score text in the main menu screen:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Start is called before the first frame update
void Start()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
}
and in my score script which is in my actual game level, here's the bit where I try to create the high score:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class score : MonoBehaviour
{
public int scoreCount = 0;
public int highScoreIFA;
void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
void Update()
{
if (scoreCount >= highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
}
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
}
}
}
In AddToScore(), I increment scoreCount.
Through some debugging, I have found that everything in the HSSetter script works - when I change the highScoreText.text, the text on screen changes, which led me to believe the issue might be with the change of scenes? Thanks!
Multiple things you should do here
The first you already updated in your question afterwards: You had the condition wrong and always updated only
if(highScoreIFA > scoreCount)
which would almost always be the case.
Now you have changed it to
if(scoreCount >= highScoreIFA)
which still is not good since if the score is equal there is no reason to update it, yet.
I would rather use
if(scoreCount > highScoreIFA)
so only really update it when needed.
Secondly in both scripts do not use Update at all! That is extremely inefficient.
I would rather use event driven approach and only change and set stuff in the one single moment it actually happens.
You should only one single class (e.g. the score) be responsible and allowed to read and write the PlayerPrefs for this. I know lot of people tent to use the PlayerPrefs for quick and dirty cross access to variables. But it is exactly this: Quick but very dirty and error prone.
If you change the keyname in the future you'll have to do it in multiple scripts.
Instead rather let only the score do it but then let other scripts reference it and retrieve the values directly from that script instead
And finally you should use
PlayerPrefs.Save();
to create checkpoints. It is automatically done in OnApplicationQuit, bit in case your app is force closed or crashes the User would lose progress ;)
Might look like
public class score : MonoBehaviour
{
public int scoreCount = 0;
// Use an event so every other script that is interested
// can just register callbacks to this
public event Action<int> onHighScoreChanged;
// Use a property that simply always invoked the event whenever
// the value of the backing field is changed
public int highScoreIFA
{
get => _highScoreIFA;
set
{
_highScoreIFA = value;
onHighScoreChanged?.Invoke(value);
}
}
// backing field for the public property
private int _highScoreIFA;
private void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
// Only update the Highscore if it is greater
// not greater or equal
if (scoreCount > highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
// Save is called automatically in OnApplicationQuit
// On certain checkpoints you should call it anyway to avoid data loss
// in case the app is force closed or crashes for some reason
PlayerPrefs.Save();
}
}
}
}
Then your other script only listens to the event and updates its display accordingly. It is even questionable if both scripts should not rather simply be one ;)
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Reference your Score script here
[SerializeField] private score _score;
private void Awake ()
{
// Find it on runtime as fallback
if(!_score) _score = FindObjectOfType<score>();
// Register a callback to be invoked everytime there is a new Highscore
// Including the loaded one from Start
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnDestroy()
{
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnHighScoreChanged(int newHighScore)
{
highScoreText.text = $"High Score: {newHighScore}";
}
}

How to call a method after each line of code or something like that

I have a method that is supposed to check a player's HP and then perform some logic based on if the number is greater than 0, or less than or equal to 0.
The method works but if I type it in the code and then change the hp value of a player it won't do anything until I type in the method name again. Then it will display the correct information.
At first I thought of using some kind of loop instead of a method. If I am correct, that means I'd have to have put curly braces around all my code that needs to get checked (which means basically around the whole code and I don't want that). Same with IF statement - even if I put it at the beginning of the code I'd still have to put curly braces around all the code.
Then I thought of a method which I already mentioned. And as I said - it does what it's supposed to do but only if I paste it multiple times into the main code.
Now, my question - is there ANY way to make the method "repeat itself" constantly or, I don't know, start at some place in the code and remain active?
Here is the method:
static void Status()
{
if (Player.playerHealth <= 0)
{
Player.isDead = true;
Console.WriteLine("You are dead!");
}
else if(Player.playerHealth > 0)
{
Player.isDead = false;
Console.WriteLine("You are not dead!");
}
}
I would be really grateful for any help.
You can define playerHealth as a property. That way, any time anyone changes it, you can make some code fire, including the check that you want.
class Player
{
protected int _playerHealth = 0;
public int PlayerHealth
{
set
{
_playerHealth = value;
if (_playerHealth == 0)
{
isDead = true;
Console.WriteLine("You are dead!");
}
}
get
{
return _playerHealth;
}
}
Now you don't even need to call Status... the logic will occur automatically whenever the player's health is modified.
var player = new Player();
player.PlayerHealth = 0; //Automatically triggers message
Correct me if I'm wrong but you want to call a method everytime the player's life changes? It looks like you should use an event.
public class HealthEventArgs : EventArgs
{
public int Health { get; set; }
public HealthEventArgs(int health)
: base()
{
this.Health = health;
}
}
public class Player
{
public event EventHandler<HealthEventArgs> LifeChanged;
int _Health;
public int Health
{
get => _Health;
set
{
_Health = value;
LifeChanged?.Invoke(this, new HealthEventArgs(_Health));
}
}
}
Then in your code it would look something like that
Player player = new Player();
player.LifeChanged += (o, e) =>
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};
With that mechanism in place, everytime the health of a player changes the event will fire and you will have access to the player and its health and can act accordingly.
If you're not confortable with lambda expressions, the code above can also be written as such
Player player = new Player();
player.LifeChanged += PlayerLifeChanged;
public void PlayerLifeChanged(object o, HealthEventArgs e)
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};

Unity Destroy and other code not working?

I'm trying to make the game Snake, and I'm trying to get the apple functionality working. What this script is meant to do, is whenever my Snake goes over the Apple, the apple disappears and reappears at a random location on the screen. But instead it does nothing, any idea as to why?
P.S: Camera is Size 10 and Aspect Ratio is 16:9 which is why I have some weird Random.Range values. Also I used Debug.Log in Update to make sure that the variable worked, and yes it does, whenever my snake moves its coordinates are displayed.
public class Apple_RandomSpawn : MonoBehaviour
{
private Vector2 foodPos;
private Snake_Move snakeMove;
void Start()
{
SpawnApple();
}
public void Update()
{
transform.position = new Vector2(foodPos.x, foodPos.y);
SnakeAte();
}
public void SpawnApple()
{
foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
}
public void SnakeAte()
{
if (Mathf.Approximately(foodPos.x, snakeMove.pos.x) && Mathf.Approximately(foodPos.y, snakeMove.pos.y))
{
SpawnApple();
}
}
}
First of all, it does not directly have something to do with your problem, but DO NOT put GetComponent() or GameObject.Find() in the Update() function. These two, especially GameObject.Find() function is super heavy, so It's recommended that you should call these kinds of function inside Start() or Awake(), or at the initiallization of a class. It can directly, and heavily impact the performance of your game, so here's my suggestion:
[SerializeField]
private Snake_Move snakeMove;
And drag your gameobject (which Snake_Head component is attatched) via Inspector. You should always consider this way first, rather than using GameObject.Find() and GetComponent().
Second, Float is not recommend to compare equality directly through =, since there must be rounding error. There is a help function with regard to comparing two float value in Unity, like Mathf.Approximately(float a, float b). Directly comparing two float value via = would almost always not work as you might think.
Third, It doesn't seem to be that there are no Instantiate() function in your code, but you are try to use one apple object, and every time you consume it, just change it's position. Then why does Object.Destroy(gameObject) exists? What you're doing is, just destroying the apple when you get it first time. I think you have to remove the Destroy() function, and SpawnApple() changes the target coordinate of apple, and the position will be updated in Update() function.
And it's no need to indirectly set the target position, and update to it in Update() function. You can directly set the position of apple, like:
// assuming you followed my suggestio aboven about snakeMove.
public void Update()
{
SnakeAte();
Debug.Log(snakeMove.pos);
}
public void SpawnApple()
{
transform.position = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
}
public void SnakeAte()
{
if (foodPos == snakeMove.pos)
{
SpawnApple();
}
}
First of all your null ref in your last comment comes from:
private Snake_Move snakeMove;
Its a private variable and its never assigned. You either need to make it public/[SerialistField] and assign it in inspect or have some sort of initialize function that give it a value.
For the hit detection Mathf.Approximately is good if you wan to check if 2 floats are exactly the same. If you'r checking 2 positions of moving objects the chance of them being exactly the same is very low and may rely on frame rate and such. Keeping your implementation you can check instead for a minimum distance between the 2 positions. You can tweak DISTANCE_THRESHOLD for a value that suits your better.
public class Apple_RandomSpawn : MonoBehaviour
{
private const float DISTANCE_THRESHOLD = 0.1f;
private Vector2 foodPos;
private Snake_Move snakeMove;
void Start()
{
SpawnApple();
}
public void Update()
{
SnakeAte();
}
public void SpawnApple()
{
foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
transform.position = new Vector2(foodPos.x, foodPos.y);
}
public void SnakeAte()
{
if (Vector3.Distance(foodPos, snakeMove) < DISTANCE_THRESHOLD)
{
SpawnApple();
}
}
}
Now remember that since your teleporting the apple to a purely random location it might teleport right back on top of your snake :).

Find every colliding object in C# and Unity3D using OnTriggerStay

As a gamer I'd phrase it like this: An AOE-Stun that stuns everyone it hits and then disappears.
I have enemy objects with a class "EnemyMovement" attached to it.
This class contains a function "Slow". I have a circle that has the class "StunSpell" attached to it. Now I want to call "Slow" once for every enemy object that collides with it.
void OnTriggerStay2D(Collider2D other){
if (other.gameObject.tag == "Enemy") { //Here i want to find every gameobject (by tag "Enemy")
other.GetComponent<EnemyMovement> ().Slow (3, 0f); //Call this function once per gameobject
//And as soon as every object that was found executed "Slow" once:
// -> Destroy(gameObject); to destroy the AOE-Object
}
}
This QA is a bit messy, but it's totally normal and commonplace in video games to simply ...
"...check the distance to each character..."
private void GrenadeTimer()
{
rb.isKinematic = true;
// here is our small explosion...
Gp.explosions.MakeExplosion("explosionA",transform.position);
float radius = splashMeasuredInEnemyHeight *
Gp.markers.GeneralExampleEnemyWidth();
List<Enemy> hits = new List<Enemy>();
foreach(Enemy e in Gp.enemies.all)
{
if (e.gameObject.layer != Grid.layerEnemies) continue;
if ( transform.DistanceTo(e) < radius ) hits.Add(e);
}
hits.SortByDistanceFrom( this.transform );
boss.SequentialHits(hits,damage);
boss.Done(this);
}
It's hard to imagine anything being simpler than that.
Note that we decide on a
radius
in meters, let's say "4.2 meters", inside which we want to do damage to the enemies. (Or, buff them, or whatever the case may be.)
This thing
Gp.enemies.all
is a List<Enemy> ... it holds all the enemies in the game at the moment. Simple right?
If you do not actually have a List<> of all the enemies (or players, NPCs - whatever is relevant) - you are ###ed. Start over on your learning project. Once you have a live List which is unit-tested, come back to this.
This line of code
Grid.layerEnemies
relates to the layer system in Unity. This often causes new hobbyists a problem ...
In reality, you can do absolutely nothing in Unity without using the layers system for every single thing.
It is beyond the scope of this article to get you started on using Layers, so we'll leave that aside. If you prefer, just leave out the line of code in your learning project.
Next. So - we run through and find all the enemies we want to affect. Let us say there are fifteen of them.
Notice that ...
The code gathers them in the loop. They end up in the "hits" list.
By all means, when you're just learning, you can simply apply the buff/damage/etc insinde the loop:
foreach(Enemy e in Gp.enemies.all)
{
if (e.gameObject.layer != Grid.layerEnemies) continue;
if ( transform.DistanceTo(e) < radius )
e.Slow(3f, 0f);
}
However, in any real game, you have to first make a list of the items, and then most typically have a manager (let's say, your "explosions manager!" - whatever) process those hits/ buffs/ damages/ whatever.
The reason is that you can rarely just throw in happenings all in the same frame. Imagine the sound/ visual effects when I quickly explode say fifteen enemies. Almost certainly your creative director / whoever will want them to happen "rat-a-tat-tat" you know? One way or another it will be far more complex than just "triggering them all". (Also, performance-wise you may well have to stagger them - obviously this can be a huge issue involving massive code bases; don't eve mention if the game is networked.) Note that in the actual example given, they end up being staggered, and indeed by distance outwards from the grenade, which looks great.
(As a curiosity, that particular code has been used to explode on the order of one billion grenades!)
Next issue:
looking at your code, you just "GetComponent". The other objects are "dumb". In reality you never do this. Note that in the example code here, there is an actual c# class Enemy
I will paste in some of Enemy at the bottom to give a flavour.
In fact, you almost always keep a List of "the primary c# Class attached to the players/enemies/etc". You generally don't bother much with the GameObject as such.
(If you do need to get to the GameObject, say to Destroy it, you just enemy.gameObject.)
So here, since we're simply checking the distance, you immediately have the Enemy class. (If you're using physics, you have to "GetComponent" to get to the Enemy class; of course you often do that, also.)
That being said - keep in mind the component-behavior nature of Unity:
That being said. My discussion is a bit slippery, there's an "Enemy" class (and indeed there's specific classes for enemies, such as "Dinosaur", "KillerRobot", "AttackParrot" and so on).
Try to bear in mind though, you really need to thing "behaviour-wise" in Unity.
There really shouldn't be an "AttackParrot" class. Really, there should just be components - behaviors - such as
Flies
ThrowsRocks
HasBrightColors
TakesDamage
LaserEyeballs
LandOnTrees
Conceptually an "AttackParrot" would just be a game object, which, happens to have, all of those six behaviors. In contrast, it would not have say "BreathesFire" and "CanHyperjump".
This is all discussed at length here:
https://stackoverflow.com/a/37243035/294884
It's a bit "purist" to say "Oh, there shouldn't be an 'Enemy' class, only behaviors" - but something to bear in mind.
Next up,
You have to have "general" components in a Unity game, which are accessible everywhere. Things like sound effects, scoring and so on.
Unity simply forgot to do this (they'll add it in the future).
Fortunately it's incredibly easy to do. Notice in the above there's a "boss" general component and a "soundEffects" general component which are called to.
In any script in your project that needs to use the general "boss" component or the general "sound" component, it's just...
Boss boss = Object.FindObjectOfType<Boss>();
Sound sound = Object.FindObjectOfType<Sound>();
That's all there is to it...
Boss boss = Object.FindObjectOfType();
This has been explained at vast length so many times, we need only link to it:
https://stackoverflow.com/a/35891919/294884
Note that, if you prefer, the alternate way to do this using PhysX is:
If you'd like to use the built-in physics: Physics2D.CircleCastNonAlloc
If you want, take a couple of days out to master that.
Note that the examples here are for a 2D game, it's identical in 3D.
(When you measure "distance" in 3D, if you game happens only on a flat surface, you may want to bother only measuring the distance on those two axis - but honestly it's irrelevant.)
You may ask, what is SortByDistanceFrom?
SortByDistanceFrom in fact ........ Sorts By Distance From
To save you typing, here is that extension:
public static void SortByDistanceFrom<T>(
this List<T> things, Transform t) where T:Component
{
Vector3 p = t.position;
things.Sort(delegate(T a, T b)
{
return Vector2.Distance(p, a.transform.position)
.CompareTo(Vector2.Distance(p, b.transform.position));
});
}
This raises another issue for new hobbyists.
Example Enemy class
Example - the Enemy class mentioned above ... included to add background.
So, all the actual enemy components (Dinosaurs, Wombats, XFighters, whatever) would derive from this one, overriding (concepts like motion, etc) as appropriate.
using UnityEngine;
using System.Collections;
public class Enemy:BaseFrite
{
public tk2dSpriteAnimator animMain;
public string usualAnimName;
[System.NonSerialized] public Enemies boss;
[Header("For this particular enemy class...")]
public float typeSpeedFactor;
public int typeStrength;
public int value;
// could be changed at any time during existence of an item
[System.NonSerialized] public FourLimits offscreen; // must be set by our boss
[System.NonSerialized] public int hitCount; // that's ATOMIC through all integers
[System.NonSerialized] public int strength; // just as atomic!
[System.NonSerialized] public float beginsOnRight;
private bool inPlay; // ie, not still in runup
void Awake()
{
boss = Gp.enemies;
}
void Start()
{
}
public void ChangeClipTo(string clipName)
{
if (animMain == null)
{
return;
}
animMain.StopAndResetFrame();
animMain.Play(clipName);
}
public virtual void ResetAndBegin() // call from the boss, to kick-off sprite
{
hitCount = 0;
strength = typeStrength;
beginsOnRight = Gp.markers.HitsBeginOnRight();
Prepare();
Gp.run.runLevel.EnemyAvailable();
}
protected virtual void Prepare() // write it for this type of sprite
{
ChangeClipTo(bn);
// so, for the most basic enemy, you just do that
// for other enemy, that will be custom (example, swap damage sprites, etc)
}
void OnTriggerEnter2D(Collider2D c)
{
GameObject cgo = c.gameObject;
// huge amount of code like this .......
if (cgo.layer == Grid.layerPeeps) // we ran in to a "Peep"
{
Projectile p = c.GetComponent<Projectile>();
if (p == null)
{
Debug.Log("WOE!!! " +cgo.name);
return;
}
int damageNow = p.damage;
Hit(damageNow);
return;
}
}
public void _stepHit()
{
if ( transform.position.x > beginsOnRight ) return;
++hitCount;
--strength;
ChangeAnimationsBasedOnHitCountIncrease();
// derived classes write that one.
// todo, actually should the next passage only be after all the steps?
// is after all value is deducted? (just as with the _bashSound)...
if (strength==0) // enemy done for!
{
Gp.coins.CreateCoinBunch(value, transform.position);
FinalEffect();
if ( Gp.skillsTest.on )
{
Gp.skillsTest.EnemyGottedInSkillsTest(gameObject);
boss.Done(this);
return;
}
Grid.pops.GotEnemy(Gp.run.RunDistance); // basically re meters/achvmts
EnemyDestroyedTypeSpecificStatsEtc(); // basically re achvments
Gp.run.runLevel.EnemyGotted(); // basically run/level stats
boss.Done(this); // basically removes it
}
}
protected virtual void EnemyDestroyedTypeSpecificStatsEtc()
{
// you would use this in derives, to mark/etc class specifics
// most typically to alert achievements system if the enemy type needs to.
}
private void _bashSound()
{
if (Gp.bil.ExplodishWeapon)
Grid.sfx.Play("Hit_Enemy_Explosive_A", "Hit_Enemy_Explosive_B");
else
Grid.sfx.Play("Hit_Enemy_Non_Explosive_A", "Hit_Enemy_Non_Explosive_B");
}
public void Hit(int n) // note that hitCount is atomic - hence strength, too
{
for (int i=1; i<=n; ++i) _stepHit();
if (strength > 0) // bil hit the enemy, but enemy is still going.
_bashSound();
}
protected virtual void ChangeAnimationsBasedOnHitCountIncrease()
{
// you may prefer to look at either "strength" or "hitCount"
}
protected virtual void FinalEffect()
{
// so, for most derived it is this standard explosion...
Gp.explosions.MakeExplosion("explosionC", transform.position);
}
public void Update()
{
if (!holdMovement) Movement();
// note don't forget Translate is in Space.Self,
// so you are already heading transform.right - cool.
if (offscreen.Outside(transform))
{
if (inPlay)
{
boss.Done(this);
return;
}
}
else
{
inPlay = true;
}
}
protected virtual void Movement() // override for parabolas, etc etc
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
}
}
So that's a general enemy class. Then you have derives of that, such as Ufo, Dinosaur, Tank, XWingFighter, etc etc. Here's Ufo ...
Note that it overrides many things. It seems to override "Prepare" (the comments suggest it "starts higher", and you can see it overrides other things.
using UnityEngine;
using System.Collections;
public class Ufo:Enemy
{
public Transform projectilePosition;
protected override void Prepare()
{
// ufo always start up high (and then zip up and down)
transform.ForceY(Gp.markers.StartHeightHighArea());
animMain.StopAndResetFrame();
animMain.Play(bn + "A");
animMain.StopAndResetFrame();
Invoke("ZipDown", Random.Range(0.6f,0.8f));
}
protected override void OnGamePause()
{
CancelInvoke();
StopAllCoroutines();
}
protected override void OnGameUnpause()
{
Attack();
if(transform.position.y<0f)
ZipUp();
else
ZipDown();
}
private float fZip = 3.3f;
private void ZipDown() { StartCoroutine(_zipdown()); }
private void ZipUp() { StartCoroutine(_zipup()); }
private IEnumerator _zipdown()
{
Grid.sfx.Play("Enemy_UFO_Move_Down");
float tLow = Gp.markers.StartHeightLowArea();
while (transform.position.y > tLow)
{
transform.Translate(0f,
fZip * -Time.deltaTime * mpsNow, 0f,Space.Self );
yield return null;
}
Attack();
Invoke("ZipUp", Random.Range(0.7f,1.4f));
}
private IEnumerator _zipup()
{
Grid.sfx.Play("Enemy_UFO_Move_Up");
float tHigh = Gp.markers.StartHeightHighArea();
while (transform.position.y < tHigh)
{
transform.Translate(0f,
fZip * Time.deltaTime * mpsNow, 0f,Space.Self );
yield return null;
}
Attack();
Invoke("ZipDown", Random.Range(0.7f,1.4f));
}
private void Attack()
{
Grid.sfx.Play("Enemy_UFO_Shoot");
animMain.Play();
Invoke("_syncShoot", .1f);
}
private void _syncShoot()
{
Gp.eeps.MakeEepUfo(projectilePosition.position);
}
protected override void ChangeAnimationsBasedOnHitCountIncrease()
{
// ufo just goes 4,2,out
if (strength == 2)
{
// if any attack, cancel it
CancelInvoke("ShootGreenPea");
CancelInvoke("Attack");
// on the ufo, anim only plays with attack
animMain.StopAndResetFrame();
animMain.Play(bn + "B");
animMain.StopAndResetFrame();
Invoke("Attack", 1.5f.Jiggle());
}
}
protected override void EnemyDestroyedTypeSpecificStatsEtc()
{
Grid.pops.AddToEnemyCount("ufo");
}
}
Let's think about the idea of "overrides in the Enemy class".
Overrides in an example Enemy class......
The many enemies have different types of movement, right? The general paradigm in the game is things moving in 2D, kinematically (ie, we "move them a certain amount of distance each frame" - not using PhysX here). So the different enemies move in radically different ways.
Here's one that moves in a certain way ... (the comments explain it)
protected override void Movement()
{
// it enters at about 2x normal speed
// the slow crossing of the stage is then about 1/2 normal speed
float mpsUse = transform.position.x < changeoverX ? mpsNow*.5f : mpsNow * 2.5f;
transform.Translate( -Time.deltaTime * mpsUse * typeSpeedFactor, 0f, 0f, Space.Self );
// recall that mpsNow was set by enemies when this was created, indeed
// nu.mpsNow = ordinaryMps * widthSpeedFactor;
}
Here's one that goes along, but sometimes "drifts downwards..."
protected override void Movement()
{
float mm = mpsNow * typeSpeedFactor;
if ( fallingMotion )
transform.Translate(
-Time.deltaTime * mm,
-Time.deltaTime * mm * fallingness, 0f,
Space.Self );
else
transform.Translate(
-Time.deltaTime * mm, 0f, 0f,
Space.Self );
}
Here's one that seems to follow a sinus ...
protected override void Movement()
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
float y = Mathf.Sin( basis-transform.position.x * (2f/length) );
y *= height;
transform.transform.ForceY( y );
}
Here's one that does complex speed changes, zooming around
protected override void Movement()
{
// it enters at about 2x normal speed
// it appears to then slow crossing of the stage about 1/2 normal speed
// however it then zooms to about 3x normal speed
float mpsUse = mpsNow;
float angled = 0f;
if ( transform.position.x > changeoverX) //enter quickly
mpsUse = mpsNow * 3f;
if ( transform.position.x < thenAngled) // for bead, angled section
{
mpsUse = mpsNow * 1.5f;
angled = leanVariation;
}
transform.Translate(
-Time.deltaTime * mpsUse * typeSpeedFactor,
-Time.deltaTime * mpsUse * typeSpeedFactor * angled,
0f, Space.Self );
}
You could make the movement anything - flying, running, bouncing, whatever.
It's all handled in c# by the protected override concept.
Static "globals" class ... simpler than a general component, for simply "holding" certain variables
Here's a trivial example of a static class which holds what you might as well call "globals", In a game engineering milieu, it's sensible to have certain things as "globals".
using UnityEngine;
using Shex;
using System.Collections;
using System.Collections.Generic;
static class Gp
{
public static Enemies enemies;
public static Pickups pickups;
public static Coins coins;
public static Peeps peeps;
public static Eeps eeps;
}
So TBC complicated "general" systems like SoundEffects, Boss, Scoring, AI, Networking, Social, InAppPurchase etc etc as described above, would indeed by the "preload" type objects as explained. (ie, you're using Boss boss = Object.FindObjectOfType(); at the top of any script, in any scene etc, that needs them.)
But for simply variables, things, that just need to access everywhere, you can use a trivial static class like that. Often just the one static class (called something like "Gameplay" or "Gp") does the job for the whole project.
{By all means, some teams would say "screw that, don't use a static class, put it in a "general" ("preload-style") component like Boss...."}
Note that of course a static class is NOT a real MonoBehavior - you "can NOT actually "DO" anything inside it in Unity". It's only for "holding variables" (most often, Lists) you want to access easily everywhere.
Again, be sure to remember that a static class is quite simply NOT a Unity game object or component - hence it very literally is not part of your game; you literally can not "do" anything whatsoever in a static class. To "do" anything, at all, in Unity it must be an actual Component, literally on a specific GameObject, at some position.
Thus for example it's completely useless trying to keep your "score" in a simple static class. Inevitably in relation to the "score" you will want to do all sorts of things (change the display on screen, award points, save encrypted preferences, trigger levels ... whatever, there is a lot of stuff to do). You absolutely cannot do that in a static - you can not "do" anything, at all, in a static - it must be an actual Unity game object. (ie, using the "preload system".) Once again statics are just for literally keeping track of some basically "global" variables, usually lists of things. (Things like "screen markers" are the perfect example.)
Just BTW in game development an "eep" is an enemy projectile and a "peep" is a player projectile, heh!
(Posted on behalf of the OP).
I solved it using a List to check which enemies are colliding with the Circle:
void CallStun(Collider2D coll){
if (coll.gameObject.tag == "Enemy") {
coll.GetComponent<EnemyHealth> ().TakeDamage (damage);
coll.GetComponent<EnemyMovement> ().Slow (3, 0f);
}
}
void OnTriggerStay2D(Collider2D other){
if (stun == false) {
return;
}
if (!collList.Contains (other)) { //if the object is not already in the list
collList.Add (other); //add the object to the list
CallStun (other); //call the functions on the specific object
} else {
Destroy (gameObject); //if no(more) collisions occur -> destroy the circle object
}
}
In the EnemyMovement class have an OnTriggerEnter method. Tag the spell and put a 2dCollider on it. When the OntriggerEnter is hit with tag == "slow spell" call the slow.
Basically I would do it the reverse way you are currently doing it. Doing it the other way will easily alow you to interact with anything that hits the enemy (since that's the intended behavior)

Categories

Resources