I feel like I'm missing some easy solution here, but I'm stuck on this. I'm calculating a score based on how far the player travels until they hit a building at the end of the course. The destruction score is separate from the distance score, and it increments until all of the building pieces have come to a rest.
I have an animation I want to play to add the distance score to the total destruction score and give the player's overall score, but I need the animation to trigger once the destruction score has stopped increasing. Right now, each piece of the building has code that checks if its moving and increments the score while true.
public class SkiLodgeScoreTracker : MonoBehaviour
{
Rigidbody rb;
private GameObject[] score;
private void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
score = GameObject.FindGameObjectsWithTag("Score");
}
private void Update()
{
//check if lodgePiece is moving, while it is, add to Score object
if(rb.velocity.magnitude >= 2f && !rb.isKinematic)
{
score[0].GetComponent<SkiScore>().addToSkiScore(2f);
}
}
}
Here's where I want to have the animation trigger once that score has stopped (this was another attempt I made, the logic doesn't work)
public Animator moveScore;
...
if(skiScore > 0)
{
previousScore2 = previousScore1;
previousScore1 = skiScore;
if(previousScore1 == previousScore2 && !scoreMoved)
{
moveScore.SetTrigger("EndCourse");
addScoreToSkiScore();
}
}
public void addScoreToSkiScore()
{
scoreMoved = true;
for(float i = score; i>0; i--)
{
skiScore += 1;
}
}
I wanted to grab the score on one frame and see if it equals the score on the next frame and, if so, then trigger the animation, but I feel like that's not a valid option.
Any ideas?
In SkiScore, keep track of how many pieces are moving, and when a piece stops and the count becomes 0, do your thing:
public class SkiScore : MonoBehaviour
{
int movingCount;
void Start()
{
ResetMovingCount();
/* ... */
}
public void ResetMovingCount() {movingCount = 0;} // call as needed
public void OnStartedMoving() {++movingCount;}
public void OnStoppedMoving()
{
if (--movingCount == 0) OnNoneMoving();
}
void OnNoneMoving() {/* do the thing */}
/* ... */
}
For each piece, use a flag to remember if it has moved recently and if it has, and it's no longer moving, let your score manager know:
public class SkiLodgeScoreTracker : MonoBehaviour
{
Rigidbody rb;
private GameObject[] score;
// flag used to recognize newly stopped movement
bool recentlyMoving;
// cache for GetComponent
SkiScore mySkiScore
private void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
score = GameObject.FindGameObjectsWithTag("Score");
// GetComponent is expensive, try not to call it in Update unless necessary
mySkiScore = score[0].GetComponent<SkiScore>();
}
private void Update()
{
//check if lodgePiece is moving, while it is, add to Score object
if(rb.velocity.magnitude >= 2f && !rb.isKinematic)
{
mySkiScore.addToSkiScore(2f);
recentlyMoving = true;
mySkiScore.OnStartedMoving();
}
else if (recentlyMoving)
{
recentlyMoving = false;
mySkiScore.OnStoppedMoving();
}
}
}
Related
I am trying to call a method from the script Dice and after that method is executed the value of the variable diceValue will change. This is the value that I want to take and use in the method from the script Levizja.
Levizja.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Levizja : MonoBehaviour
{
public Transform[] lojtaret;
public GameObject zari;
private int numer = 0;
public Dice vleraEzarit;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GetComponent<Rigidbody>().AddForceAtPosition(new Vector3(Random.Range(0, 500), Random.Range(0, 500) * 10, Random.Range(0, 500)), new Vector3(0, 0, 0), ForceMode.Force);
Debug.Log("U hodh zari me numer: " + Dice.getValue());
}
}
}
Dice.cs
using System.Collections.Generic;
using UnityEngine;
public class Dice : MonoBehaviour
{
Rigidbody rb;
bool hasLanded, thrown;
Vector3 initPosition;
[SerializeField] private static int diceValue;
private static int numriLojtareve;
//public int diceValue;
public DiceSides[] diceSides;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Reset();
RollDice();
}
if (rb.IsSleeping() && !hasLanded && thrown)
{
hasLanded = true;
rb.useGravity = false;
rb.isKinematic = true;
SideValueCheck();
}
else if (rb.IsSleeping() && hasLanded && diceValue == 0)
RollAgain();
}
void SideValueCheck()
{
diceValue = 0;
foreach(DiceSides side in diceSides)
{
if (side.OnGround())
{
diceValue = side.getValue();
Debug.Log(diceValue + " has been rolled!");
}
}
}
public static int getValue()
{
return diceValue;
}
}
Some of the methods are not included just to address only the issue.
I want to execute the Update method in Dice.cs which will call SideValueCheck method. This way the variable diceValue will be updated. After that I want the Update method in Levizja.cs to execute this way the new value will be stored there.
What happens is the first time I get the value 0 and the next run I get the last value that dice had. So if first time it landed 3 it shows 0. Next time it lands 2 it shows 3 and so on.
You could adjust this in the Script Execution Order and force a certain order of the executions of the same event message type (Update in this case).
However, this won't be enough. You rather want to wait until the dice has a result.
So before/instead of touching the execution order I would rather rethink the code structure and do something else. E.g. why do both your scripts need to check the user input individually?
Rather have one script call the methods of the other one event based. There is also no reason to have things static here as you already have a reference to an instance of a Dice anyway in vleraEzarit
public class Dice : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private DiceSides[] diceSides;
[Header("Debug")]
[SerializeField] private int diceValue;
private bool hasLanded, thrown;
private Vector3 initPosition;
private int numriLojtareve;
// if you still also want to also provide a read-only access
// to the current value
public int DiceValue => diceValue;
// Event to be invoked everytime there is a new valid dice result
public event Action<int> OnHasResult;
private void Awake()
{
if(!_rigidbody)
{
_rigidbody = GetComponent<Rigidbody>();
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Reset();
RollDice();
}
if (_rigidbody.IsSleeping() && !hasLanded && thrown)
{
hasLanded = true;
_rigidbody.useGravity = false;
_rigidbody.isKinematic = true;
SideValueCheck();
}
else if (_rigidbody.IsSleeping() && hasLanded && diceValue == 0)
{
RollAgain();
}
}
void SideValueCheck()
{
foreach(DiceSides side in diceSides)
{
if (side.OnGround())
{
diceValue = side.getValue();
Debug.Log(diceValue + " has been rolled!");
// Invoke the event and whoever is listening to it will be informed
OnHasResult?.Invoke(diceValue);
// also I would return here since i makes no sense to continue
// checking the other sides if you already have a result
return;
}
}
// I would move this here
// In my eyes it is clearer now that this is the fallback case
// and only happening if nothing in the loop matches
diceValue = 0;
}
}
And then make your Levizja listen to this event and only act once it is invoked like e.g.
public class Levizja : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private Transform[] lojtaret;
[SerializeField] private GameObject zari;
[SerializeField] private Dice vleraEzarit;
private int numer = 0;
private void Awake()
{
if(!_rigidbody)
{
_rigidbody = GetComponent<Rigidbody>();
}
// Attach a callback/listener to the event
// just out of a habit I usually remove it first to make sure it can definitely only be added once
vleraEzarit.OnHasResult -= HandleDiceResult;
vleraEzarit.OnHasResult += HandleDiceResult;
}
private void OnDestroy()
{
// make sure to remove callbacks once not needed anymore
// to avoid exceptions
vleraEzarit.OnHasResult -= HandleDiceResult;
}
// This is called ONCE everytime the dice has found a new result
private void HandleDiceResult(int diceValue)
{
_rigidbody.AddForceAtPosition(new Vector3(Random.Range(0, 500), Random.Range(0, 500) * 10, Random.Range(0, 500)), new Vector3(0, 0, 0), ForceMode.Force);
Debug.Log("U hodh zari me numer: " + Dice.getValue());
}
}
Ideally the diceValue should be returned by the method and Dice should not have a state. If you absolutely need Dice to have a state then it should not be static.
I need help with my script. I really can't understand how to make it. I have a script that counts time but I don't know how to make it so that when the game is over the time appears in my Game Over scene as the highest score. I only need to save this high score for one time. After the game is closed the score can be removed. In the game, I have a countdown timer, when it reaches 0 the game is over.
This is my TimeCounter script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TimeCounter: MonoBehaviour
{
public Text timerText;
private float startTime;
bool finishCountTime = false;
public string minutes, seconds;
// Use this for initialization
void Start()
{
}
public void startTheTimer()
{
Invoke("startTime", 10);
startTime = Time.time;
}
public void stopTheTimer()
{
finishCountTime = true;
}
// Update is called once per frame
void Update()
{
float t = Time.time - startTime;
minutes = ((int)t / 60).ToString();
seconds = (t % 60).ToString("f2");
timerText.text = minutes + ":" + seconds;
}
}
and this is GameManager script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Threading;
public class GameManager: MonoBehaviour
{
[SerializeField] private float timer = 30;
private void Awake()
{
pop.onClicked += PopClicked;
}
private void OnDestroy()
{
pop.onClicked -= PopClicked;
}
private void PopClicked()
{
if (pop.Unclicked.Count == 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
private void Update()
{
if (timer <= 0)
{
SceneManager.LoadScene("GameOver");
}
}
}
Here is an example snippet of how to use DontDestroyOnLoad along with the SceneLoaded delegates.
using UnityEngine;
using UnityEngine.SceneManagement;
public class ExampleScript : MonoBehaviour
{
private float score;
private void Awake()
{
var objs = FindObjectsOfType(typeof(ExampleScript));
if (objs.Length > 1)
{
Destroy(this.gameObject);
}
DontDestroyOnLoad(this.gameObject);
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if(scene.name == "gameOver")
{
// display the time here as we are showing the score
Debug.Log(score);
}
else
{
// we loaded into another scene - presumably your game scene, so reset the score
score = 0;
}
}
}
As it is DontDestroyOnLoad, you will need to check if the current scene contains the object already and destroy it. As you have two scripts running your logic, you might need to slightly tweak it so that one of the managers is able to communicate to one another to pull your score or move it to the same script. I have greatly over-generalized your issue as I was not sure which part you did not understand.
Edit: Here is the more in-depth answer on how to achieve what you would like to do.
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class TestScript : MonoBehaviour
{
private float score; // our current score
private bool updateScore = false; // whether or not we should update our score UI every frame
private Text ScoreText = null; // our text object that updates our score
private void Awake()
{
// as the object is DontDestroyOnLoad, we need to check if another instance exists
// if one does, we need to delete it as if multiple exist in one scene, there could be issues
// if a player continually replays your scene where this object starts, then multiple will exist
var objs = FindObjectsOfType(typeof(TestScript));
if (objs.Length > 1)
{
Destroy(this.gameObject);
}
DontDestroyOnLoad(this.gameObject);
}
private void OnEnable()
{
// add the callback for when a scene is loaded
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
// remove the callback from when a scene is loaded
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// grab the new reference to out score UI
ScoreText = GameObject.Find("Score").GetComponent<Text>();
// when we have the gameover scene, display our final score
if (scene.name == "gameover")
{
// display the time here as we are showing the score
ScoreText.text = "Final Score: " + score.ToString("#");
updateScore = false;
}
else
{
// we loaded into another scene - presumably your game scene, so reset the score
score = 0;
updateScore = true;
}
}
private void Update()
{
// if we are in the game scene and we should update score, update the score however you like
if(updateScore)
{
score += Time.deltaTime;
ScoreText.text = "Current Score: " + score.ToString("#");
}
// this is here solely for testing - instead of a real game just hit space to end the game and restart it
if(Input.GetKeyDown(KeyCode.Space))
{
if(SceneManager.GetActiveScene().name == "gameover")
{
SceneManager.LoadScene(0);
}
else
{
SceneManager.LoadScene("gameover");
}
}
}
}
Here is an example of the script working as well as the two scene hierarchies. For this to work exactly as I have it, you will need two scenes. In your build settings, the first scene will be your game scene, and then there needs to be some other scene in the build settings with the name gameover. As well, you will need to have some sort of UnityEngine.UI.Text component that is called Score in both scenes.
All of this can be changed, but this is what you need to do to get it to work as I have it set up.
I am creating a small 2d game in which you need to survive. Each tree has its own strength = 5. When the player collides and presses the left mouse button then strength is -1 and player wood stat is +1. when the tree strength is equal or less than 0 then the tree is destroyed. Here is my code : (Question is after the code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Stats: MonoBehaviour
{
//Player Stats
public float hp = 100;
public float wood = 0;
//Tree stats
public float treeLogStrenth = 5;
//Text
public Text woodText;
void Start ()
{
woodText.text = "0";
}
void Update ()
{
woodText.text = wood.ToString();
if (treeLogStrenth <= 0)
{
Destroy(GetComponent<PlayerCollisions>().;
}
}
}
here is another code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollisions: MonoBehaviour
{
public void OnCollisionStay2D (Collision2D collisionInfo)
{
if (collisionInfo.gameObject.tag == "Tree" && Input.GetMouseButtonDown(0))
{
string treeName = collisionInfo.gameObject.name;
GetComponent<Stats>().wood += 1;
GetComponent<Stats>().treeLogStrenth -= 1;
}
}
}
MY QUESTION : How to make instead of creating all the time another game object for each tree and destroying it, only like one simple code that will do it. please help (UNITY NEWEST VERSION)
So to clear things up: Stats should be attached to the player, right?
You should not do things every frame in Update but rather event driven like
public class Stats : MonoBehaviour
{
// You should never allow your stats to be set via public fields
[SerializeField] private float hp = 100;
[SerializeField] private float wood = 0;
// if you need to read them from somewhere else you can use read-only properties
public float HP => hp;
public float Wood => wood;
[SerializeField] private Text woodText;
private void Start ()
{
woodText.text = "0";
}
public void AddWood(int amount)
{
wood += amount;
woodText.text = wood.ToString();
}
}
Then each tree should rather have its own component instance attached like e.g.
public class Tree : MonoBehaviour
{
[SerializeField] private float treeLogStrenth = 5;
public void HandleClick(Stats playerStats)
{
// if this tree has wood left add it to the player stats
if(treeLogStrength > 0)
{
playerStats.AddWood(1);
treeLogStrenth -= 1;
}
// destroy this tree when no wood left
if (treeLogStrenth <= 0)
{
Destroy(gameObject);
}
}
}
and then finally also attached to the Player
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store the currently collided tree in order to reuse it
private Tree currentlyCollidedTree;
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// Get the Tree component of the tree object you are currently colliding with
// but only once and store the reference in order to reuse it
if(!currentlyCollidedTree) currentlyCollidedTree= collisionInfo.gameObject.GetComponent<Tree>();
// tell the tree to handle a click and pass in your stats reference
currentlyCollidedTree.HandleClick(stats);
}
}
// reset the currentlyCollidedTree field when not colliding anymore
private void OnCollisionExit2D()
{
currentlyCollidedTree = null;
}
}
Well yes there would be an alternative where not every tree needs its own component instance but I would recommend to not use it actually!
You could make your player remember which tree he already clicked in something like
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store all trees we ever clicked on in relation to the according available wood
private Dictionary<GameObject, int> encounteredTrees = new Dictionary<GameObject, int>();
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// did we work on this tree before?
if(encounteredTrees.Contains(collisionInfo.gameObject))
{
// if so gain one wood and remove one from this tree
stats.AddWood(1);
encounteredTrees[collisionInfo.gameObject] -= 1;
// destroy the tree if no wood available and remove it from the dictionary
if(encounteredTrees[collisionInfo.gameObject] <= 0)
{
encounteredTrees.RemoveKey(collisionInfo.gameObject);
Destroy(collisionInfo.gameObject);
}
}
else
{
// the first time we work this tree gain one wood and add
// the tree as new entry to the dictionary with 4 wood left
stats.AddWood(1);
encounteredTrees.Add(collisionInfo.gameObject, 4);
}
}
}
}
this however limits you extremely and it is not possible anymore that you have e.g. different Tree prefabs with different amount of available wood ...
In Unity3D my enemy is not taking damage upon colliding with my projectile explosion.
Although this is not the case as it the health variable is unaffected upon colliding with my projectile explosion.
My Enemy and Barrel classes inherit from Entity which handles the taking of damage (subtracting the damage variable from the health variable). Although only the barrel class is working as intended.
The tags are 100% correct and I would prefer to continue using inheritance so please no suggestions to change the method in which my classes take damage.
the class that Enemy and Barrel inherit from
using UnityEngine;
using System.Collections;
public class Entity : MonoBehaviour {
public float health = 25;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public virtual void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Destroy(this.gameObject);
}
}
}
Enemy class
using UnityEngine;
using System.Collections;
public class Enemy : Entity {
private NavMeshAgent agent;
public GameObject target;
// Use this for initialization
void Start () {
agent = GetComponent<NavMeshAgent> ();
}
// Update is called once per frame
void Update () {
agent.SetDestination (target.transform.position);
}
}
Barrel class
using UnityEngine;
using System.Collections;
public class Barrel : Entity {
private Transform myTransform;
//Effects
public GameObject barrelExplosion;
public GameObject explosionDamage;
public GameObject explosionSound;
// Use this for initialization
void Start () {
myTransform = this.transform;
}
// Update is called once per frame
void Update () {
}
public override void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Instantiate(barrelExplosion, myTransform.position, myTransform.rotation);
Instantiate(explosionSound, myTransform.position, myTransform.rotation);
Instantiate(explosionDamage, myTransform.position, myTransform.rotation);
Destroy(this.gameObject);
}
}
}
ExplosionAOE the class that sends the damage
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ExplosionAOE : MonoBehaviour {
public float damage = 100.0f;
public float lifeTime = 0.05f;
private float lifeTimeDuration;
public List<GameObject> damageTargets = new List<GameObject>();
public float radius = 15.0f;
GameManager gameManager;
void Start() {
gameManager = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
//Destroy (this.gameObject, lifeTime);
lifeTimeDuration = Time.time + lifeTime;
transform.GetComponent<SphereCollider>().radius = radius;
}
void Update() {
//Explosion finishes, damage targets and remove AOE field
if (Time.time > lifeTimeDuration) {
foreach (GameObject target in damageTargets) {
if (target != null) {
//Calculate damage based on proximity to centre of explosion
float thisDamage = ((radius - Vector3.Distance(target.transform.position, transform.position)) / radius) * damage;
print(thisDamage);
target.GetComponent<Entity>().takeDamage(thisDamage);
//target.SendMessage("takeDamage", damage); //<< This is not good code. Let's fix this!
}
}
Destroy(this.gameObject);
}
}
void OnTriggerEnter(Collider otherObject) {
if (otherObject.gameObject.tag == "Enemy") {
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Vector3 jumpVector = (otherObject.transform.position - transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
}
Sorry this is a bit of a lengthy one and EVERYTHING is tagged correctly so that is not the issue, thanks.
Problem 1.
Use "Debug.Log" everywhere
void OnTriggerEnter(Collider otherObject) {
Debug.Log("in trig");
Debug.Log("otherObject.gameObject.tag is " + otherObject.gameObject.tag);
if (otherObject.gameObject.tag == "Enemy") {
Debug.Log("a");
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Debug.Log("b");
Vector3 jumpVector = (otherObject.transform.position -
transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
In particular, in Entity and Enemy.
Questions such as this one are instantly answered by tracking with Debug.Log.
Problem 2.
It's a PITA getting the relationships between triggers, rigidbody, etc.
It's very likely that's a problem here.
http://docs.unity3d.com/Manual/CollidersOverview.html
Go down to the annoying "trigger action matrix" and work from there.
Problem 3.
As a rule, never use the "tags" feature in Unity. (They only added tags to help "hello world" tutorials.)
In practice you use layers everywhere and always:
(Layers are particularly essential in shooting games: every single category needs a layer.)
Problem 4.
The code shown is definitely looking good. Here's some example code not unlike yours for tips.
Trivial example, note the breakaway code (the returns) inside the OnTrigger, you should do that).
Also,
use extentions
everywhere and always in Unity. Quick tutorial
it's the #1 tip if you actually want to work professionally.
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;
}
..........
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)
{
// we can ONLY touch either Biff or a projectile. to wit: layerBiff, layerPeeps
GameObject cgo = c.gameObject;
if ( gameObject.layer != Grid.layerEnemies ) // if we are not enemy layer....
{
Debug.Log("SOME BIZARRE PROBLEM!!!");
return;
}
if (cgo.layer == Grid.layerBiff) // we ran in to Biff
{
Gp.billy.BiffBashed();
// if I am an enemy, I DO NOT get hurt by biff smashing in to me.
return;
}
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;
}
Debug.Log("Weirded");
}
public void _stepHit()
{
if ( transform.position.x > beginsOnRight ) return;
++hitCount;
--strength;
ChangeAnimationsBasedOnHitCountIncrease();
// derived classes write that one.
if (strength==0) // enemy done for!
{
Gp.coins.CreateCoinBunch(value, transform.position);
FinalEffect();
if ( Gp.superTest.on )
{
Gp.superTest.EnemyGottedInSuperTest(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.biff.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) // biff 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();
if (offscreen.Outside(transform))
{
if (inPlay)
{
boss.Done(this);
return;
}
}
else
{
inPlay = true;
}
}
protected virtual void Movement()
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
}
......
/*
(frite - flying sprite)
The very base for enemies, projectiles etc.
*/
using UnityEngine;
using System.Collections;
public class BaseFrite:MonoBehaviour
{
[System.NonSerialized] public float mpsNow;
// must be set by the boss (of the derive) at creation of the derive instance!
private bool _paused;
public bool Paused
{
set {
if (_paused == value) return;
_paused = value;
holdMovement = _paused==true;
if (_paused) OnGamePause();
else OnGameUnpause();
}
get { return _paused; }
}
protected bool holdMovement;
protected virtual void OnGamePause()
{
}
protected virtual void OnGameUnpause()
{
}
protected string bn;
public void SetClipName(string clipBaseName)
{
bn = clipBaseName;
}
}
Is more easy if in ExplosionAOE/OnTriggerEnter function you call the takeDamage function:
scriptCall = otherObject.GetComponent(EnemyScript);
scriptCall.takeDamage(damage);
I am new at Unity. I have successfully made a player can shoot a bullet to the enemy depends on the current wave, and I make where the enemy have it is own life depends on the current wave multiply with certain value. If the enemy life is 0, the enemy dies. But, the problem is: for example there are 2 enemies on the scene and the enemy life is 5 and the player start shooting the first enemy until the enemy life decreases to 2, when the player start shooting the second enemy, the enemy life is not 5 anymore, but it is 2 until one of the 2 cubes is dead and the enemy life reset to 5.
How can I solve this problem?
Here is the bullet script:
public class BulletManager : MonoBehaviour
{
private ScoreManager scoreManager;
private PlayerController playerController;
private EnemyManager enemyManager;
public int bulletPower;
private void Start()
{
scoreManager = GameObject.Find("Game Manager").GetComponent<ScoreManager>();
playerController = GameObject.Find("Character").GetComponent<PlayerController>();
enemyManager = GameObject.Find("Game Manager").GetComponent<EnemyManager>();
bulletPower = playerController.currentWave;
}
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Enemy")
{
enemyManager.enemyLife -= bulletPower;
if (enemyManager.enemyLife <= 0)
{
scoreManager.Points += (2 * playerController.currentWave);
enemyManager.enemyLife = playerController.currentWave * 5;
Destroy(col.gameObject);
}
}
}
}
And here is the enemy script:
public class EnemyManager : MonoBehaviour
{
private ScoreManager scoreManager;
private SoundManager soundManager;
private PlayerController playerController;
public int enemyLife;
private void Start()
{
scoreManager = GameObject.Find("Game Manager").GetComponent<ScoreManager>();
soundManager = GameObject.Find("Game Manager").GetComponent<SoundManager>();
playerController = GameObject.Find("Character").GetComponent<PlayerController>();
enemyLife = playerController.currentWave * 5;
}
private void Update()
{
if (playerController.life <= 0)
{
Invoke("Restart", 1);
}
}
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Bullet")
{
soundManager.PlaySound("Enemy Dead");
Destroy(col.gameObject);
}
else if (col.gameObject.tag == "Destroyer")
{
playerController.life--;
}
}
private void Restart()
{
scoreManager.SendToHighScore();
Application.LoadLevel(0);
}
}
The problem is that all of your enemies use the EnemyManager class to manage their health. What your code does is count the amount of hits on all enemies and then destroys the last one that was hit. My suggestion is to add an EnemyStats class to manage stats specific to each enemy (name of the enemy, the amount of damage they can do, weapon graphics, sound effects, the skys the limit). I hate to give you the answer right off the bat (or at least a basic one) since this is a good learning opportunity but here is a basic script that should do what you need and the changes you need to make to your BulletManager script.
EnemyStats.cs (Attach to the enemies game object)
public class EnemyStats : MonoBehaviour
{
public int currentHealth;
public int maxHealth;
public float increasePerWave;
public int bulletPower;
// use a negative value for healing
public void ReduceHealth(int damage)
{
currentHealth -= damage;
}
public bool IsDead()
{
return currentHealth <= 0;
}
public int GetDamage()
{
return bulletPower;
}
/// Restores enemy to their max health for the current wave. Call this after creating an spawning.
public void RestoreHealth(int wave)
{
currentHealth = (int)(maxHealth * increasePerWave * (wave + 1));
}
}
BulletManager update
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Enemy")
{
EnemyStats stats = col.GetComponent<EnemyStats>();
if (stats)
{
stats.ReduceHealth(bulletPower);
if (stats.IsDead())
{
scoreManager.Points += (2 * playerController.currentWave);
Destroy(col.gameObject);
}
}
}
}