Let me explain the question first :)
I have a hero and an enemy. It is a fighting game. Both hero and enemy have idle, block, punch and getHit animations and states in the Animation Controller. The hero and enemy have scripts attached to them. The hero is controlled by player and then enemy is controller by AI.
Now I attach the script to hero first and then the enemy. Now when the enemy punches and if hero is not defending the hero takes a hit. But if the enemy is not blocking and the hero hits the enemy doesnt take the hit. This is because the script was attached to hero first.
Now if I remove the script from both and attach the enemy script first to enemy and then attach the hero script to hero. The wiseversa is true. In the enemy the hits will be registered and on the hero the hits wont be registered.
I am not sure why this is happening and I have tried different things and still the problem persists.
I also tried looking everywhere online for solution but none of them addressed my concern.
Thanks :)
below is the enemy script that checks is enemy can take hit
void Update () {
myTick++;
currentTick = myTick;
GameObject player = GameObject.Find("theDude");
Animator pAnim = player.GetComponent<Animator>();
//Getting Hit
if (pAnim.GetBool("tIsPunching"))
{
if (anim.GetBool("bEnemyIsDefending") == false)
{
Debug.Log("enemy got hit");
anim.SetTrigger("tEnemyGotHit");
anim.SetBool("bEnemyIsDefending", true);
}
}
}
And here is the hero script that checks if hero can take hit.
void Update () {
totalTime += Time.deltaTime;
GameObject enemy = GameObject.Find("Enemy");
Animator eAnim = enemy.GetComponent<Animator>();
//Getting Hit
if (eAnim.GetBool("tEnemyIsPunching"))
{
if (anim.GetBool("bIsDefending") == false)
{
Debug.Log("player got hit");
anim.SetTrigger("tGotHit");
}
}
}
Instead of get object I used to have a public GameObject and attached the hero and enemy in the respective classes. But It doesnt make any difference.
I think your problem lies in your use of the triggers tEnemyIsPunching and tIsPunching. Triggers get reset whenever they cause a transition to occur
(see: https://docs.unity3d.com/Manual/AnimationParameters.html).
In your case tIsEnemyPunching or (tIsPunching) is getting reset in the same frame as it gets set. Here is an example of what one update loop may look like in your game if the hero script is added first:
Hero Update()
Check if enemy is punching
He is not, so don't do anything
Enemy Update()
Punch! Set 'bIsEnemyPunching' = true
Animation Update()
bIsEnemyPunching is true so transition to the punching animation
reset bIsEnemyPunching = false
On the next update let's look at what happens in the hero update:
Hero Update()
Check bIsEnemyPunching
bIsEnemyPunching was reset in the previous frame, so it is false
Since the enemy isn't punching don't do anything
So Hero never sees the punch because bIsEnemyPunching got reset before Hero had a change to check it.
This is why the order of adding the scripts matters. Whichever script updates first is able to punch because the second script will see the trigger before it gets reset. However, the second script to update will never be able to punch because the trigger gets reset before the other scripts gets a chance to update.
One solution is to check the name of the animation state instead of the trigger value.
static int punchStateHash = Animator.StringToHash("Punch");
AnimatorStateInfo enemyState = eAnim.GetCurrentAnimatorStateInfo(0);
if (enemyState.nameHash == punchStateHash){
//The enemy is punching!
}
Alternatively, whenever a punch is triggered just call a function on whichever character is getting punched. So when the enemy punches the hero the Punch(...) function on the enemy should call TakePunch(...) on hero (or whatever you want to call those functions). The hero then checks his own state to determine if he is blocking.
Additional Note
You should avoid using the following code in an update function:
GameObject player = GameObject.Find("theDude");
Animator pAnim = player.GetComponent<Animator>();
These functions are very slow because Unity must search all objects for the one called theDude and then search all it's components to find the animator. Do this once and save off the result.
Related
Right now, I have a multiplayer game where the player fires projectiles at other players that have a certain amount of bubbles. Once a player has lost all of their bubbles, they lose.
The issue I had previously was that on one screen, a first player could shoot out a second player's bubbles, but not all of the bubbles on the second player's screen would be popped, so I am trying to sync it over the network somehow.
The issue I'm seeing is that NetworkServer.Destroy requires finding the GameObject you intend to destroy by its NetworkIdentity, but only the root Player GameObject is allowed to have the NetworkIdentity component on it. What's the best way I can sync the destruction of a child object of a player?
The https://unity3d.com/fr/learn/tutorials/topics/multiplayer-networking/ course is very similar to the game you describe. It has a Bullet script attached to the fired bullets. If the bullets touched something, it check if it's a player, and apply damage to it (the player prefab has a Health script attached to it).
using UnityEngine;
using System.Collections;
public class Bullet : MonoBehaviour {
void OnCollisionEnter(Collision collision)
{
var hit = collision.gameObject;
/* Code below is from the original example
var health = hit.GetComponent<Health>();
if (health != null)
{
health.TakeDamage(10);
}*/
var bubble = hit.GetComponent<BubbleBehavior>();
if (bubble != null)
{
bubble.Popped(); // Implement a Popped function doing the magic
}
Destroy(gameObject);
}
}
In your case you can do the same, but instead of checking if the touched object is a player, simply check if it is a bubble, and do whatever you want from the touched bubble.
In the original example, they sync the health (it is a "SyncVar"), in your case you can sync the number of bubbles remaining.
I have a plane that contains around 200 tiles, and I'd like to be able to detect which tile has been clicked by the player. Each tile has a box collider attached. I've also created an empty game object in the scene to which I attached EventSystem and the below script:
public class PlaneBehaviour : MonoBehaviour, IPointerDownHandler {
public GameObject ClickSymbol;
public void Start() {
var physicsRaycaster = FindObjectOfType<PhysicsRaycaster>();
if (physicsRaycaster == null) {
Camera.main.gameObject.AddComponent<PhysicsRaycaster>();
}
}
public void OnPointerDown(PointerEventData eventData) {
var o = eventData.pointerCurrentRaycast.gameObject;
}
}
Now, when user clicks anywhere, nothing happens - I've put a breakpoint inside the OnPointerDown method, but it's not getting hit (and the code inside the Start method is run - I've also verified that by putting a breakpoint in there).
There are many things that could cause the callback function not being called. Before trying these, put Debug.Log inside the OnPointerDown function to make sure that it's being called:
1.Go to GameObject-->UI--->EventSystem.
A GameObject named "EventSystem" will be created and will proper scripts such as EventSystem, StandAloneInputModule attached to it.
2.The PlaneBehaviour script must be attached to the GameObject with the Collider not to an empty GameObject. It detects click on the Objects it is attached to.
3.If you are using a collider that ends with "2D" in its name then Physics2DRaycaster must be used instead of PhysicsRaycaster.
4.Are you using more than two cameras? If so manually attach PhysicsRaycaster to the correct Camera that is currently showing that Object.
5.Re-size the GameObjects you want to detect clicks on to be bigger. I've seen doing this solve the problems sometimes. You then have to move the camera back if this works.
I am trying to create a Minesweeper + clicker/idle game for practice. For now, I can't get the clicking to work properly. I spawned in all objects through code meaning they are all the same prefabs but at different locations. When I click them the code activates for all of them and all of the tiles change to being open. I am not sure if I'm using the wrong click function here, as I know there is also an OnMouseDown() function instead of Input.GetMouseButtonDown(0) but the only one where the code activates is in the second one.
public class TileClick : MonoBehaviour {
public GameObject openTile;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown(0)) {
Instantiate(openTile, transform.position, Quaternion.identity);
Object.Destroy(this.gameObject);
}
}
}
That's my clicking detection. While it does detect clicks, as I said before, it detects them for ALL tiles and activates them all. I only want it to activate on one of them. I've seen RayCasting stuff but I really need more explanation on how that thing works (if that's the solution).
If the TileClick script component is attached to your prefab, then for every tile you instantiate, you have a corresponding TileClick component attached.
When you click, the input is detected by all of these blocks simultaneously, and each of them calls Instantiate but the transform.position is the one corresponding to the tile that makes the call. This is why it seems like all the tiles open up simultaneously, because you are, in fact, instantiating an "open" tile on each of them every time you click.
You should keep all your user input code in a separate MonoBehaviour, and attach that to a single empty GameObject in your scene. For detecting the hit, you should attach a 2DCollider component to your tile prefab (you can decide the type according to the shape of your tiles), and then just use a simple RayCast in your user input code to instantiate an "open" tile at the position where the hit occurs:
void Update () {
if (Input.GetMouseButtonDown(0)) {
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition),
Vector2.zero);
if(hit.collider != null)
{
Instantiate(openTile,
hit.collider.gameObject.transform.position,
Quaternion.identity);
}
}
I highly recommend understanding how RayCast works since it is a very useful tool. Here's a good place to start: https://docs.unity3d.com/ScriptReference/Physics2D.Raycast.html
What I'm trying to do is when it hits a triggered collider I want it to minus an int, but what unity does is minus it with 3 instead, and I have it attached to the player itself
lifecontroller lves;
public GameObject gm;
CoinScript coins;
// Use this for initialization
void Start () {
gm = GameObject.FindGameObjectWithTag("GM");
coins = gm.GetComponent<CoinScript>();
lves = gm.GetComponent<lifecontroller>();
}
void OnTriggerEnter2D(Collider2D other)
{
if(other.tag == "Evil")
{
lves.lives -= 1;
Debug.Log("U hit it");
}
}
// Update is called once per frame
void Update () {
if(lves.lives == 0)
{
Debug.Log("u died");
}
}
Judging from the screenshot, your script is attached to the Player GameObject multiple times. Select your Player GameObject, and remove the duplicate Script.
Make sure there is only one script attached to it. It is probably attached there 3 times.
This problem can also happen if you are instantiating multiple Player GameObjects during run-time as this will duplicate your script too.
EDIT:
With the updated Image in your comment, the problem is that you have 3 Colliders on your Player. It is true that only one of them is made IsTrigger but this is a problem if the GameObject with the Evil tag is marked as IsTrigger too.
You have 2 options to fix this:
1.Select the GameObjects with the Evil tag and make sure that no Collider attached to it is marked as IsTrigger. Unceheck all IsTrigger properties on the Collider of all Evil tagged GameObjects only. This should fix your problem.
2.Only have one Collider on Player GameObject. Remove the other 2. This again should fix your problem.
The trigger is definitly happening 3 times, your screenshot shows that (there is a 3 on the right since duplicate logs get collapsed).
This might occur because of the collider shape I think (circle). Try using a bool that us set on trigger enter and clear on trigger exit (or even Update should be ok for clearing).
This is what I suggested to debug your problem:
void OnTriggerEnter2D(Collider2D other)
{
if(other.tag == "Evil")
{
lves.lives -= 1;
Debug.Log("U hit it", other.gameObject);
}
}
And by looking at your screenshot you are getting 3 log messages.
Always use don't collapse in console window to see whats happening
If I have a player and let's say a red ball hits my player and the player goes on fire... OK easy enough I did that. But now I want to add to the code below under the if statement that a blue ball hits my player and freezes or a black ball and causes death animation... But it's just not working out to add multiple animation events to 1 player.
Public GameObject GoneGo;
void OnTriggerEnter2D(Collider2D collisionObject)
{
if (collisionObject.gameObject.tag == "Orb")
{
PlayGone();
}
}
void PlayGone()
{
GameObject gone = (GameObject)Instantiate(GoneGo);
gone.transform.position = transform.position;
}
What you could do is perform a name check on the collider that impacts the player. You already have it checked the tag, have it check for redball,blueball, or blackball as the GameObject name afterwards.
You can handle multiple animations by looking into unitys mechanim system, which is the animator component (not animation component).
https://community.mixamo.com/hc/en-us/articles/203879268-Unity-Mecanim-Animation-Basics
What you would do after setting up the animator is have it SetTrigger for different trigger parameters. Or, you could use something like SetBool with a bool in the parameters like HitByBlueBall or HitByRedBall.
Hope this helps!